+
+
+
+ Text-to-speech Preference
+
+
+
+ Here you can specify what kind of text-to-speech providers you would
+ want to use in your AnythingLLM experience. By default, we use the
+ browser's built in support for these services, but you may want to
+ use others.
+
+
+
+ {hasChanges && (
+ handleSubmit()}
+ className="mt-3 mr-0 -mb-14 z-10"
+ >
+ {saving ? "Saving..." : "Save changes"}
+
+ )}
+
+
Provider
+
+ {searchMenuOpen && (
+
setSearchMenuOpen(false)}
+ />
+ )}
+ {searchMenuOpen ? (
+
+
+
+
+ setSearchQuery(e.target.value)}
+ ref={searchInputRef}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") e.preventDefault();
+ }}
+ />
+
+
+
+ {filteredProviders.map((provider) => (
+ updateProviderChoice(provider.value)}
+ />
+ ))}
+
+
+
+ ) : (
+
+ )}
+
+
setHasChanges(true)}
+ className="mt-4 flex flex-col gap-y-1"
+ >
+ {selectedProvider &&
+ PROVIDERS.find(
+ (provider) => provider.value === selectedProvider
+ )?.options(settings)}
+
+
+
+ );
+}
diff --git a/frontend/src/utils/paths.js b/frontend/src/utils/paths.js
index 4dc4d528..cc2b69ee 100644
--- a/frontend/src/utils/paths.js
+++ b/frontend/src/utils/paths.js
@@ -98,6 +98,9 @@ export default {
transcriptionPreference: () => {
return "/settings/transcription-preference";
},
+ audioPreference: () => {
+ return "/settings/audio-preference";
+ },
embedder: {
modelPreference: () => "/settings/embedding-preference",
chunkingPreference: () => "/settings/text-splitter-preference",
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index bd12e9fa..93bdc088 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -2841,6 +2841,11 @@ react-smooth@^4.0.0:
prop-types "^15.8.1"
react-transition-group "^4.4.5"
+react-speech-recognition@^3.10.0:
+ version "3.10.0"
+ resolved "https://registry.yarnpkg.com/react-speech-recognition/-/react-speech-recognition-3.10.0.tgz#7aa43bb28d78b92671864dabba3a70489ccad27b"
+ integrity sha512-EVSr4Ik8l9urwdPiK2r0+ADrLyDDrjB0qBRdUWO+w2MfwEBrj6NuRmy1GD3x7BU/V6/hab0pl8Lupen0zwlJyw==
+
react-tag-input-component@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/react-tag-input-component/-/react-tag-input-component-2.0.2.tgz#f62f013c6a535141dd1c6c3a88858223170150f1"
diff --git a/server/.env.example b/server/.env.example
index 290a0709..5e0233b7 100644
--- a/server/.env.example
+++ b/server/.env.example
@@ -168,6 +168,19 @@ WHISPER_PROVIDER="local"
# WHISPER_PROVIDER="openai"
# OPEN_AI_KEY=sk-xxxxxxxx
+###########################################
+######## TTS/STT Model Selection ##########
+###########################################
+TTS_PROVIDER="native"
+
+# TTS_PROVIDER="openai"
+# TTS_OPEN_AI_KEY=sk-example
+# TTS_OPEN_AI_VOICE_MODEL=nova
+
+# TTS_PROVIDER="elevenlabs"
+# TTS_ELEVEN_LABS_KEY=
+# TTS_ELEVEN_LABS_VOICE_MODEL=21m00Tcm4TlvDq8ikWAM # Rachel
+
# CLOUD DEPLOYMENT VARIRABLES ONLY
# AUTH_TOKEN="hunter2" # This is the password to your application if remote hosting.
# STORAGE_DIR= # absolute filesystem path with no trailing slash
diff --git a/server/endpoints/workspaces.js b/server/endpoints/workspaces.js
index c22c679a..81cbd615 100644
--- a/server/endpoints/workspaces.js
+++ b/server/endpoints/workspaces.js
@@ -1,6 +1,11 @@
const path = require("path");
const fs = require("fs");
-const { reqBody, multiUserMode, userFromSession } = require("../utils/http");
+const {
+ reqBody,
+ multiUserMode,
+ userFromSession,
+ safeJsonParse,
+} = require("../utils/http");
const { normalizePath } = require("../utils/files");
const { Workspace } = require("../models/workspace");
const { Document } = require("../models/documents");
@@ -25,6 +30,7 @@ const {
determineWorkspacePfpFilepath,
fetchPfp,
} = require("../utils/files/pfp");
+const { getTTSProvider } = require("../utils/TextToSpeech");
function workspaceEndpoints(app) {
if (!app) return;
@@ -506,6 +512,48 @@ function workspaceEndpoints(app) {
}
);
+ app.get(
+ "/workspace/:slug/tts/:chatId",
+ [validatedRequest, flexUserRoleValid([ROLES.all]), validWorkspaceSlug],
+ async function (request, response) {
+ try {
+ const { chatId } = request.params;
+ const workspace = response.locals.workspace;
+ const cacheKey = `${workspace.slug}:${chatId}`;
+ const wsChat = await WorkspaceChats.get({
+ id: Number(chatId),
+ workspaceId: workspace.id,
+ });
+
+ const cachedResponse = responseCache.get(cacheKey);
+ if (cachedResponse) {
+ response.writeHead(200, {
+ "Content-Type": cachedResponse.mime || "audio/mpeg",
+ });
+ response.end(cachedResponse.buffer);
+ return;
+ }
+
+ const text = safeJsonParse(wsChat.response, null)?.text;
+ if (!text) return response.sendStatus(204).end();
+
+ const TTSProvider = getTTSProvider();
+ const buffer = await TTSProvider.ttsBuffer(text);
+ if (buffer === null) return response.sendStatus(204).end();
+
+ responseCache.set(cacheKey, { buffer, mime: "audio/mpeg" });
+ response.writeHead(200, {
+ "Content-Type": "audio/mpeg",
+ });
+ response.end(buffer);
+ return;
+ } catch (error) {
+ console.error("Error processing the TTS request:", error);
+ response.status(500).json({ message: "TTS could not be completed" });
+ }
+ }
+ );
+
app.get(
"/workspace/:slug/pfp",
[validatedRequest, flexUserRoleValid([ROLES.all])],
diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js
index 904c448d..248ca8cd 100644
--- a/server/models/systemSettings.js
+++ b/server/models/systemSettings.js
@@ -131,6 +131,17 @@ const SystemSettings = {
// --------------------------------------------------------
WhisperProvider: process.env.WHISPER_PROVIDER || "local",
+ // --------------------------------------------------------
+ // TTS/STT Selection Settings & Configs
+ // - Currently the only 3rd party is OpenAI or the native browser-built in
+ // --------------------------------------------------------
+ TextToSpeechProvider: process.env.TTS_PROVIDER || "native",
+ TTSOpenAIKey: !!process.env.TTS_OPEN_AI_KEY,
+ TTSOpenAIVoiceModel: process.env.TTS_OPEN_AI_VOICE_MODEL,
+ // Eleven Labs TTS
+ TTSElevenLabsKey: !!process.env.TTS_ELEVEN_LABS_KEY,
+ TTSElevenLabsVoiceModel: process.env.TTS_ELEVEN_LABS_VOICE_MODEL,
+
// --------------------------------------------------------
// Agent Settings & Configs
// --------------------------------------------------------
diff --git a/server/package.json b/server/package.json
index edee71b0..73b947c4 100644
--- a/server/package.json
+++ b/server/package.json
@@ -44,6 +44,7 @@
"cohere-ai": "^7.9.5",
"cors": "^2.8.5",
"dotenv": "^16.0.3",
+ "elevenlabs": "^0.5.0",
"express": "^4.18.2",
"express-ws": "^5.0.2",
"extract-json-from-string": "^1.0.1",
diff --git a/server/utils/TextToSpeech/elevenLabs/index.js b/server/utils/TextToSpeech/elevenLabs/index.js
new file mode 100644
index 00000000..e3d25f3a
--- /dev/null
+++ b/server/utils/TextToSpeech/elevenLabs/index.js
@@ -0,0 +1,54 @@
+const { ElevenLabsClient, stream } = require("elevenlabs");
+
+class ElevenLabsTTS {
+ constructor() {
+ if (!process.env.TTS_ELEVEN_LABS_KEY)
+ throw new Error("No ElevenLabs API key was set.");
+ this.elevenLabs = new ElevenLabsClient({
+ apiKey: process.env.TTS_ELEVEN_LABS_KEY,
+ });
+
+ // Rachel as default voice
+ // https://api.elevenlabs.io/v1/voices
+ this.voiceId =
+ process.env.TTS_ELEVEN_LABS_VOICE_MODEL ?? "21m00Tcm4TlvDq8ikWAM";
+ this.modelId = "eleven_multilingual_v2";
+ }
+
+ static async voices(apiKey = null) {
+ try {
+ const client = new ElevenLabsClient({
+ apiKey: apiKey ?? process.env.TTS_ELEVEN_LABS_KEY ?? null,
+ });
+ return (await client.voices.getAll())?.voices ?? [];
+ } catch {}
+ return [];
+ }
+
+ #stream2buffer(stream) {
+ return new Promise((resolve, reject) => {
+ const _buf = [];
+ stream.on("data", (chunk) => _buf.push(chunk));
+ stream.on("end", () => resolve(Buffer.concat(_buf)));
+ stream.on("error", (err) => reject(err));
+ });
+ }
+
+ async ttsBuffer(textInput) {
+ try {
+ const audio = await this.elevenLabs.generate({
+ voice: this.voiceId,
+ text: textInput,
+ model_id: "eleven_multilingual_v2",
+ });
+ return Buffer.from(await this.#stream2buffer(audio));
+ } catch (e) {
+ console.error(e);
+ }
+ return null;
+ }
+}
+
+module.exports = {
+ ElevenLabsTTS,
+};
diff --git a/server/utils/TextToSpeech/index.js b/server/utils/TextToSpeech/index.js
new file mode 100644
index 00000000..155fc954
--- /dev/null
+++ b/server/utils/TextToSpeech/index.js
@@ -0,0 +1,15 @@
+function getTTSProvider() {
+ const provider = process.env.TTS_PROVIDER || "openai";
+ switch (provider) {
+ case "openai":
+ const { OpenAiTTS } = require("./openAi");
+ return new OpenAiTTS();
+ case "elevenlabs":
+ const { ElevenLabsTTS } = require("./elevenLabs");
+ return new ElevenLabsTTS();
+ default:
+ throw new Error("ENV: No TTS_PROVIDER value found in environment!");
+ }
+}
+
+module.exports = { getTTSProvider };
diff --git a/server/utils/TextToSpeech/openAi/index.js b/server/utils/TextToSpeech/openAi/index.js
new file mode 100644
index 00000000..3c5b4840
--- /dev/null
+++ b/server/utils/TextToSpeech/openAi/index.js
@@ -0,0 +1,29 @@
+class OpenAiTTS {
+ constructor() {
+ if (!process.env.TTS_OPEN_AI_KEY)
+ throw new Error("No OpenAI API key was set.");
+ const { OpenAI: OpenAIApi } = require("openai");
+ this.openai = new OpenAIApi({
+ apiKey: process.env.TTS_OPEN_AI_KEY,
+ });
+ this.voice = process.env.TTS_OPEN_AI_VOICE_MODEL ?? "alloy";
+ }
+
+ async ttsBuffer(textInput) {
+ try {
+ const result = await this.openai.audio.speech.create({
+ model: "tts-1",
+ voice: this.voice,
+ input: textInput,
+ });
+ return Buffer.from(await result.arrayBuffer());
+ } catch (e) {
+ console.error(e);
+ }
+ return null;
+ }
+}
+
+module.exports = {
+ OpenAiTTS,
+};
diff --git a/server/utils/helpers/customModels.js b/server/utils/helpers/customModels.js
index b7aae93b..caf5a77c 100644
--- a/server/utils/helpers/customModels.js
+++ b/server/utils/helpers/customModels.js
@@ -4,6 +4,7 @@ const {
} = require("../AiProviders/openRouter");
const { perplexityModels } = require("../AiProviders/perplexity");
const { togetherAiModels } = require("../AiProviders/togetherAi");
+const { ElevenLabsTTS } = require("../TextToSpeech/elevenLabs");
const SUPPORT_CUSTOM_MODELS = [
"openai",
"localai",
@@ -15,6 +16,7 @@ const SUPPORT_CUSTOM_MODELS = [
"openrouter",
"lmstudio",
"koboldcpp",
+ "elevenlabs-tts",
];
async function getCustomModels(provider = "", apiKey = null, basePath = null) {
@@ -42,6 +44,8 @@ async function getCustomModels(provider = "", apiKey = null, basePath = null) {
return await getLMStudioModels(basePath);
case "koboldcpp":
return await getKoboldCPPModels(basePath);
+ case "elevenlabs-tts":
+ return await getElevenLabsModels(apiKey);
default:
return { models: [], error: "Invalid provider for custom models" };
}
@@ -321,6 +325,32 @@ function nativeLLMModels() {
return { models: files, error: null };
}
+async function getElevenLabsModels(apiKey = null) {
+ const models = (await ElevenLabsTTS.voices(apiKey)).map((model) => {
+ return {
+ id: model.voice_id,
+ organization: model.category,
+ name: model.name,
+ };
+ });
+
+ if (models.length === 0) {
+ return {
+ models: [
+ {
+ id: "21m00Tcm4TlvDq8ikWAM",
+ organization: "premade",
+ name: "Rachel (default)",
+ },
+ ],
+ error: null,
+ };
+ }
+
+ if (models.length > 0 && !!apiKey) process.env.TTS_ELEVEN_LABS_KEY = apiKey;
+ return { models, error: null };
+}
+
module.exports = {
getCustomModels,
};
diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js
index 947fbc62..e2b1d2e1 100644
--- a/server/utils/helpers/updateENV.js
+++ b/server/utils/helpers/updateENV.js
@@ -366,6 +366,32 @@ const KEY_MAPPING = {
envKey: "AGENT_SERPER_DEV_KEY",
checks: [],
},
+
+ // TTS/STT Integration ENVS
+ TextToSpeechProvider: {
+ envKey: "TTS_PROVIDER",
+ checks: [supportedTTSProvider],
+ },
+
+ // TTS OpenAI
+ TTSOpenAIKey: {
+ envKey: "TTS_OPEN_AI_KEY",
+ checks: [validOpenAIKey],
+ },
+ TTSOpenAIVoiceModel: {
+ envKey: "TTS_OPEN_AI_VOICE_MODEL",
+ checks: [],
+ },
+
+ // TTS ElevenLabs
+ TTSElevenLabsKey: {
+ envKey: "TTS_ELEVEN_LABS_KEY",
+ checks: [isNotEmpty],
+ },
+ TTSElevenLabsVoiceModel: {
+ envKey: "TTS_ELEVEN_LABS_VOICE_MODEL",
+ checks: [],
+ },
};
function isNotEmpty(input = "") {
@@ -419,6 +445,11 @@ function validOllamaLLMBasePath(input = "") {
}
}
+function supportedTTSProvider(input = "") {
+ const validSelection = ["native", "openai", "elevenlabs"].includes(input);
+ return validSelection ? null : `${input} is not a valid TTS provider.`;
+}
+
function supportedLLM(input = "") {
const validSelection = [
"openai",
diff --git a/server/yarn.lock b/server/yarn.lock
index 5edd09a3..9e4f184d 100644
--- a/server/yarn.lock
+++ b/server/yarn.lock
@@ -1901,6 +1901,11 @@ combined-stream@^1.0.8:
dependencies:
delayed-stream "~1.0.0"
+command-exists@^1.2.9:
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69"
+ integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==
+
command-line-args@5.2.1, command-line-args@^5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.1.tgz#c44c32e437a57d7c51157696893c5909e9cec42e"
@@ -2255,6 +2260,18 @@ ee-first@1.1.1:
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
+elevenlabs@^0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/elevenlabs/-/elevenlabs-0.5.0.tgz#07eb1a943b0ab99b925875bd5c57833a3a024e58"
+ integrity sha512-jfex4ecuWIlyAUuMrMJAJNa5MLziqYQOCDw4ZYuoc9PCYLxtHwaYBWpZoDhnYMcceLI7rRRvmbLMcT9HlVMfHA==
+ dependencies:
+ command-exists "^1.2.9"
+ execa "^5.1.1"
+ form-data "4.0.0"
+ node-fetch "2.7.0"
+ qs "6.11.2"
+ url-join "4.0.1"
+
emoji-regex@^10.2.1:
version "10.3.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.3.0.tgz#76998b9268409eb3dae3de989254d456e70cfe23"
@@ -2605,6 +2622,21 @@ eventemitter3@^4.0.4:
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
+execa@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
+ integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
+ dependencies:
+ cross-spawn "^7.0.3"
+ get-stream "^6.0.0"
+ human-signals "^2.1.0"
+ is-stream "^2.0.0"
+ merge-stream "^2.0.0"
+ npm-run-path "^4.0.1"
+ onetime "^5.1.2"
+ signal-exit "^3.0.3"
+ strip-final-newline "^2.0.0"
+
expand-template@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
@@ -3024,6 +3056,11 @@ get-stream@^5.1.0:
dependencies:
pump "^3.0.0"
+get-stream@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
+ integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
+
get-symbol-description@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5"
@@ -3297,6 +3334,11 @@ https-proxy-agent@^7.0.0:
agent-base "^7.0.2"
debug "4"
+human-signals@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
+ integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
+
humanize-ms@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
@@ -4092,6 +4134,11 @@ merge-descriptors@1.0.1:
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
+merge-stream@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
+ integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
+
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
@@ -4455,6 +4502,13 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+npm-run-path@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
+ integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
+ dependencies:
+ path-key "^3.0.0"
+
npmlog@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0"
@@ -4593,7 +4647,7 @@ one-time@^1.0.0:
dependencies:
fn.name "1.x.x"
-onetime@^5.1.0:
+onetime@^5.1.0, onetime@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
@@ -4774,7 +4828,7 @@ path-is-absolute@^1.0.0:
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
-path-key@^3.1.0:
+path-key@^3.0.0, path-key@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
@@ -5322,7 +5376,7 @@ side-channel@^1.0.4, side-channel@^1.0.6:
get-intrinsic "^1.2.4"
object-inspect "^1.13.1"
-signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.7:
+signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7:
version "3.0.7"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
@@ -5559,6 +5613,11 @@ strip-ansi@^7.0.1, strip-ansi@^7.1.0:
dependencies:
ansi-regex "^6.0.1"
+strip-final-newline@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
+ integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
+
strip-json-comments@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"