mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-10 15:55:53 +08:00
Merge branch 'main' into chore/add-prism-plugin
This commit is contained in:
@@ -84,6 +84,9 @@ const __dirname = getModuleDir(import.meta.url);
|
||||
// Resolving the app root once keeps every repo-level lookup below aligned across both layouts.
|
||||
const APP_ROOT = findAppRoot(__dirname);
|
||||
const installMode = fs.existsSync(path.join(APP_ROOT, '.git')) ? 'git' : 'npm';
|
||||
const MAX_FILE_UPLOAD_SIZE_MB = 200;
|
||||
const MAX_FILE_UPLOAD_SIZE_BYTES = MAX_FILE_UPLOAD_SIZE_MB * 1024 * 1024;
|
||||
const MAX_FILE_UPLOAD_COUNT = 20;
|
||||
|
||||
console.log('SERVER_PORT from env:', process.env.SERVER_PORT);
|
||||
|
||||
@@ -897,27 +900,27 @@ const uploadFilesHandler = async (req, res) => {
|
||||
}
|
||||
}),
|
||||
limits: {
|
||||
fileSize: 50 * 1024 * 1024, // 50MB limit
|
||||
files: 20 // Max 20 files at once
|
||||
fileSize: MAX_FILE_UPLOAD_SIZE_BYTES,
|
||||
files: MAX_FILE_UPLOAD_COUNT
|
||||
}
|
||||
});
|
||||
|
||||
// Use multer middleware
|
||||
uploadMiddleware.array('files', 20)(req, res, async (err) => {
|
||||
uploadMiddleware.array('files', MAX_FILE_UPLOAD_COUNT)(req, res, async (err) => {
|
||||
if (err) {
|
||||
console.error('Multer error:', err);
|
||||
if (err.code === 'LIMIT_FILE_SIZE') {
|
||||
return res.status(400).json({ error: 'File too large. Maximum size is 50MB.' });
|
||||
return res.status(400).json({ error: `File too large. Maximum size is ${MAX_FILE_UPLOAD_SIZE_MB}MB.` });
|
||||
}
|
||||
if (err.code === 'LIMIT_FILE_COUNT') {
|
||||
return res.status(400).json({ error: 'Too many files. Maximum is 20 files.' });
|
||||
return res.status(400).json({ error: `Too many files. Maximum is ${MAX_FILE_UPLOAD_COUNT} files.` });
|
||||
}
|
||||
return res.status(500).json({ error: err.message });
|
||||
}
|
||||
|
||||
try {
|
||||
const { projectId } = req.params;
|
||||
const { targetPath, relativePaths } = req.body;
|
||||
const { targetPath, relativePaths, requestedFileCount: requestedFileCountRaw } = req.body;
|
||||
|
||||
// Parse relative paths if provided (for folder uploads)
|
||||
let filePaths = [];
|
||||
@@ -941,6 +944,11 @@ const uploadFilesHandler = async (req, res) => {
|
||||
return res.status(400).json({ error: 'No files provided' });
|
||||
}
|
||||
|
||||
const parsedRequestedFileCount = Number.parseInt(requestedFileCountRaw, 10);
|
||||
const requestedFileCount = Number.isFinite(parsedRequestedFileCount) && parsedRequestedFileCount > 0
|
||||
? parsedRequestedFileCount
|
||||
: req.files.length;
|
||||
|
||||
// Resolve the project directory through the DB using the new projectId.
|
||||
const projectRoot = await projectsDb.getProjectPathById(projectId);
|
||||
if (!projectRoot) {
|
||||
@@ -1019,8 +1027,10 @@ const uploadFilesHandler = async (req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
files: uploadedFiles,
|
||||
uploadedCount: uploadedFiles.length,
|
||||
requestedFileCount,
|
||||
targetPath: resolvedTargetDir,
|
||||
message: `Uploaded ${uploadedFiles.length} file(s) successfully`
|
||||
message: `Uploaded ${uploadedFiles.length} ${uploadedFiles.length === 1 ? 'file' : 'files'} successfully`
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error uploading files:', error);
|
||||
|
||||
@@ -7,6 +7,41 @@ const runningPlugins = new Map();
|
||||
// Map<pluginName, Promise<port>> — in-flight start operations
|
||||
const startingPlugins = new Map();
|
||||
|
||||
/**
|
||||
* Build the environment handed to a plugin server subprocess.
|
||||
*
|
||||
* Intentionally minimal: only non-secret essentials, never the host's full
|
||||
* environment. On Windows a handful of system variables are required for any
|
||||
* child to bootstrap (Node itself, and any Python or CLI a plugin shells out
|
||||
* to). Without APPDATA a `pip install --user` tool cannot locate its
|
||||
* site-packages and fails to import; SystemRoot, PATHEXT and TEMP are needed to
|
||||
* resolve system DLLs, executable extensions and a temp directory. None of
|
||||
* these carry secrets, so the ones that are set get passed straight through.
|
||||
*/
|
||||
function buildPluginEnv(name) {
|
||||
const env = {
|
||||
PATH: process.env.PATH,
|
||||
HOME: process.env.HOME,
|
||||
NODE_ENV: process.env.NODE_ENV || 'production',
|
||||
PLUGIN_NAME: name,
|
||||
};
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
const WINDOWS_ESSENTIALS = [
|
||||
'SystemRoot', 'windir', 'SystemDrive',
|
||||
'USERPROFILE', 'APPDATA', 'LOCALAPPDATA',
|
||||
'TEMP', 'TMP', 'PATHEXT',
|
||||
];
|
||||
for (const key of WINDOWS_ESSENTIALS) {
|
||||
if (process.env[key] !== undefined) {
|
||||
env[key] = process.env[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a plugin's server subprocess.
|
||||
* The plugin's server entry must print a JSON line with { ready: true, port: <number> }
|
||||
@@ -26,15 +61,9 @@ export function startPluginServer(name, pluginDir, serverEntry) {
|
||||
|
||||
const serverPath = path.join(pluginDir, serverEntry);
|
||||
|
||||
// Restricted env — only essentials, no host secrets
|
||||
const pluginProcess = spawn('node', [serverPath], {
|
||||
cwd: pluginDir,
|
||||
env: {
|
||||
PATH: process.env.PATH,
|
||||
HOME: process.env.HOME,
|
||||
NODE_ENV: process.env.NODE_ENV || 'production',
|
||||
PLUGIN_NAME: name,
|
||||
},
|
||||
env: buildPluginEnv(name),
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user