mirror of
https://github.com/Mintplex-Labs/anything-llm
synced 2026-04-25 17:15:37 +02:00
Adjust fix path to use ESM import (#4867)
* Adjust fix path to use ESM import * normalize fix-path imports and usage across the app * extract path fix logic to utils for server and collector * add helpers * repin strip-ansi in collector * fix log for localWhisper lint
This commit is contained in:
2
.github/workflows/dev-build.yaml
vendored
2
.github/workflows/dev-build.yaml
vendored
@@ -6,7 +6,7 @@ concurrency:
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['4391-dmr-support'] # put your current branch to create a build. Core team only.
|
||||
branches: ['fix-path-patch'] # put your current branch to create a build. Core team only.
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'cloud-deployments/*'
|
||||
|
||||
@@ -3,7 +3,8 @@ const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
// Mock fix-path as a noop to prevent SIGSEGV (segfault)
|
||||
jest.mock("fix-path", () => jest.fn());
|
||||
// Returns ESM-style default export for dynamic import()
|
||||
jest.mock("fix-path", () => ({ default: jest.fn() }));
|
||||
|
||||
const { FFMPEGWrapper } = require("../../../../utils/WhisperProviders/ffmpeg");
|
||||
|
||||
@@ -26,14 +27,14 @@ describeRunner("FFMPEGWrapper", () => {
|
||||
});
|
||||
|
||||
it("should find ffmpeg executable", async () => {
|
||||
const knownPath = ffmpeg.ffmpegPath;
|
||||
const knownPath = await ffmpeg.ffmpegPath();
|
||||
expect(knownPath).toBeDefined();
|
||||
expect(typeof knownPath).toBe("string");
|
||||
expect(knownPath.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should validate ffmpeg executable", async () => {
|
||||
const knownPath = ffmpeg.ffmpegPath;
|
||||
const knownPath = await ffmpeg.ffmpegPath();
|
||||
expect(ffmpeg.isValidFFMPEG(knownPath)).toBe(true);
|
||||
});
|
||||
|
||||
@@ -56,7 +57,7 @@ describeRunner("FFMPEGWrapper", () => {
|
||||
const buffer = await response.arrayBuffer();
|
||||
fs.writeFileSync(inputPath, Buffer.from(buffer));
|
||||
|
||||
const result = ffmpeg.convertAudioToWav(inputPath, outputPath);
|
||||
const result = await ffmpeg.convertAudioToWav(inputPath, outputPath);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(fs.existsSync(outputPath)).toBe(true);
|
||||
@@ -69,8 +70,8 @@ describeRunner("FFMPEGWrapper", () => {
|
||||
const nonExistentFile = path.resolve(testDir, "non-existent-file.wav");
|
||||
const outputPath = path.resolve(testDir, "test-output-fail.wav");
|
||||
|
||||
expect(() => {
|
||||
ffmpeg.convertAudioToWav(nonExistentFile, outputPath)
|
||||
}).toThrow(`Input file ${nonExistentFile} does not exist.`);
|
||||
expect(async () => {
|
||||
return await ffmpeg.convertAudioToWav(nonExistentFile, outputPath);
|
||||
}).rejects.toThrow(`Input file ${nonExistentFile} does not exist.`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"puppeteer": "~21.5.2",
|
||||
"sharp": "^0.33.5",
|
||||
"slugify": "^1.6.6",
|
||||
"strip-ansi": "^7.1.2",
|
||||
"tesseract.js": "^6.0.0",
|
||||
"url-pattern": "^1.0.3",
|
||||
"uuid": "^9.0.0",
|
||||
@@ -54,7 +55,6 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"string-width": "^4.2.3",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wrap-ansi": "^7.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { execSync, spawnSync } = require("child_process");
|
||||
|
||||
const { patchShellEnvironmentPath } = require("../../shell");
|
||||
/**
|
||||
* Custom FFMPEG wrapper class for audio file conversion.
|
||||
* Replaces deprecated fluent-ffmpeg package.
|
||||
@@ -27,20 +27,12 @@ class FFMPEGWrapper {
|
||||
* Locates ffmpeg binary.
|
||||
* Uses fix-path on non-Windows platforms to ensure we can find ffmpeg.
|
||||
*
|
||||
* @returns {string} Path to ffmpeg binary
|
||||
* @returns {Promise<string>} Path to ffmpeg binary
|
||||
* @throws {Error}
|
||||
*/
|
||||
get ffmpegPath() {
|
||||
async ffmpegPath() {
|
||||
if (this._ffmpegPath) return this._ffmpegPath;
|
||||
|
||||
if (process.platform !== "win32") {
|
||||
try {
|
||||
const fixPath = require("fix-path");
|
||||
fixPath();
|
||||
} catch (error) {
|
||||
this.log("Could not load fix-path, using system PATH");
|
||||
}
|
||||
}
|
||||
await patchShellEnvironmentPath();
|
||||
|
||||
try {
|
||||
const which = process.platform === "win32" ? "where" : "which";
|
||||
@@ -83,10 +75,10 @@ class FFMPEGWrapper {
|
||||
*
|
||||
* @param {string} inputPath - Input path for audio file (any format supported by ffmpeg)
|
||||
* @param {string} outputPath - Output path for converted file
|
||||
* @returns {boolean}
|
||||
* @returns {Promise<boolean>}
|
||||
* @throws {Error} If ffmpeg binary cannot be found or conversion fails
|
||||
*/
|
||||
convertAudioToWav(inputPath, outputPath) {
|
||||
async convertAudioToWav(inputPath, outputPath) {
|
||||
if (!fs.existsSync(inputPath))
|
||||
throw new Error(`Input file ${inputPath} does not exist.`);
|
||||
const outputDir = path.dirname(outputPath);
|
||||
@@ -95,7 +87,7 @@ class FFMPEGWrapper {
|
||||
this.log(`Converting ${path.basename(inputPath)} to WAV format...`);
|
||||
// Convert to 16k hz mono 32f
|
||||
const result = spawnSync(
|
||||
this.ffmpegPath,
|
||||
await this.ffmpegPath(),
|
||||
[
|
||||
"-i",
|
||||
inputPath,
|
||||
|
||||
@@ -71,7 +71,7 @@ class LocalWhisper {
|
||||
fs.mkdirSync(outFolder, { recursive: true });
|
||||
|
||||
const outputFile = path.resolve(outFolder, `${v4()}.wav`);
|
||||
const success = ffmpeg.convertAudioToWav(sourcePath, outputFile);
|
||||
const success = await ffmpeg.convertAudioToWav(sourcePath, outputFile);
|
||||
if (!success)
|
||||
throw new Error(
|
||||
"[Conversion Failed]: Could not convert file to .wav format!"
|
||||
@@ -136,7 +136,7 @@ class LocalWhisper {
|
||||
progress_callback: (data) => {
|
||||
if (!data.hasOwnProperty("progress")) return;
|
||||
console.log(
|
||||
`\x1b[34m[Embedding - Downloading Model Files]\x1b[0m ${
|
||||
`\x1b[34m[ONNXWhisper - Downloading Model Files]\x1b[0m ${
|
||||
data.file
|
||||
} ${~~data?.progress}%`
|
||||
);
|
||||
|
||||
25
collector/utils/shell.js
Normal file
25
collector/utils/shell.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Patch the shell environment path to ensure the PATH is properly set for the current platform.
|
||||
* On Docker, we are on Node v18 and cannot support fix-path v5.
|
||||
* So we need to use the ESM-style import() to import the fix-path module + add the strip-ansi call to patch the PATH, which is the only change between v4 and v5.
|
||||
* https://github.com/sindresorhus/fix-path/issues/6
|
||||
* @returns {Promise<{[key: string]: string}>} - Environment variables from shell
|
||||
*/
|
||||
async function patchShellEnvironmentPath() {
|
||||
try {
|
||||
if (process.platform === "win32") return process.env;
|
||||
const { default: fixPath } = await import("fix-path");
|
||||
const { default: stripAnsi } = await import("strip-ansi");
|
||||
fixPath();
|
||||
if (process.env.PATH) process.env.PATH = stripAnsi(process.env.PATH);
|
||||
console.log("Shell environment path patched successfully.");
|
||||
return process.env;
|
||||
} catch (error) {
|
||||
console.error("Failed to patch shell environment path:", error);
|
||||
return process.env;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
patchShellEnvironmentPath,
|
||||
};
|
||||
@@ -504,6 +504,11 @@ ansi-regex@^5.0.1:
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
||||
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
|
||||
|
||||
ansi-regex@^6.0.1:
|
||||
version "6.2.2"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1"
|
||||
integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==
|
||||
|
||||
ansi-styles@^4.0.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
|
||||
@@ -3422,13 +3427,20 @@ string_decoder@~1.1.1:
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1, strip-ansi@^7.0.1:
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^7.0.1, strip-ansi@^7.1.2:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba"
|
||||
integrity sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==
|
||||
dependencies:
|
||||
ansi-regex "^6.0.1"
|
||||
|
||||
strip-dirs@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5"
|
||||
|
||||
@@ -11,6 +11,7 @@ const {
|
||||
const {
|
||||
StreamableHTTPClientTransport,
|
||||
} = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
||||
const { patchShellEnvironmentPath } = require("../../helpers/shell");
|
||||
|
||||
/**
|
||||
* @typedef {'stdio' | 'http' | 'sse'} MCPServerTypes
|
||||
@@ -230,33 +231,6 @@ class MCPHypervisor {
|
||||
this.mcpLoadingResults = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Load shell environment for desktop applications.
|
||||
* MacOS and Linux don't inherit login shell environment. So this function
|
||||
* fixes the PATH and accessible commands when running AnythingLLM outside of Docker during development on Mac/Linux and in-container (Linux).
|
||||
* @returns {Promise<{[key: string]: string}>} - Environment variables from shell
|
||||
*/
|
||||
async #loadShellEnvironment() {
|
||||
try {
|
||||
if (process.platform === "win32") return process.env;
|
||||
const { default: fixPath } = await import("fix-path");
|
||||
const { default: stripAnsi } = await import("strip-ansi");
|
||||
fixPath();
|
||||
|
||||
// Due to node v20 requirement to have a minimum version of fix-path v5, we need to strip ANSI codes manually
|
||||
// which was the only patch between v4 and v5. Here we just apply manually.
|
||||
// https://github.com/sindresorhus/fix-path/issues/6
|
||||
if (process.env.PATH) process.env.PATH = stripAnsi(process.env.PATH);
|
||||
return process.env;
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
"Failed to load shell environment, using process.env:",
|
||||
error.message
|
||||
);
|
||||
return process.env;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the MCP server environment variables - ensures proper PATH and NODE_PATH
|
||||
* inheritance across all platforms and deployment scenarios.
|
||||
@@ -264,7 +238,7 @@ class MCPHypervisor {
|
||||
* @returns {Promise<{env: { [key: string]: string } | {}}}> - The environment variables
|
||||
*/
|
||||
async #buildMCPServerENV(server) {
|
||||
const shellEnv = await this.#loadShellEnvironment();
|
||||
const shellEnv = await patchShellEnvironmentPath();
|
||||
let baseEnv = {
|
||||
PATH:
|
||||
shellEnv.PATH ||
|
||||
|
||||
25
server/utils/helpers/shell.js
Normal file
25
server/utils/helpers/shell.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Patch the shell environment path to ensure the PATH is properly set for the current platform.
|
||||
* On Docker, we are on Node v18 and cannot support fix-path v5.
|
||||
* So we need to use the ESM-style import() to import the fix-path module + add the strip-ansi call to patch the PATH, which is the only change between v4 and v5.
|
||||
* https://github.com/sindresorhus/fix-path/issues/6
|
||||
* @returns {Promise<{[key: string]: string}>} - Environment variables from shell
|
||||
*/
|
||||
async function patchShellEnvironmentPath() {
|
||||
try {
|
||||
if (process.platform === "win32") return process.env;
|
||||
const { default: fixPath } = await import("fix-path");
|
||||
const { default: stripAnsi } = await import("strip-ansi");
|
||||
fixPath();
|
||||
if (process.env.PATH) process.env.PATH = stripAnsi(process.env.PATH);
|
||||
console.log("Shell environment path patched successfully.");
|
||||
return process.env;
|
||||
} catch (error) {
|
||||
console.error("Failed to patch shell environment path:", error);
|
||||
return process.env;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
patchShellEnvironmentPath,
|
||||
};
|
||||
Reference in New Issue
Block a user