diff --git a/.github/workflows/Build-Rollout.yml b/.github/workflows/Build-Rollout.yml index 6627a85..23ba133 100644 --- a/.github/workflows/Build-Rollout.yml +++ b/.github/workflows/Build-Rollout.yml @@ -15,10 +15,10 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 - - name: use NodeJS v22.12.0 + - name: use NodeJS v24.14.0 uses: actions/setup-node@v4 with: - node-version: '22.12.0' + node-version: '24.14.0' registry-url: 'https://registry.npmjs.org' always-auth: true @@ -28,9 +28,15 @@ jobs: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Install Dependencies - run: npm install + run: npm ci - - name: run Rollout Build + - name: Run Checks + run: npm run check + + - name: Set Release Version + run: npm run release:version + + - name: Run Build run: npm run build - name: Publish Library diff --git a/.github/workflows/Build-Validation.yml b/.github/workflows/Build-Validation.yml index 61b65ce..caf02fe 100644 --- a/.github/workflows/Build-Validation.yml +++ b/.github/workflows/Build-Validation.yml @@ -2,6 +2,9 @@ name: Build Validation on: workflow_dispatch: + push: + branches: + - main pull_request: types: [opened, synchronize, reopened] @@ -12,13 +15,16 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 - - name: use NodeJS v22.16.0 + - name: use NodeJS v24.14.0 uses: actions/setup-node@v4 with: - node-version: '22.16.0' + node-version: '24.14.0' - name: Install Dependencies - run: npm install + run: npm ci - - name: run Rollout Build + - name: Run Checks + run: npm run check + + - name: Run Build run: npm run build diff --git a/.prettierrc b/.prettierrc index ab61c3b..6b8e1cc 100644 --- a/.prettierrc +++ b/.prettierrc @@ -9,5 +9,5 @@ "bracketSpacing": true, "bracketSameLine": false, "quoteProps": "as-needed", - "endOfLine": "crlf" + "endOfLine": "auto" } diff --git a/CODEOWNERS b/CODEOWNERS deleted file mode 100644 index 081743a..0000000 --- a/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -FrauJulian diff --git a/README.md b/README.md index e86b595..e348bd1 100644 --- a/README.md +++ b/README.md @@ -1,127 +1,315 @@ # Discord Audio Stream [![npm](https://img.shields.io/npm/dw/discord-audio-stream)](http://npmjs.org/package/discord-audio-stream) -![GitHub package.json version](https://img.shields.io/github/package-json/v/FrauJulian/discord-audio-stream) +![latest release](https://img.shields.io/badge/dynamic/json?label=release&query=$.name&url=https%3A%2F%2Fapi.github.com%2Frepos%2FFrauJulian%2Fdiscord-audio-stream%2Freleases%2Flatest&color=blue) ![GitHub Repo stars](https://img.shields.io/github/stars/FrauJulian/discord-audio-stream?style=social) -**This module is designed to work with [discord.js/voice](https://www.npmjs.com/package/@discordjs/voice) v0.19. This -package doesn't support older -versions!** +> **Designed for 24/7 Discord audio playback.** +> `discord-audio-stream` is a small TypeScript library for managed Discord voice playback through +> `@discordjs/voice` and ffmpeg. -> **Designed for 24/7 audio playing on discord.** -> Discord has many unwanted rate limits, especially in the audio area. This package does all the work and ensures that -> your music never stops playing due to ffmpeg or Discord, with as little effort as possible. +Discord voice streams need careful lifecycle handling. This package manages the voice connection, ffmpeg process, +raw PCM resource creation, optional reconnect renewal, and predictable cleanup so your bot code stays small and readable. -> **Recommended best Practise:** -> Create a global Map (Map) list. When the feature is used on a guild, add a new -> instance of AudioManager to the Map. When starting the connection or audio, overwrite the respective configuration with -> the override methods provided. To finally start, use the respective method. For each call and each change, retrieve the -> AudioManager object from the list and perform your actions. When the feature get stopped, first execute StopConnection -> and then Dispose to save as much power as possible. Garbage collection does the rest. -> **For large features:** For large systems, it is recommended to queue each start with the -> packet [p-queue](https://www.npmjs.com/package/p-queue), to give ffmpeg enough time.. +> **Recommended best practice:** +> Keep one `AudioManager` per guild, usually in a `Map`. Update the connection or source through +> `setConnection()` and `setSource()`, then call `start()`. When playback stops, call `stop()`. When the manager will not +> be reused, call `dispose()` to release timers, streams, ffmpeg, and the voice connection. + +> **For large bots:** +> Queue many simultaneous starts, for example with [`p-queue`](https://www.npmjs.com/package/p-queue), so the host does +> not spawn too many ffmpeg processes in the same tick. ## 👋 Support -Please create an [issue](https://github.com/FrauJulian/DiscordAudioStreamNPM/issues) on github or write [ -`fraujulian`](https://discord.com/users/860206216893693973) on discord! +Please create an [issue](https://github.com/FrauJulian/Discord-Audio-Stream/issues) on GitHub or contact +[`fraujulian`](https://discord.com/users/860206216893693973) on Discord. ## 📝 Usage ### Installation -**Node.js 22.12.0 or newer is required.** +**Node.js `22.12.0` or newer is required.** + +Install the library and the required voice packages: ```bash -npm install discord-audio-stream -yarn add discord-audio-stream -pnpm add discord-audio-stream -bun add discord-audio-stream +npm install discord-audio-stream @discordjs/voice prism-media @snazzah/davey opusscript ``` -#### Required Dependencies +`libsodium-wrappers` is optional. Install it only when your runtime does not support `aes-256-gcm`: -- > You only need to install [`libsodium-wrappers`](https://www.npmjs.com/package/libsodium-wrappers) if your system does not support `aes-256-gcm` (verify by running `require('node:crypto').getCiphers().includes('aes-256-gcm')`). -- [`@snazzah/davey`](https://www.npmjs.com/package/@snazzah/davey) -- [`opusscript`](https://www.npmjs.com/package/opusscript) -- [`prism-media`](https://www.npmjs.com/package/prism-media) -- [@discordjs/voice](https://www.npmjs.com/package/@discordjs/voice) -- **FFmpeg** (one of those) - - [`FFmpeg`](https://ffmpeg.org/) (environment) - *recommended* - - [`FFmpeg-static`](https://www.npmjs.com/package/ffmpeg-static) (library) - -### AudioManager Instance - -#### Constructor - -``` -AudioManager(ffmpegMode: string, renewInMs, number, connectionData: VoiceConnectionDataModel, audioData: VoiceAudioDataModel): IDisposable, IAudioManager +```bash +node -e "console.log(require('node:crypto').getCiphers().includes('aes-256-gcm'))" +npm install libsodium-wrappers ``` -- `ffmpegMode`: 'Native' or 'Standalone' - - Native: [`FFmpeg`](https://ffmpeg.org/) (environment) - - Standalone: [`FFmpeg-static`](https://www.npmjs.com/package/ffmpeg-static) (library) -- `renewInMs`: renewal time, default 1,5h = 5400000ms - optional -- `connectionData`: options for voice connection - optional -- `audioData`: options for audio player - optional +For bundled ffmpeg support, install `ffmpeg-static` and use `ffmpeg.mode: 'static'`: -#### Example +```bash +npm install ffmpeg-static +``` -```js -let audioManager = new AudioManager( - 'Native', - 5400000, - { - VoiceChannelId: 0, //voice channel id where to play music - GuildId: 0, //guild id - VoiceAdapter: 0, //guild VoiceAdapter +If ffmpeg is already available on the host PATH, use `ffmpeg.mode: 'native'`. + +### AudioManager Example + +```ts +import { AudioManager } from 'discord-audio-stream'; + +const manager = new AudioManager({ + connection: { + guildId: guild.id, + channelId: voiceChannel.id, + adapterCreator: guild.voiceAdapterCreator, }, - { - ResourceType: '', //resource type like link or file - Resource: '', //auto play link or file name + source: { + type: 'url', + url: 'https://example.com/live-stream.mp3', }, -); + ffmpeg: { + mode: 'native', + }, + renewIntervalMs: 5_400_000, +}); + +await manager.start(); ``` -### Fields and Methods of AudioManager +For a file source: -#### Fields +```ts +manager.setSource({ + type: 'file', + path: 'audio/intro.mp3', +}); -| Name | Type | Security | Description | -| --------- | -------- | --------- | ------------------------------------- | -| `Active` | **bool** | protected | To check if the connection is active. | -| `Playing` | **bool** | protected | To check if it is playing audio. | +await manager.start(); +``` -#### Methods +Relative file paths are resolved from `process.cwd()`. URL sources are validated before playback starts. -| Name | Parameters | Return type | Description | -| ----------------------------- | ------------------------------------------------------- | ------------- | ---------------------------------------- | -| `OverrideVoiceConnectionData` | `connectionData`: **VoiceConnectionDataModel** | void | To override intern connectionData field. | -| `OverrideVoiceAudioDataModel` | `audioData`: **VoiceAudioDataModel** | void | To override intern audioData field. | -| `CreateAndPlay` | | Promise void | Join channel and start playing audio. | -| `CreateConnection` | | void | Let it connect to voice channel. | -| `PlayAudio` | | Promise void | To start playing audio. | -| `PauseAudio` | | void | Pause the audio, if it is playing | -| `ResumeAudio` | | void | Resume the audio, if it is paused. | -| `SetVolume` | `volume`: number (0 - 100 percent) | void | To set the audio volume. | -| `StopConnection` | | Promise void | Method to disconnect the voice channel. | -| `Dispose` | | void | Dispose all data in object. | +## ⚙️ API -## 📝 Types +### Constructor -- [**VoiceConnection** by discord.js/voice](https://github.com/discordjs/discord.js/blob/main/packages/voice/src/VoiceConnection.ts#L166) -- [**AudioPlayer** by discord.js/voice](https://github.com/discordjs/discord.js/blob/main/packages/voice/src/audio/AudioPlayer.ts#L155) -- [**AudioResource** by discord.js/voice](https://github.com/discordjs/discord.js/blob/main/packages/voice/src/audio/AudioResource.ts#L44) -- [**DiscordGatewayAdapterCreator** by discord.js/voice](https://github.com/discordjs/discord.js/blob/main/packages/voice/src/util/adapter.ts#L50) -- [**VoiceConnectionDataModel** by discord-audio-stream](https://github.com/FrauJulian/Discord-Audio-Stream/blob/master/src/types.d.ts#L3) -- [**VoiceAudioDataModel** by discord-audio-stream](https://github.com/FrauJulian/Discord-Audio-Stream/blob/master/src/types.d.ts#L21) -- [**IDisposable** by discord-audio-stream](https://github.com/FrauJulian/Discord-Audio-Stream/blob/master/src/types.d.ts#L38) -- [**IAudioManager** by discord-audio-stream](https://github.com/FrauJulian/Discord-Audio-Stream/blob/master/src/types.d.ts#L45) +```ts +type AudioManagerOptions = { + ffmpeg?: { + mode?: 'native' | 'static'; + executablePath?: string; + inputArgs?: readonly string[]; + outputArgs?: readonly string[]; + }; + connection?: { + guildId: string; + channelId: string; + adapterCreator: DiscordGatewayAdapterCreator; + }; + source?: { type: 'url'; url: string } | { type: 'file'; path: string }; + renewIntervalMs?: number | false; + connectTimeoutMs?: number; + volume?: { + enabled?: boolean; + initialPercent?: number; + }; +}; +``` -## 📋 Contributors: +### Defaults + +| Option | Default | +| ------------------ | ----------- | +| `ffmpeg.mode` | `'native'` | +| `connectTimeoutMs` | `20_000` | +| `renewIntervalMs` | `5_400_000` | +| `volume.enabled` | `false` | + +### Methods + +| Method | Description | +| ------------------------ | ---------------------------------------------------------------------------------- | +| `setConnection(options)` | Replaces the voice connection target. | +| `setSource(source)` | Replaces the audio source. | +| `connect()` | Joins the configured Discord voice channel. | +| `play(source?)` | Starts playback on an existing connection. | +| `start()` | Connects and starts playback. | +| `pause()` | Pauses active playback. | +| `resume()` | Resumes paused playback. | +| `stop()` | Stops playback, clears renewal, and destroys the voice connection. | +| `setVolume(percent)` | Sets volume from `0` to `100`; requires `volume.enabled: true`. | +| `dispose()` | Idempotently releases timers, ffmpeg, streams, player state, and voice connection. | + +### State + +```ts +manager.state; // 'idle' | 'connecting' | 'ready' | 'playing' | 'paused' | 'stopped' | 'disposed' +manager.isPlaying; +manager.isConnected; +``` + +## 🔊 Audio Options + +### Volume + +Inline volume has a runtime cost in `@discordjs/voice`, so it is disabled by default. + +```ts +const manager = new AudioManager({ + connection, + source, + volume: { + enabled: true, + initialPercent: 50, + }, +}); + +await manager.start(); +manager.setVolume(25); +``` + +Calling `setVolume()` without `volume.enabled: true` throws `AudioManagerStateError`. + +### ffmpeg Modes + +- `mode: 'native'` uses the `ffmpeg` executable from the host environment. +- `mode: 'static'` resolves the optional `ffmpeg-static` package. +- `executablePath` overrides both modes and is useful for Docker images or custom ffmpeg builds. + +Default output is raw Discord-compatible PCM: `s16le`, `48000 Hz`, `2 channels`. + +You can override ffmpeg arguments through `ffmpeg.inputArgs` and `ffmpeg.outputArgs`. When you override them, you are +responsible for keeping the output compatible with `StreamType.Raw`. + +### Renewal + +By default, the manager schedules a renewal after `5_400_000 ms` so long-running streams can reconnect periodically. +Set `renewIntervalMs: false` to disable this behavior: + +```ts +const manager = new AudioManager({ + connection, + source, + renewIntervalMs: false, +}); +``` + +`stop()` and `dispose()` always clear the renewal timer. + +## 🧯 Errors + +The package exports these error classes: + +- `AudioManagerError` +- `AudioManagerConfigError` +- `AudioManagerStateError` +- `FfmpegProcessError` + +Configuration problems, such as a missing source or invalid URL, throw `AudioManagerConfigError`. Invalid lifecycle +operations, such as calling `pause()` while nothing is playing, throw `AudioManagerStateError`. + +## 📋 Migration From `0.7` + +Version `1.0` intentionally breaks the old PascalCase API. + +| Old | New | +| -------------------------------------------------------- | ------------------------------------------------------------------- | +| `new AudioManager('Native', renewMs, connection, audio)` | `new AudioManager({ ffmpeg, renewIntervalMs, connection, source })` | +| `OverrideVoiceConnectionData(...)` | `setConnection(...)` | +| `OverrideVoiceAudioDataModel(...)` | `setSource(...)` | +| `CreateConnection()` | `connect()` | +| `PlayAudio()` | `play()` | +| `CreateAndPlay()` | `start()` | +| `PauseAudio()` | `pause()` | +| `ResumeAudio()` | `resume()` | +| `StopConnection()` | `stop()` | +| `SetVolume(...)` | `setVolume(...)` | +| `Dispose()` | `dispose()` | + +Old connection data: + +```ts +{ + VoiceChannelId: voiceChannel.id, + GuildId: guild.id, + VoiceAdapter: guild.voiceAdapterCreator, +} +``` + +New connection data: + +```ts +{ + channelId: voiceChannel.id, + guildId: guild.id, + adapterCreator: guild.voiceAdapterCreator, +} +``` + +Old resource data: + +```ts +{ + ResourceType: ('Link', Resource, 'https://example.com/audio.mp3'); +} +{ + ResourceType: ('File', Resource, 'audio/intro.mp3'); +} +``` + +New source data: + +```ts +{ + type: ('url', url, 'https://example.com/audio.mp3'); +} +{ + type: ('file', path, 'audio/intro.mp3'); +} +``` + +Old ffmpeg modes: + +```ts +'Native'; +'Standalone'; +``` + +New ffmpeg modes: + +```ts +{ + ffmpeg: { + mode: 'native'; + } +} +{ + ffmpeg: { + mode: 'static'; + } +} +``` + +## 🧑‍💻 Development + +```bash +npm ci +npm run check +npm run build +``` + +`npm run check` runs formatting, linting, type-aware linting, TypeScript type checking, and unit tests. + +Release version stamping is separate from normal builds: + +```bash +npm run release:version +``` + +## 📋 Contributors ~ [**FrauJulian - Julian Lechner**](https://fraujulian.xyz/) - CODEOWNER ## 🤝 Enjoy the package? -Give it a star ⭐ on [github](https://github.com/FrauJulian/discord-audio-stream)! +Give it a star ⭐ on [GitHub](https://github.com/FrauJulian/discord-audio-stream)! diff --git a/eslint.config.cjs b/eslint.config.cjs index 547a2e4..ebace3c 100644 --- a/eslint.config.cjs +++ b/eslint.config.cjs @@ -36,7 +36,7 @@ const config = [ 'warn', { vars: 'all', varsIgnorePattern: '^_', args: 'after-used', argsIgnorePattern: '^_' }, ], - '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-explicit-any': 'error', /* Imports */ 'import/first': 'error', @@ -46,7 +46,7 @@ const config = [ 'error', { devDependencies: [ - '**/*.config.{js,cjs,mjs,ts}', + '**/*.config*.{js,cjs,mjs,ts}', '**/scripts/**', '**/*.test.{ts,js}', '**/Testing.{ts,js}', diff --git a/jest.config.cjs b/jest.config.cjs new file mode 100644 index 0000000..c360ecf --- /dev/null +++ b/jest.config.cjs @@ -0,0 +1,18 @@ +/** @type {import('jest').Config} */ +module.exports = { + clearMocks: true, + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + }, + moduleFileExtensions: ['ts', 'js', 'json'], + testEnvironment: 'node', + testMatch: ['/tests/**/*.test.ts'], + transform: { + '^.+\\.ts$': [ + 'ts-jest', + { + tsconfig: '/tsconfig.test.json', + }, + ], + }, +}; diff --git a/package-lock.json b/package-lock.json index 8f54625..566db43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,12 @@ "name": "discord-audio-stream", "version": "0.7.DDDhhmm", "license": "LGPL-2.1-only", - "dependencies": { - "@typescript-eslint/eslint-plugin": "^8.50.0", - "@typescript-eslint/parser": "^8.50.0" - }, "devDependencies": { + "@jest/globals": "^30.2.0", "@types/ejs": "^3.1.5", "@types/node": "^25.0.2", + "@typescript-eslint/eslint-plugin": "^8.50.0", + "@typescript-eslint/parser": "^8.50.0", "del-cli": "^7.0.0", "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", @@ -24,6 +23,7 @@ "eslint-plugin-promise": "^7.2.1", "eslint-plugin-unused-imports": "^4.3.0", "husky": "^9.1.7", + "jest": "^30.2.0", "npm-check-updates": "^19.2.0", "prettier": "^3.7.4", "ts-jest": "^29.4.6", @@ -39,9 +39,18 @@ "peerDependencies": { "@discordjs/voice": "^0.19.0", "@snazzah/davey": "^0.1.8", + "ffmpeg-static": "^5.2.0", "libsodium-wrappers": "^0.7.15", "opusscript": "^0.0.8", "prism-media": "^1.3.5" + }, + "peerDependenciesMeta": { + "ffmpeg-static": { + "optional": true + }, + "libsodium-wrappers": { + "optional": true + } } }, "node_modules/@babel/code-frame": { @@ -75,7 +84,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1058,6 +1066,7 @@ "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -1076,6 +1085,7 @@ "version": "4.12.2", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -1085,6 +1095,7 @@ "version": "0.21.1", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.7", @@ -1096,9 +1107,10 @@ } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -1106,9 +1118,10 @@ } }, "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -1121,6 +1134,7 @@ "version": "0.4.2", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.17.0" @@ -1133,6 +1147,7 @@ "version": "0.17.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -1145,6 +1160,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", @@ -1165,9 +1181,10 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -1178,15 +1195,17 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -1199,6 +1218,7 @@ "version": "9.39.2", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1211,6 +1231,7 @@ "version": "2.1.7", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1220,6 +1241,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.17.0", @@ -1233,6 +1255,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -1242,6 +1265,7 @@ "version": "0.16.7", "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", @@ -1255,6 +1279,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.22" @@ -1268,6 +1293,7 @@ "version": "0.4.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -1860,6 +1886,7 @@ "integrity": "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", @@ -1929,9 +1956,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.5.tgz", - "integrity": "sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", "cpu": [ "arm" ], @@ -1943,9 +1970,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.5.tgz", - "integrity": "sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", "cpu": [ "arm64" ], @@ -1957,9 +1984,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.5.tgz", - "integrity": "sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", "cpu": [ "arm64" ], @@ -1971,9 +1998,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.5.tgz", - "integrity": "sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", "cpu": [ "x64" ], @@ -1985,9 +2012,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.5.tgz", - "integrity": "sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", "cpu": [ "arm64" ], @@ -1999,9 +2026,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.5.tgz", - "integrity": "sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", "cpu": [ "x64" ], @@ -2013,13 +2040,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.5.tgz", - "integrity": "sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", "cpu": [ "arm" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2027,13 +2057,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.5.tgz", - "integrity": "sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", "cpu": [ "arm" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2041,13 +2074,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.5.tgz", - "integrity": "sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2055,13 +2091,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.5.tgz", - "integrity": "sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2069,13 +2108,33 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.5.tgz", - "integrity": "sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", "cpu": [ "loong64" ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2083,13 +2142,33 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.5.tgz", - "integrity": "sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2097,13 +2176,16 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.5.tgz", - "integrity": "sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2111,13 +2193,16 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.5.tgz", - "integrity": "sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2125,13 +2210,16 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.5.tgz", - "integrity": "sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2139,13 +2227,16 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.5.tgz", - "integrity": "sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2153,9 +2244,26 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.5.tgz", - "integrity": "sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", "cpu": [ "x64" ], @@ -2163,13 +2271,13 @@ "license": "MIT", "optional": true, "os": [ - "linux" + "openbsd" ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.5.tgz", - "integrity": "sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", "cpu": [ "arm64" ], @@ -2181,9 +2289,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.5.tgz", - "integrity": "sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", "cpu": [ "arm64" ], @@ -2195,9 +2303,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.5.tgz", - "integrity": "sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", "cpu": [ "ia32" ], @@ -2209,9 +2317,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.5.tgz", - "integrity": "sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", "cpu": [ "x64" ], @@ -2223,9 +2331,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.5.tgz", - "integrity": "sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", "cpu": [ "x64" ], @@ -2324,6 +2432,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">= 10" } @@ -2340,6 +2449,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">= 10" } @@ -2356,6 +2466,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 10" } @@ -2372,6 +2483,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 10" } @@ -2388,6 +2500,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">= 10" } @@ -2404,6 +2517,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10" } @@ -2420,6 +2534,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10" } @@ -2436,6 +2551,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10" } @@ -2452,6 +2568,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10" } @@ -2468,6 +2585,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10" } @@ -2481,6 +2599,7 @@ ], "license": "MIT", "optional": true, + "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "^1.0.7" }, @@ -2500,6 +2619,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 10" } @@ -2516,6 +2636,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 10" } @@ -2532,6 +2653,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 10" } @@ -2602,6 +2724,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, "license": "MIT" }, "node_modules/@types/istanbul-lib-coverage": { @@ -2635,6 +2758,7 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, "license": "MIT" }, "node_modules/@types/json5": { @@ -2665,6 +2789,7 @@ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*" } @@ -2690,8 +2815,8 @@ "version": "8.50.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.1.tgz", "integrity": "sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.50.1", @@ -2719,6 +2844,7 @@ "version": "8.50.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.1.tgz", "integrity": "sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/scope-manager": "8.50.1", @@ -2743,6 +2869,7 @@ "version": "8.50.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.1.tgz", "integrity": "sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.50.1", @@ -2764,6 +2891,7 @@ "version": "8.50.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.1.tgz", "integrity": "sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.50.1", @@ -2781,6 +2909,7 @@ "version": "8.50.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.1.tgz", "integrity": "sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2797,6 +2926,7 @@ "version": "8.50.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.1.tgz", "integrity": "sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2810,6 +2940,7 @@ "version": "8.50.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.1.tgz", "integrity": "sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/project-service": "8.50.1", @@ -2837,6 +2968,7 @@ "version": "8.50.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.1.tgz", "integrity": "sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.50.1", @@ -2854,6 +2986,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2866,6 +2999,7 @@ "version": "8.50.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.1.tgz", "integrity": "sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.50.1", @@ -2887,6 +3021,7 @@ "version": "8.50.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.1.tgz", "integrity": "sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.50.1", @@ -2904,6 +3039,7 @@ "version": "8.50.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.1.tgz", "integrity": "sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2920,6 +3056,7 @@ "version": "8.50.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.1.tgz", "integrity": "sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.50.1", @@ -2944,6 +3081,7 @@ "version": "8.50.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.1.tgz", "integrity": "sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2957,6 +3095,7 @@ "version": "8.50.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.1.tgz", "integrity": "sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/project-service": "8.50.1", @@ -2984,6 +3123,7 @@ "version": "8.50.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.1.tgz", "integrity": "sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==", + "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", @@ -3007,6 +3147,7 @@ "version": "8.50.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.1.tgz", "integrity": "sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.50.1", @@ -3024,6 +3165,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3325,8 +3467,8 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3338,15 +3480,17 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -3392,6 +3536,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -3428,6 +3573,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, "license": "Python-2.0" }, "node_modules/array-buffer-byte-length": { @@ -3691,6 +3837,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/baseline-browser-mapping": { @@ -3704,9 +3851,10 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -3745,7 +3893,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -3870,6 +4017,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3910,6 +4058,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -4071,6 +4220,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -4083,6 +4233,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/commander": { @@ -4099,6 +4250,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, "license": "MIT" }, "node_modules/confbox": { @@ -4129,6 +4281,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -4197,6 +4350,7 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -4229,6 +4383,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, "license": "MIT" }, "node_modules/deepmerge": { @@ -4336,6 +4491,7 @@ "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.37.tgz", "integrity": "sha512-Cv47jzY1jkGkh5sv0bfHYqGgKOWO1peOrGMkDFM4UmaGMOTgOW8QSexhvixa9sVOiz8MnVOBryWYyw/CEVhj7w==", "license": "MIT", + "peer": true, "workspaces": [ "scripts/actions/documentation" ] @@ -4582,7 +4738,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -4632,6 +4787,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -4644,8 +4800,8 @@ "version": "9.39.2", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4870,7 +5026,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -4900,9 +5055,9 @@ } }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -4921,9 +5076,9 @@ } }, "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -5032,6 +5187,7 @@ "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -5048,6 +5204,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -5057,9 +5214,10 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -5070,6 +5228,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5082,15 +5241,17 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -5103,6 +5264,7 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.15.0", @@ -5120,6 +5282,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5146,6 +5309,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -5158,6 +5322,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -5170,6 +5335,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -5179,6 +5345,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -5247,6 +5414,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -5283,12 +5451,14 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, "license": "MIT" }, "node_modules/fastq": { @@ -5315,6 +5485,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" @@ -5340,6 +5511,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -5368,6 +5540,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", @@ -5378,9 +5551,10 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, "license": "ISC" }, "node_modules/for-each": { @@ -5627,6 +5801,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -5639,6 +5814,7 @@ "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -5713,9 +5889,9 @@ "license": "ISC" }, "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5751,6 +5927,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5864,6 +6041,7 @@ "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -5873,6 +6051,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -5909,6 +6088,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -6104,6 +6284,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6169,6 +6350,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -6425,6 +6607,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -6520,7 +6703,6 @@ "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "30.2.0", "@jest/types": "30.2.0", @@ -7062,9 +7244,9 @@ } }, "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -7179,6 +7361,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -7204,6 +7387,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, "license": "MIT" }, "node_modules/json-parse-even-better-errors": { @@ -7217,12 +7401,14 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, "license": "MIT" }, "node_modules/json5": { @@ -7242,6 +7428,7 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -7261,6 +7448,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", @@ -7270,22 +7458,6 @@ "node": ">= 0.8.0" } }, - "node_modules/libsodium": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.15.tgz", - "integrity": "sha512-sZwRknt/tUpE2AwzHq3jEyUU5uvIZHtSssktXq7owd++3CSgn8RGrv6UZJJBpP7+iBghBqe7Z06/2M31rI2NKw==", - "license": "ISC" - }, - "node_modules/libsodium-wrappers": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.15.tgz", - "integrity": "sha512-E4anqJQwcfiC6+Yrl01C1m8p99wEhLmJSs0VQqST66SbQXXBoaJY0pF4BNjRYa/sOQAxx6lXAaAFIlx+15tXJQ==", - "license": "ISC", - "peer": true, - "dependencies": { - "libsodium": "^0.7.15" - } - }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -7320,6 +7492,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -7342,6 +7515,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, "license": "MIT" }, "node_modules/lru-cache": { @@ -7462,12 +7636,13 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -7513,6 +7688,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "license": "MIT" }, "node_modules/mz": { @@ -7547,6 +7723,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, "license": "MIT" }, "node_modules/neo-async": { @@ -7745,6 +7922,7 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, "license": "MIT", "dependencies": { "deep-is": "^0.1.3", @@ -7787,6 +7965,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -7802,6 +7981,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -7847,6 +8027,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -7878,6 +8059,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7897,6 +8079,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7961,9 +8144,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -8121,6 +8304,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -8214,6 +8398,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -8380,6 +8565,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -8407,9 +8593,9 @@ } }, "node_modules/rollup": { - "version": "4.53.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.5.tgz", - "integrity": "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==", + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", "dev": true, "license": "MIT", "dependencies": { @@ -8423,28 +8609,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.53.5", - "@rollup/rollup-android-arm64": "4.53.5", - "@rollup/rollup-darwin-arm64": "4.53.5", - "@rollup/rollup-darwin-x64": "4.53.5", - "@rollup/rollup-freebsd-arm64": "4.53.5", - "@rollup/rollup-freebsd-x64": "4.53.5", - "@rollup/rollup-linux-arm-gnueabihf": "4.53.5", - "@rollup/rollup-linux-arm-musleabihf": "4.53.5", - "@rollup/rollup-linux-arm64-gnu": "4.53.5", - "@rollup/rollup-linux-arm64-musl": "4.53.5", - "@rollup/rollup-linux-loong64-gnu": "4.53.5", - "@rollup/rollup-linux-ppc64-gnu": "4.53.5", - "@rollup/rollup-linux-riscv64-gnu": "4.53.5", - "@rollup/rollup-linux-riscv64-musl": "4.53.5", - "@rollup/rollup-linux-s390x-gnu": "4.53.5", - "@rollup/rollup-linux-x64-gnu": "4.53.5", - "@rollup/rollup-linux-x64-musl": "4.53.5", - "@rollup/rollup-openharmony-arm64": "4.53.5", - "@rollup/rollup-win32-arm64-msvc": "4.53.5", - "@rollup/rollup-win32-ia32-msvc": "4.53.5", - "@rollup/rollup-win32-x64-gnu": "4.53.5", - "@rollup/rollup-win32-x64-msvc": "4.53.5", + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", "fsevents": "~2.3.2" } }, @@ -8531,6 +8720,7 @@ "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -8592,6 +8782,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -8604,6 +8795,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9010,6 +9202,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9045,6 +9238,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -9112,9 +9306,9 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -9145,9 +9339,9 @@ } }, "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -9191,6 +9385,7 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -9207,6 +9402,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -9221,11 +9417,11 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -9267,6 +9463,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18.12" @@ -9299,9 +9496,9 @@ } }, "node_modules/ts-declaration-location/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -9503,6 +9700,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" @@ -9616,8 +9814,8 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9692,7 +9890,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "napi-postinstall": "^0.3.0" }, @@ -9756,6 +9953,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -9790,6 +9988,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -9894,6 +10093,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10023,10 +10223,11 @@ } }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -10138,6 +10339,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" diff --git a/package.json b/package.json index 332f049..7f3997c 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "discord-audio-stream", - "version": "0.7.DDDhhmm", + "version": "1.0.DDDhhmm", "license": "LGPL-2.1-only", - "description": "Discord library to make stream any audio easier.", + "description": "A small Discord voice audio streaming library with managed ffmpeg playback.", "exports": { ".": { "require": { @@ -26,13 +26,16 @@ }, "scripts": { "update:dependencies": "npx npm-check-updates --upgrade", - "update:version": "node set-version.js", + "release:version": "node set-version.js", "lint": "eslint . --ext .ts,.tsx,.js,.cjs,.mjs", "lint:fix": "eslint . --ext .ts,.tsx,.js,.cjs,.mjs --fix", "lint:types": "eslint . --config eslint.config.typeaware.cjs --ext .ts,.tsx", "format": "prettier --write .", "format:check": "prettier --check .", - "build": "del-cli dist && npm run update:version && tsup", + "typecheck": "tsc --noEmit", + "test": "jest --runInBand", + "check": "npm run format:check && npm run lint && npm run lint:types && npm run typecheck && npm run test", + "build": "del-cli dist && tsup", "prepare": "husky" }, "repository": "https://github.com/FrauJulian/Discord-Audio-Stream", @@ -58,13 +61,12 @@ "prettier --write" ] }, - "dependencies": { - "@typescript-eslint/eslint-plugin": "^8.50.0", - "@typescript-eslint/parser": "^8.50.0" - }, "devDependencies": { + "@jest/globals": "^30.2.0", "@types/ejs": "^3.1.5", "@types/node": "^25.0.2", + "@typescript-eslint/eslint-plugin": "^8.50.0", + "@typescript-eslint/parser": "^8.50.0", "del-cli": "^7.0.0", "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", @@ -74,6 +76,7 @@ "eslint-plugin-promise": "^7.2.1", "eslint-plugin-unused-imports": "^4.3.0", "husky": "^9.1.7", + "jest": "^30.2.0", "npm-check-updates": "^19.2.0", "prettier": "^3.7.4", "ts-jest": "^29.4.6", @@ -83,10 +86,19 @@ "peerDependencies": { "@discordjs/voice": "^0.19.0", "@snazzah/davey": "^0.1.8", + "ffmpeg-static": "^5.2.0", "libsodium-wrappers": "^0.7.15", "opusscript": "^0.0.8", "prism-media": "^1.3.5" }, + "peerDependenciesMeta": { + "ffmpeg-static": { + "optional": true + }, + "libsodium-wrappers": { + "optional": true + } + }, "engines": { "node": ">=22.12.0" }, diff --git a/src/audio-manager.ts b/src/audio-manager.ts index 84c3a1f..9eb97f1 100644 --- a/src/audio-manager.ts +++ b/src/audio-manager.ts @@ -8,190 +8,250 @@ import { StreamType, VoiceConnectionStatus, } from '@discordjs/voice'; -import { join } from 'node:path'; -import type { IAudioManager, IDisposable, VoiceAudioDataModel, VoiceConnectionDataModel } from './types'; -import { spawn } from 'node:child_process'; -import type { Readable } from 'node:stream'; +import { isAbsolute, resolve } from 'node:path'; -export default class AudioManager implements IAudioManager, IDisposable { - private voiceConnection?: VoiceConnection | null; - private audioPlayer?: AudioPlayer | null; - private audioResource?: AudioResource | null; +import { AudioManagerConfigError, AudioManagerStateError } from './errors'; +import { startFfmpeg, type FfmpegProcessHandle } from './ffmpeg'; +import type { + AudioManagerOptions, + AudioSource, + PlaybackState, + ResolvedAudioSource, + VoiceConnectionOptions, +} from './types'; - protected Active?: boolean | null; - protected Playing?: boolean | null; +const DEFAULT_CONNECT_TIMEOUT_MS = 20_000; +const DEFAULT_RENEW_INTERVAL_MS = 5_400_000; - private timeout?: NodeJS.Timeout | null; - private ffmpegProcess?: ReturnType | null; - private pcmStream?: Readable | null; +export default class AudioManager { + private readonly audioPlayer: AudioPlayer; - constructor( - private readonly ffmpegMode: 'Native' | 'Standalone', - private renewMs: number | null = null, - private connectionData: VoiceConnectionDataModel | null = null, - private audioData: VoiceAudioDataModel | null = null, - ) { - this.renewMs = renewMs ? renewMs : 5400000; - } - - public OverrideVoiceConnectionData(connectionData: VoiceConnectionDataModel): void { - this.connectionData = connectionData; - } - - public OverrideVoiceAudioDataModel(audioData: VoiceAudioDataModel): void { - this.audioData = audioData; - } - - public CreateConnection(): void { - this.voiceConnection = joinVoiceChannel({ - channelId: this.connectionData!.VoiceChannelId, - guildId: this.connectionData!.GuildId, - adapterCreator: this.connectionData!.VoiceAdapter, - }); - - this.timeout = setTimeout(async (): Promise => { - await this.StopConnection(); - await this.CreateAndPlay(); - }, this.renewMs!); - } - - public async PlayAudio(): Promise { - if (!this.audioData || !this.voiceConnection) return; - - await entersState(this.voiceConnection!, VoiceConnectionStatus.Ready, 20_000); - - if (this.ffmpegProcess) { - this.ffmpegProcess.kill('SIGKILL'); - this.ffmpegProcess = null; - } - - let source: string; - - if (this.audioData!.ResourceType === 'File') { - source = join(__dirname, this.audioData!.Resource); - } else if (this.audioData!.ResourceType === 'Link') { - source = this.audioData!.Resource; - } else { - throw new TypeError('Invalid resource type.'); - } - - this.ffmpegProcess = spawn( - this.ffmpegMode === 'Native' ? 'ffmpeg' : require('ffmpeg-static'), - [ - '-loglevel', - 'error', - '-i', - source, - '-analyzeduration', - '0', - '-f', - 's16le', - '-ar', - '48000', - '-ac', - '2', - 'pipe:1', - ], - { - stdio: ['ignore', 'pipe', 'pipe'], - }, - ); - - this.pcmStream = this.ffmpegProcess!.stdout as Readable; - - this.audioResource = createAudioResource(this.pcmStream, { - inputType: StreamType.Raw, - inlineVolume: true, - }); + private connection: VoiceConnection | undefined; + private resource: AudioResource | undefined; + private ffmpeg: FfmpegProcessHandle | undefined; + private renewTimer: NodeJS.Timeout | undefined; + private playbackState: PlaybackState = 'idle'; + private connectionOptions: VoiceConnectionOptions | undefined; + private audioSource: AudioSource | undefined; + private readonly options: Required> & + Omit; + public constructor(options: AudioManagerOptions = {}) { + this.options = { + ...options, + connectTimeoutMs: options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS, + }; + this.connectionOptions = options.connection; + this.audioSource = options.source; this.audioPlayer = createAudioPlayer({ behaviors: { noSubscriber: NoSubscriberBehavior.Play, }, }); - - this.voiceConnection!.subscribe(this.audioPlayer); - - this.audioPlayer.play(this.audioResource); - this.Playing = true; } - public PauseAudio(): void { - if (this.Playing) { - this.audioPlayer!.pause(); - this.Playing = false; - } else { - throw new ReferenceError('Audio is not playing.'); + public get state(): PlaybackState { + return this.playbackState; + } + + public get isPlaying(): boolean { + return this.playbackState === 'playing'; + } + + public get isConnected(): boolean { + return Boolean(this.connection); + } + + public setConnection(options: VoiceConnectionOptions): void { + this.assertNotDisposed(); + this.connectionOptions = options; + } + + public setSource(source: AudioSource): void { + this.assertNotDisposed(); + this.audioSource = source; + } + + public async connect(): Promise { + this.assertNotDisposed(); + + if (!this.connectionOptions) { + throw new AudioManagerConfigError('Voice connection options are required before connecting.'); + } + + this.clearRenewTimer(); + this.playbackState = 'connecting'; + this.connection?.destroy(); + this.connection = joinVoiceChannel({ + guildId: this.connectionOptions.guildId, + channelId: this.connectionOptions.channelId, + adapterCreator: this.connectionOptions.adapterCreator, + }); + this.connection.subscribe(this.audioPlayer); + + await entersState(this.connection, VoiceConnectionStatus.Ready, this.options.connectTimeoutMs); + this.playbackState = 'ready'; + this.scheduleRenewal(); + } + + public async play(source?: AudioSource): Promise { + this.assertNotDisposed(); + + if (source) { + this.setSource(source); + } + + if (!this.connection) { + throw new AudioManagerStateError('A voice connection is required before audio can be played.'); + } + + const resolvedSource = this.resolveSource(); + this.stopCurrentPlayback(); + this.ffmpeg = startFfmpeg(resolvedSource.input, this.options.ffmpeg); + this.resource = createAudioResource(this.ffmpeg.process.stdout, { + inputType: StreamType.Raw, + inlineVolume: this.options.volume?.enabled === true, + }); + + if (this.options.volume?.enabled === true && this.options.volume.initialPercent !== undefined) { + this.setVolume(this.options.volume.initialPercent); + } + + this.audioPlayer.play(this.resource); + this.playbackState = 'playing'; + } + + public async start(): Promise { + await this.connect(); + await this.play(); + } + + public pause(): void { + this.assertNotDisposed(); + + if (this.playbackState !== 'playing') { + throw new AudioManagerStateError('Audio can only be paused while it is playing.'); + } + + this.audioPlayer.pause(); + this.playbackState = 'paused'; + } + + public resume(): void { + this.assertNotDisposed(); + + if (this.playbackState !== 'paused') { + throw new AudioManagerStateError('Audio can only be resumed while it is paused.'); + } + + this.audioPlayer.unpause(); + this.playbackState = 'playing'; + } + + public async stop(): Promise { + if (this.playbackState === 'disposed') { + return; + } + + this.clearRenewTimer(); + this.stopCurrentPlayback(); + this.audioPlayer.stop(true); + this.connection?.disconnect(); + this.connection?.destroy(); + this.connection = undefined; + this.playbackState = 'stopped'; + } + + public setVolume(volumeInPercent: number): void { + this.assertNotDisposed(); + + if (this.options.volume?.enabled !== true) { + throw new AudioManagerStateError('Volume control requires volume.enabled to be true.'); + } + + if (volumeInPercent < 0 || volumeInPercent > 100) { + throw new AudioManagerConfigError('Volume must be between 0 and 100 percent.'); + } + + if (!this.resource?.volume) { + throw new AudioManagerStateError('No audio resource with volume control is currently active.'); + } + + this.resource.volume.setVolume(volumeInPercent / 100); + } + + public dispose(): void { + if (this.playbackState === 'disposed') { + return; + } + + this.clearRenewTimer(); + this.stopCurrentPlayback(); + this.audioPlayer.stop(true); + this.connection?.destroy(); + this.connection = undefined; + this.connectionOptions = undefined; + this.audioSource = undefined; + this.playbackState = 'disposed'; + } + + private resolveSource(): ResolvedAudioSource { + if (!this.audioSource) { + throw new AudioManagerConfigError('Audio source is required before playback can start.'); + } + + if (this.audioSource.type === 'url') { + try { + return { + input: new URL(this.audioSource.url).toString(), + source: this.audioSource, + }; + } catch (error) { + throw new AudioManagerConfigError(`Invalid audio source URL. Cause: ${String(error)}`); + } + } + + return { + input: isAbsolute(this.audioSource.path) + ? this.audioSource.path + : resolve(process.cwd(), this.audioSource.path), + source: this.audioSource, + }; + } + + private scheduleRenewal(): void { + const renewIntervalMs = this.options.renewIntervalMs ?? DEFAULT_RENEW_INTERVAL_MS; + + if (renewIntervalMs === false) { + return; + } + + this.renewTimer = setTimeout(() => { + void this.start(); + }, renewIntervalMs); + + if (typeof this.renewTimer.unref === 'function') { + this.renewTimer.unref(); } } - public ResumeAudio(): void { - if (!this.Playing) { - this.audioPlayer!.unpause(); - this.Playing = true; - } else { - throw new ReferenceError('Audio is playing.'); + private clearRenewTimer(): void { + if (this.renewTimer) { + clearTimeout(this.renewTimer); + this.renewTimer = undefined; } } - public async CreateAndPlay(): Promise { - this.CreateConnection(); - await this.PlayAudio(); + private stopCurrentPlayback(): void { + this.resource?.playStream.destroy(); + this.resource = undefined; + this.ffmpeg?.stop(); + this.ffmpeg = undefined; } - public async StopConnection(): Promise { - this.voiceConnection?.disconnect(); - this.voiceConnection?.destroy(); - this.voiceConnection = null; - - this.Active = false; - this.Playing = false; - } - - public SetVolume(volumeInPercent: number): void { - if (volumeInPercent < 0 || volumeInPercent > 100) throw new Error('Volume must be between 0 and 100.'); - this.audioResource!.volume!.setVolume(volumeInPercent / 100); - } - - public Dispose(): void { - try { - if (this.timeout) { - clearTimeout(this.timeout); - } - - if (this.audioPlayer) { - this.audioPlayer.stop(true); - } - - if (this.audioPlayer) { - this.audioPlayer!.stop(); - - if (this.audioResource && this.audioResource.playStream) { - this.audioResource!.playStream.destroy(); - } - } - - if (this.voiceConnection) { - this.voiceConnection!.destroy(); - } - - if (this.ffmpegProcess) { - this.ffmpegProcess.kill('SIGKILL'); - this.ffmpegProcess = null; - } - - this.pcmStream?.destroy(); - this.pcmStream = null; - - this.audioPlayer = null; - this.audioResource = null; - this.voiceConnection = null; - this.timeout = null; - this.Active = null; - this.Playing = null; - this.connectionData = null; - this.audioData = null; - this.renewMs = null; - } catch {} + private assertNotDisposed(): void { + if (this.playbackState === 'disposed') { + throw new AudioManagerStateError('AudioManager has been disposed.'); + } } } diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..5bea37e --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,19 @@ +export class AudioManagerError extends Error { + public constructor(message: string) { + super(message); + this.name = new.target.name; + } +} + +export class AudioManagerConfigError extends AudioManagerError {} + +export class AudioManagerStateError extends AudioManagerError {} + +export class FfmpegProcessError extends AudioManagerError { + public constructor( + message: string, + public readonly cause?: unknown, + ) { + super(message); + } +} diff --git a/src/ffmpeg.ts b/src/ffmpeg.ts new file mode 100644 index 0000000..7e6e891 --- /dev/null +++ b/src/ffmpeg.ts @@ -0,0 +1,83 @@ +import { spawn } from 'node:child_process'; +import type { ChildProcessByStdio } from 'node:child_process'; +import { createRequire } from 'node:module'; +import type { Readable } from 'node:stream'; + +import { AudioManagerConfigError } from './errors'; +import type { FfmpegOptions } from './types'; + +const requireFromCurrentModule = createRequire(__filename); + +const DEFAULT_INPUT_ARGS = ['-hide_banner', '-loglevel', 'error', '-nostdin'] as const; +const DEFAULT_OUTPUT_ARGS = ['-vn', '-f', 's16le', '-ar', '48000', '-ac', '2', 'pipe:1'] as const; +const FORCE_KILL_TIMEOUT_MS = 2_000; + +export type FfmpegProcessHandle = { + process: ChildProcessByStdio; + stop(): void; +}; + +export function resolveFfmpegExecutable(options: FfmpegOptions = {}): string { + if (options.executablePath?.trim()) { + return options.executablePath; + } + + if ((options.mode ?? 'native') === 'native') { + return 'ffmpeg'; + } + + try { + const executable = requireFromCurrentModule('ffmpeg-static') as unknown; + + if (typeof executable === 'string' && executable.length > 0) { + return executable; + } + } catch (error) { + throw new AudioManagerConfigError( + `Unable to resolve ffmpeg-static. Install it or pass ffmpeg.executablePath. Cause: ${String(error)}`, + ); + } + + throw new AudioManagerConfigError('ffmpeg-static did not expose an executable path.'); +} + +export function startFfmpeg(input: string, options: FfmpegOptions = {}): FfmpegProcessHandle { + const executable = resolveFfmpegExecutable(options); + const args = [ + ...(options.inputArgs ?? DEFAULT_INPUT_ARGS), + '-i', + input, + ...(options.outputArgs ?? DEFAULT_OUTPUT_ARGS), + ]; + const childProcess = spawn(executable, args, { stdio: ['ignore', 'pipe', 'pipe'] }); + + childProcess.once('error', () => undefined); + childProcess.stderr.resume(); + + return { + process: childProcess, + stop: (): void => { + stopProcess(childProcess); + }, + }; +} + +function stopProcess(childProcess: ChildProcessByStdio): void { + childProcess.stdout.destroy(); + childProcess.stderr.destroy(); + childProcess.removeAllListeners(); + + if (childProcess.killed || childProcess.exitCode !== null || childProcess.signalCode !== null) { + return; + } + + childProcess.kill('SIGTERM'); + + const forceKillTimeout = setTimeout(() => { + if (!childProcess.killed && childProcess.exitCode === null && childProcess.signalCode === null) { + childProcess.kill('SIGKILL'); + } + }, FORCE_KILL_TIMEOUT_MS); + + forceKillTimeout.unref(); +} diff --git a/src/index.ts b/src/index.ts index 3e85461..e8d14aa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,11 @@ export { default as AudioManager } from './audio-manager'; -export type { VoiceAudioDataModel, VoiceConnectionDataModel } from './types'; +export { AudioManagerConfigError, AudioManagerError, AudioManagerStateError, FfmpegProcessError } from './errors'; +export type { + AudioManagerOptions, + AudioSource, + FfmpegMode, + FfmpegOptions, + PlaybackState, + VoiceConnectionOptions, + VolumeOptions, +} from './types'; diff --git a/src/types.d.ts b/src/types.d.ts deleted file mode 100644 index a1b84bf..0000000 --- a/src/types.d.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { DiscordGatewayAdapterCreator } from '@discordjs/voice'; - -export type VoiceConnectionDataModel = { - /** - * The ID of the voice channel to connect to. - */ - VoiceChannelId: string; - - /** - * The Id of the guild (server) to connect to. - */ - GuildId: string; - - /** - * The adapter creator for the voice connection. - * Can be archived by the guild instance via the voiceAdapterCreator property. - */ - VoiceAdapter: DiscordGatewayAdapterCreator; -}; - -export type VoiceAudioDataModel = { - /** - * The unique identifier for the audio resource. - */ - ResourceType: 'Link' | 'File'; - - /** - * The URL or file path of the audio resource. - * any is to be assumed to require(filepath) - */ - Resource: string | any; -}; - -/** - * An interface which is implemented in audio manager - * for a proper Dispose functionality. - */ -export type IDisposable = { - Dispose(): void; -}; - -/** - * An interface to handle instances later on. - */ -export type IAudioManager = { - OverrideVoiceConnectionData(connectionData: VoiceConnectionDataModel): void; - OverrideVoiceAudioDataModel(audioData: VoiceAudioDataModel): void; - CreateAndPlay(): Promise; - CreateConnection(): void; - PlayAudio(): Promise; - PauseAudio(): void; - ResumeAudio(): void; - StopConnection(): Promise; - SetVolume(volumeInPercent: number): void; -}; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..32fc04f --- /dev/null +++ b/src/types.ts @@ -0,0 +1,178 @@ +import type { DiscordGatewayAdapterCreator } from '@discordjs/voice'; + +/** + * Strategy used to resolve the ffmpeg executable. + * + * - `native` uses the `ffmpeg` binary available on the host PATH. + * - `static` resolves the optional `ffmpeg-static` package. + */ +export type FfmpegMode = 'native' | 'static'; + +/** + * Audio input consumed by ffmpeg. + * + * URL sources are validated through the built-in `URL` constructor before playback starts. + * File sources may be absolute or relative; relative paths are resolved from `process.cwd()`. + */ +export type AudioSource = + | { + /** + * Marks this source as a remote URL. + */ + type: 'url'; + + /** + * Fully qualified audio URL passed to ffmpeg. + */ + url: string; + } + | { + /** + * Marks this source as a local file path. + */ + type: 'file'; + + /** + * Absolute file path or path relative to `process.cwd()`. + */ + path: string; + }; + +/** + * Discord voice channel connection settings. + */ +export type VoiceConnectionOptions = { + /** + * Discord guild/server ID. + */ + guildId: string; + + /** + * Discord voice channel ID where audio should be played. + */ + channelId: string; + + /** + * Discord voice adapter creator, usually `guild.voiceAdapterCreator` from discord.js. + */ + adapterCreator: DiscordGatewayAdapterCreator; +}; + +/** + * Public playback lifecycle state exposed by `AudioManager.state`. + */ +export type PlaybackState = 'idle' | 'connecting' | 'ready' | 'playing' | 'paused' | 'stopped' | 'disposed'; + +/** + * ffmpeg executable and argument configuration. + */ +export type FfmpegOptions = { + /** + * ffmpeg resolution mode. + * + * @defaultValue `'native'` + */ + mode?: FfmpegMode; + + /** + * Explicit ffmpeg executable path. + * + * When provided, this value takes precedence over `mode`. + */ + executablePath?: string; + + /** + * Arguments placed before `-i `. + * + * Override only when you need full control over ffmpeg input behavior. + */ + inputArgs?: readonly string[]; + + /** + * Arguments placed after `-i `. + * + * If overridden, the output must remain compatible with `StreamType.Raw`. + */ + outputArgs?: readonly string[]; +}; + +/** + * Optional inline volume control settings. + */ +export type VolumeOptions = { + /** + * Enables Discord voice inline volume support. + * + * Disabled by default because inline volume has runtime overhead. + * + * @defaultValue `false` + */ + enabled?: boolean; + + /** + * Initial volume percentage applied when playback starts. + * + * Requires `enabled: true`. + */ + initialPercent?: number; +}; + +/** + * Constructor options for `AudioManager`. + */ +export type AudioManagerOptions = { + /** + * ffmpeg executable and argument configuration. + */ + ffmpeg?: FfmpegOptions; + + /** + * Initial Discord voice connection settings. + * + * May also be supplied later through `setConnection()`. + */ + connection?: VoiceConnectionOptions; + + /** + * Initial audio source. + * + * May also be supplied later through `setSource()` or `play(source)`. + */ + source?: AudioSource; + + /** + * Milliseconds after which the manager reconnects and restarts playback. + * + * Set to `false` to disable renewal. + * + * @defaultValue `5_400_000` + */ + renewIntervalMs?: number | false; + + /** + * Maximum milliseconds to wait for the Discord voice connection to become ready. + * + * @defaultValue `20_000` + */ + connectTimeoutMs?: number; + + /** + * Optional inline volume configuration. + */ + volume?: VolumeOptions; +}; + +/** + * Internal normalized audio source passed to ffmpeg. + */ +export type ResolvedAudioSource = { + /** + * Validated URL or absolute file path passed as ffmpeg input. + */ + input: string; + + /** + * Original source configuration. + */ + source: AudioSource; +}; diff --git a/tests/audio-manager.test.ts b/tests/audio-manager.test.ts new file mode 100644 index 0000000..d95036b --- /dev/null +++ b/tests/audio-manager.test.ts @@ -0,0 +1,216 @@ +import { jest, describe, beforeEach, afterEach, it, expect } from '@jest/globals'; +import { sep } from 'node:path'; +import { PassThrough } from 'node:stream'; + +import AudioManager from '../src/audio-manager'; +import { AudioManagerConfigError, AudioManagerStateError } from '@/errors'; +import { startFfmpeg } from '@/ffmpeg'; +import type { AudioSource, VoiceConnectionOptions } from '@/types'; + +const mockAudioPlayer = { + play: jest.fn(), + pause: jest.fn(), + unpause: jest.fn(), + stop: jest.fn(), +}; +const mockConnection = { + subscribe: jest.fn(), + disconnect: jest.fn(), + destroy: jest.fn(), +}; +const mockAudioResource = { + playStream: { + destroy: jest.fn(), + }, + volume: { + setVolume: jest.fn(), + }, +}; +const mockFfmpegHandle = { + process: { + stdout: new PassThrough(), + }, + stop: jest.fn(), +}; + +jest.mock('@discordjs/voice', () => ({ + NoSubscriberBehavior: { + Play: 'play', + }, + StreamType: { + Raw: 'raw', + }, + VoiceConnectionStatus: { + Ready: 'ready', + }, + createAudioPlayer: jest.fn(() => mockAudioPlayer), + createAudioResource: jest.fn(() => mockAudioResource), + entersState: jest.fn(() => Promise.resolve(mockConnection)), + joinVoiceChannel: jest.fn(() => mockConnection), +})); + +jest.mock('@/ffmpeg', () => ({ + startFfmpeg: jest.fn(() => mockFfmpegHandle), +})); + +const connectionOptions: VoiceConnectionOptions = { + guildId: 'guild-id', + channelId: 'channel-id', + adapterCreator: jest.fn() as unknown as VoiceConnectionOptions['adapterCreator'], +}; + +const source: AudioSource = { + type: 'url', + url: 'https://example.com/audio.mp3', +}; + +describe('AudioManager', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('requires connection options before connecting', async () => { + const manager = new AudioManager({ renewIntervalMs: false }); + + await expect(manager.connect()).rejects.toThrow(AudioManagerConfigError); + }); + + it('connects and starts playback with resolved URL source', async () => { + const manager = new AudioManager({ + connection: connectionOptions, + source, + renewIntervalMs: false, + }); + + await manager.start(); + + expect(manager.state).toBe('playing'); + expect(manager.isConnected).toBe(true); + expect(manager.isPlaying).toBe(true); + expect(mockConnection.subscribe).toHaveBeenCalledWith(mockAudioPlayer); + expect(startFfmpeg).toHaveBeenCalledWith('https://example.com/audio.mp3', undefined); + expect(mockAudioPlayer.play).toHaveBeenCalledWith(mockAudioResource); + }); + + it('resolves file sources against the current working directory', async () => { + const manager = new AudioManager({ + connection: connectionOptions, + source: { + type: 'file', + path: 'audio/song.mp3', + }, + renewIntervalMs: false, + }); + + await manager.start(); + + expect(startFfmpeg).toHaveBeenCalledWith(expect.stringContaining(`audio${sep}song.mp3`), undefined); + }); + + it('rejects invalid URL sources', async () => { + const manager = new AudioManager({ + connection: connectionOptions, + source: { + type: 'url', + url: 'not a url', + }, + renewIntervalMs: false, + }); + + await manager.connect(); + + await expect(manager.play()).rejects.toThrow(AudioManagerConfigError); + }); + + it('applies initial volume only when volume support is enabled', async () => { + const manager = new AudioManager({ + connection: connectionOptions, + source, + renewIntervalMs: false, + volume: { + enabled: true, + initialPercent: 35, + }, + }); + + await manager.start(); + + expect(mockAudioResource.volume.setVolume).toHaveBeenCalledWith(0.35); + }); + + it('rejects volume changes when inline volume is disabled', () => { + const manager = new AudioManager({ renewIntervalMs: false }); + + expect(() => manager.setVolume(50)).toThrow(AudioManagerStateError); + }); + + it('pauses and resumes only from valid playback states', async () => { + const manager = new AudioManager({ + connection: connectionOptions, + source, + renewIntervalMs: false, + }); + + expect(() => manager.pause()).toThrow(AudioManagerStateError); + + await manager.start(); + manager.pause(); + manager.resume(); + + expect(mockAudioPlayer.pause).toHaveBeenCalledTimes(1); + expect(mockAudioPlayer.unpause).toHaveBeenCalledTimes(1); + expect(manager.state).toBe('playing'); + }); + + it('stops playback and voice resources idempotently', async () => { + const manager = new AudioManager({ + connection: connectionOptions, + source, + renewIntervalMs: false, + }); + + await manager.start(); + await manager.stop(); + await manager.stop(); + + expect(mockFfmpegHandle.stop).toHaveBeenCalledTimes(1); + expect(mockAudioResource.playStream.destroy).toHaveBeenCalledTimes(1); + expect(mockConnection.destroy).toHaveBeenCalled(); + expect(manager.state).toBe('stopped'); + }); + + it('clears existing renewal timer before reconnecting', async () => { + jest.useFakeTimers(); + + const manager = new AudioManager({ + connection: connectionOptions, + source, + renewIntervalMs: 10_000, + }); + + await manager.connect(); + await manager.connect(); + + expect(jest.getTimerCount()).toBe(1); + manager.dispose(); + expect(jest.getTimerCount()).toBe(0); + }); + + it('prevents use after disposal', async () => { + const manager = new AudioManager({ + connection: connectionOptions, + source, + renewIntervalMs: false, + }); + + await manager.start(); + manager.dispose(); + + expect(() => manager.pause()).toThrow(AudioManagerStateError); + expect(manager.state).toBe('disposed'); + }); +}); diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 9db49fa..4dbb15f 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", - "include": ["src/**/*.ts"] + "include": ["src/**/*.ts", "tests/**/*.ts", "tsup.config.ts"] } diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 0000000..c9b7fe1 --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "isolatedModules": true, + "outDir": "./dist-test" + }, + "include": ["src/**/*.ts", "tests/**/*.ts", "tsup.config.ts"] +}