nuwax-file-server 1.5.6 → 1.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{Command as ee}from"commander";import{createRequire as te}from"module";import f from"path";import U from"os";import c from"fs-extra";import{spawn as E}from"cross-spawn";import V from"tree-kill";import{fileURLToPath as q}from"url";import{execFileSync as R}from"child_process";import M from"http";import{createRequire as G}from"module";var I=f.dirname(q(import.meta.url)),H=G(import.meta.url),n={name:"nuwax-file-server",pidDir:f.join(U.tmpdir(),"nuwax-file-server"),pidFileName:"server.pid",lockFileName:"start.lock",defaultStopTimeout:3e4,defaultStartTimeout:3e4,staleLockTimeout:12e4,checkInterval:500};function $(){return f.join(n.pidDir,n.pidFileName)}function F(){return f.join(n.pidDir,n.lockFileName)}function J(){let e=[f.join(I,"..","server.js"),f.join(I,"server.js")];for(let t of e)if(c.existsSync(t))return t;return e[0]}function W(){let e=["../../package.json","../package.json"];for(let t of e)try{let o=H(t);if(o?.version)return o.version}catch{}return"unknown"}function x(){try{let e=$();if(!c.existsSync(e))return null;let t=c.readFileSync(e,"utf8"),o=JSON.parse(t);return!o||typeof o.pid!="number"?null:o}catch(e){return e.code!=="ENOENT"&&console.error(`Read PID file failed: ${e.message}`),null}}function B(e){try{let t=$();c.ensureDirSync(n.pidDir),c.writeFileSync(t,JSON.stringify(e,null,2)),console.debug(`PID file written: ${t}`)}catch(t){throw console.error(`Write PID file failed: ${t.message}`),t}}function v(){try{let e=$();c.existsSync(e)&&(c.removeSync(e),console.debug(`PID file deleted: ${e}`))}catch(e){console.error(`Delete PID file failed: ${e.message}`)}}function g(e){try{return process.kill(e,0),!0}catch(t){return t.code!=="ESRCH"}}function K(e){try{return k()?R("powershell",["-NoProfile","-Command",`(Get-CimInstance Win32_Process -Filter "ProcessId = ${e}").CommandLine`],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim():R("ps",["-p",String(e),"-o","command="],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return""}}function X(e){if(!g(e))return!1;let t=K(e);if(!t)return!1;let o=t.toLowerCase(),s=f.basename(f.join(I,"..","server.js")).toLowerCase(),i=n.name.toLowerCase();return o.includes(s)||o.includes(i)}function z(){c.ensureDirSync(n.pidDir);let e=F(),t=`${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2,10)}`,o=JSON.stringify({pid:process.pid,token:t,createdAt:new Date().toISOString()},null,2);try{let s=c.openSync(e,"wx");return c.writeSync(s,o),{fd:s,token:t}}catch(s){if(s.code!=="EEXIST")throw s;try{let i=c.readFileSync(e,"utf8"),r=JSON.parse(i),a=Number(r?.pid),u=r?.createdAt?new Date(r.createdAt).getTime():0,l=Date.now()-u,C=Number.isFinite(a)&&a>0&&g(a),p=!u||Number.isNaN(l)||l>n.staleLockTimeout;if(!C||p){c.removeSync(e);let S=c.openSync(e,"wx");return c.writeSync(S,o),console.warn("Detected stale start lock, auto cleaned"),{fd:S,token:t}}}catch{c.removeSync(e);let r=c.openSync(e,"wx");return c.writeSync(r,o),console.warn("Detected invalid start lock, auto cleaned"),{fd:r,token:t}}throw s}}function Q(e){let t=e?.fd,o=e?.token;try{t!=null&&c.closeSync(t)}catch{}try{let s=F();if(c.existsSync(s)){let i=!1;if(!o)i=!0;else try{let r=c.readFileSync(s,"utf8");i=JSON.parse(r)?.token===o}catch{i=!0}i&&c.removeSync(s)}}catch{}}function k(){return process.platform==="win32"}async function Y(e=n.defaultStopTimeout){let t=x();return t?X(t.pid)?(console.log(`Existing service process detected (PID: ${t.pid}), stopping before start...`),await h(t.pid,!1)&&await D(t.pid,e)?(v(),{success:!0,message:`Existing process ${t.pid} stopped gracefully`}):(console.warn(`Graceful stop timeout or failed for PID ${t.pid}, force stop...`),await h(t.pid,!0)?await D(t.pid,e)?(v(),{success:!0,message:`Existing process ${t.pid} stopped forcibly`}):{success:!1,message:`Existing process ${t.pid} did not exit after force stop`}:{success:!1,message:`Failed to stop existing process ${t.pid}`})):(console.log(`Found stale PID file (PID: ${t.pid}), clean it...`),v(),{success:!0,message:"Stale PID file cleaned"}):{success:!0,message:"No existing service process"}}async function h(e,t=!1){return new Promise(o=>{if(!g(e)){console.debug(`Process ${e} does not exist, already stopped`),o(!0);return}let s=t?"SIGKILL":"SIGTERM",i=k()?"taskkill":"tree-kill";if(console.debug(`Use ${i} to stop process ${e} (signal: ${s})`),k()){let r=t?["/F","/PID",String(e)]:["/PID",String(e)],a=E("taskkill",r,{stdio:["ignore","pipe","pipe"],windowsHide:!0}),u="";a.stdout.on("data",l=>{u+=l.toString()}),a.stderr.on("data",l=>{u+=l.toString()}),a.on("error",l=>{console.error(`Stop process failed: ${l.message}`),o(!1)}),a.on("close",l=>{l===0?(console.debug(`Process ${e} stopped`),o(!0)):(console.warn(`taskkill exit code: ${l}, output: ${u}`),t?o(!1):h(e,!0).then(o))})}else V(e,s,r=>{r?r.code==="ESRCH"?(console.debug(`Process ${e} does not exist`),o(!0)):(console.error(`Stop process failed: ${r.message}`),o(!1)):(console.debug(`Process ${e} stopped`),o(!0))})})}async function D(e,t=n.defaultStopTimeout){let o=Date.now();for(;g(e);){if(Date.now()-o>t)return console.warn(`Wait for process ${e} to stop timeout (${t}ms)`),!1;await new Promise(i=>setTimeout(i,n.checkInterval))}let s=Date.now()-o;return console.debug(`Process ${e} stopped after ${s}ms`),!0}async function Z(e,t=n.defaultStartTimeout){let o=Number(e);if(!Number.isFinite(o)||o<=0)return!1;let s=Date.now();for(;Date.now()-s<=t;){if(await new Promise(r=>{let a=M.get({host:"127.0.0.1",port:o,path:"/health",timeout:Math.min(2e3,n.checkInterval*4)},u=>{r(u.statusCode>=200&&u.statusCode<300),u.resume()});a.on("timeout",()=>{a.destroy(),r(!1)}),a.on("error",()=>r(!1))}))return!0;await new Promise(r=>setTimeout(r,n.checkInterval))}return!1}async function N(e={}){let{env:t,port:o,config:s}=e,i=null;console.log(`Start service ${n.name}...`);try{i=z()}catch(r){return r.code==="EEXIST"?{success:!1,pid:null,message:"Another start operation is in progress, please retry later"}:{success:!1,pid:null,message:`Acquire start lock failed: ${r.message}`}}try{let r=Number(e.timeout)||n.defaultStopTimeout,a=await Y(r);if(!a.success)return{success:!1,pid:null,message:`Service start blocked: ${a.message}`};let u={...process.env};t&&(u.NODE_ENV=t,console.log(`Environment: ${t}`)),o&&(u.PORT=o,console.log(`Port: ${o}`)),s&&(u.CONFIG_FILE=s,console.log(`Configuration file: ${s}`));let l=J(),p=E("node",[l,...[]],{env:u,stdio:["pipe","pipe","pipe"],detached:!0,cwd:process.cwd()});if(p.stdout.on("data",m=>{process.stdout.write(m)}),p.stderr.on("data",m=>{process.stderr.write(m)}),p.on("error",m=>{console.error(`Start service failed: ${m.message}`)}),await new Promise(m=>setTimeout(m,2e3)),!g(p.pid))return{success:!1,pid:null,message:"Service start failed"};let S={pid:p.pid,startedAt:new Date().toISOString(),env:t||process.env.NODE_ENV||"production",port:o||process.env.PORT||"60000",version:W(),platform:process.platform};B(S);let P=Number(e.startTimeout)||n.defaultStartTimeout;return await Z(S.port,P)?(p.unref(),console.log(`Service started (PID: ${p.pid})`),console.log(`Service address: http://localhost:${S.port}`),{success:!0,pid:p.pid,message:"Service started successfully"}):(console.error(`Service health check timeout (${P}ms), stop failed instance...`),await h(p.pid,!0),v(),{success:!1,pid:null,message:`Service health check timeout (${P}ms)`})}finally{Q(i)}}async function T(e={}){let{force:t=!1,timeout:o=n.defaultStopTimeout}=e;console.log(`Stop service ${n.name}...`);let s=x();if(!s)return{success:!1,message:"Service not found"};if(!g(s.pid))return console.log("Service process has stopped, clean PID file..."),v(),{success:!0,message:"Service has stopped (process has exited)"};if(!await h(s.pid,t))return{success:!1,message:"Stop service failed"};let r=await D(s.pid,o);return v(),r?{success:!0,message:"Service has stopped"}:{success:!1,message:"Service stop timeout"}}async function O(e={}){console.log(`Restart service ${n.name}...`);let t=await T(e);!t.success&&t.message!=="Service not found"&&console.warn(`Stop service failed: ${t.message}`),await new Promise(s=>setTimeout(s,2e3));let o=await N(e);return o.success?{success:!0,pid:o.pid,message:"Service has restarted"}:{success:!1,pid:null,message:`Restart failed: ${o.message}`}}function _(){let e=x();return e?g(e.pid)?{running:!0,pidInfo:e,message:"Service running"}:{running:!1,pidInfo:e,message:"Service process does not exist"}:{running:!1,pidInfo:null,message:"Service not running"}}function L(e){try{let t=new Date(e),o=new Date;if(isNaN(t.getTime()))return"unknown";let s=Math.floor((o-t)/1e3),i=Math.floor(s/3600),r=Math.floor(s%3600/60),a=s%60;return i>0?`${i} hours ${r} minutes ${a} seconds`:r>0?`${r} minutes ${a} seconds`:`${a} seconds`}catch{return"unknown"}}var Pe=te(import.meta.url),A="1.5.6",d=new ee,w={reset:"\x1B[0m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",cyan:"\x1B[36m"};function j(e){console.error(`${w.red}ERROR: ${e}${w.reset}`)}function b(e){console.log(`${w.green}${e}${w.reset}`)}function oe(e){console.log(`${w.blue}${e}${w.reset}`)}function y(e){return async(...t)=>{try{let o=await e(...t);o&&o.success===!1?(j(o.message||"Command failed"),process.exitCode=1):process.exitCode=0}catch(o){j(o?.message||"Command failed with unexpected error"),process.exitCode=1}}}async function se(e){let t=await N({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(`Service started (PID: ${t.pid})`),t}async function re(e){let t=await T({force:e.force,timeout:Number(e.timeout)});return t.success&&b(t.message||"Service stopped"),t}async function ne(e){let t=await O({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(t.message||"Service restarted"),t}function ie(){let e=_();return oe(`${n.name} service status:`),console.log(""),console.log(` Service name: ${n.name}`),console.log(` Running status: ${e.running?"Running":"Stopped"}`),console.log(` Message: ${e.message}`),console.log(` PID file: ${$()}`),e.pidInfo&&(console.log(` Process ID: ${e.pidInfo.pid}`),console.log(` Environment: ${e.pidInfo.env||"Unknown"}`),console.log(` Port: ${e.pidInfo.port||"Unknown"}`),console.log(` Version: ${e.pidInfo.version||A}`),console.log(` Platform: ${e.pidInfo.platform||process.platform}`),console.log(` Started at: ${e.pidInfo.startedAt||"Unknown"}`),console.log(` Uptime: ${L(e.pidInfo.startedAt)}`)),console.log(""),{success:e.running,message:e.message}}function ce(){d.name("nuwax-file-server").description("Cross-platform file service deployment tool, supporting start/stop/restart/status").version(A,"-v, --version","Display version number").helpOption("-h, --help","Display help information"),d.command("start").allowUnknownOption().description("Start service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(se)),d.command("stop").description("Stop service").option("--force","Force stop").option("--timeout <ms>","\u505C\u6B62\u670D\u52A1\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).action(y(re)),d.command("restart").allowUnknownOption().description("Restart service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(ne)),d.command("status").description("View service status").action(y(ie)),d.command("help").description("Display help information").action(()=>{d.outputHelp()}),d.parse(process.argv),process.argv.slice(2).length||(d.outputHelp(),process.exitCode=0)}ce();
2
+ import{Command as ee}from"commander";import{createRequire as te}from"module";import f from"path";import U from"os";import c from"fs-extra";import{spawn as E}from"cross-spawn";import V from"tree-kill";import{fileURLToPath as q}from"url";import{execFileSync as R}from"child_process";import M from"http";import{createRequire as G}from"module";var I=f.dirname(q(import.meta.url)),H=G(import.meta.url),n={name:"nuwax-file-server",pidDir:f.join(U.tmpdir(),"nuwax-file-server"),pidFileName:"server.pid",lockFileName:"start.lock",defaultStopTimeout:3e4,defaultStartTimeout:3e4,staleLockTimeout:12e4,checkInterval:500};function $(){return f.join(n.pidDir,n.pidFileName)}function F(){return f.join(n.pidDir,n.lockFileName)}function J(){let e=[f.join(I,"..","server.js"),f.join(I,"server.js")];for(let t of e)if(c.existsSync(t))return t;return e[0]}function W(){let e=["../../package.json","../package.json"];for(let t of e)try{let o=H(t);if(o?.version)return o.version}catch{}return"unknown"}function x(){try{let e=$();if(!c.existsSync(e))return null;let t=c.readFileSync(e,"utf8"),o=JSON.parse(t);return!o||typeof o.pid!="number"?null:o}catch(e){return e.code!=="ENOENT"&&console.error(`Read PID file failed: ${e.message}`),null}}function B(e){try{let t=$();c.ensureDirSync(n.pidDir),c.writeFileSync(t,JSON.stringify(e,null,2)),console.debug(`PID file written: ${t}`)}catch(t){throw console.error(`Write PID file failed: ${t.message}`),t}}function v(){try{let e=$();c.existsSync(e)&&(c.removeSync(e),console.debug(`PID file deleted: ${e}`))}catch(e){console.error(`Delete PID file failed: ${e.message}`)}}function g(e){try{return process.kill(e,0),!0}catch(t){return t.code!=="ESRCH"}}function K(e){try{return k()?R("powershell",["-NoProfile","-Command",`(Get-CimInstance Win32_Process -Filter "ProcessId = ${e}").CommandLine`],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim():R("ps",["-p",String(e),"-o","command="],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return""}}function X(e){if(!g(e))return!1;let t=K(e);if(!t)return!1;let o=t.toLowerCase(),s=f.basename(f.join(I,"..","server.js")).toLowerCase(),i=n.name.toLowerCase();return o.includes(s)||o.includes(i)}function z(){c.ensureDirSync(n.pidDir);let e=F(),t=`${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2,10)}`,o=JSON.stringify({pid:process.pid,token:t,createdAt:new Date().toISOString()},null,2);try{let s=c.openSync(e,"wx");return c.writeSync(s,o),{fd:s,token:t}}catch(s){if(s.code!=="EEXIST")throw s;try{let i=c.readFileSync(e,"utf8"),r=JSON.parse(i),a=Number(r?.pid),u=r?.createdAt?new Date(r.createdAt).getTime():0,l=Date.now()-u,C=Number.isFinite(a)&&a>0&&g(a),p=!u||Number.isNaN(l)||l>n.staleLockTimeout;if(!C||p){c.removeSync(e);let S=c.openSync(e,"wx");return c.writeSync(S,o),console.warn("Detected stale start lock, auto cleaned"),{fd:S,token:t}}}catch{c.removeSync(e);let r=c.openSync(e,"wx");return c.writeSync(r,o),console.warn("Detected invalid start lock, auto cleaned"),{fd:r,token:t}}throw s}}function Q(e){let t=e?.fd,o=e?.token;try{t!=null&&c.closeSync(t)}catch{}try{let s=F();if(c.existsSync(s)){let i=!1;if(!o)i=!0;else try{let r=c.readFileSync(s,"utf8");i=JSON.parse(r)?.token===o}catch{i=!0}i&&c.removeSync(s)}}catch{}}function k(){return process.platform==="win32"}async function Y(e=n.defaultStopTimeout){let t=x();return t?X(t.pid)?(console.log(`Existing service process detected (PID: ${t.pid}), stopping before start...`),await h(t.pid,!1)&&await D(t.pid,e)?(v(),{success:!0,message:`Existing process ${t.pid} stopped gracefully`}):(console.warn(`Graceful stop timeout or failed for PID ${t.pid}, force stop...`),await h(t.pid,!0)?await D(t.pid,e)?(v(),{success:!0,message:`Existing process ${t.pid} stopped forcibly`}):{success:!1,message:`Existing process ${t.pid} did not exit after force stop`}:{success:!1,message:`Failed to stop existing process ${t.pid}`})):(console.log(`Found stale PID file (PID: ${t.pid}), clean it...`),v(),{success:!0,message:"Stale PID file cleaned"}):{success:!0,message:"No existing service process"}}async function h(e,t=!1){return new Promise(o=>{if(!g(e)){console.debug(`Process ${e} does not exist, already stopped`),o(!0);return}let s=t?"SIGKILL":"SIGTERM",i=k()?"taskkill":"tree-kill";if(console.debug(`Use ${i} to stop process ${e} (signal: ${s})`),k()){let r=t?["/F","/PID",String(e)]:["/PID",String(e)],a=E("taskkill",r,{stdio:["ignore","pipe","pipe"],windowsHide:!0}),u="";a.stdout.on("data",l=>{u+=l.toString()}),a.stderr.on("data",l=>{u+=l.toString()}),a.on("error",l=>{console.error(`Stop process failed: ${l.message}`),o(!1)}),a.on("close",l=>{l===0?(console.debug(`Process ${e} stopped`),o(!0)):(console.warn(`taskkill exit code: ${l}, output: ${u}`),t?o(!1):h(e,!0).then(o))})}else V(e,s,r=>{r?r.code==="ESRCH"?(console.debug(`Process ${e} does not exist`),o(!0)):(console.error(`Stop process failed: ${r.message}`),o(!1)):(console.debug(`Process ${e} stopped`),o(!0))})})}async function D(e,t=n.defaultStopTimeout){let o=Date.now();for(;g(e);){if(Date.now()-o>t)return console.warn(`Wait for process ${e} to stop timeout (${t}ms)`),!1;await new Promise(i=>setTimeout(i,n.checkInterval))}let s=Date.now()-o;return console.debug(`Process ${e} stopped after ${s}ms`),!0}async function Z(e,t=n.defaultStartTimeout){let o=Number(e);if(!Number.isFinite(o)||o<=0)return!1;let s=Date.now();for(;Date.now()-s<=t;){if(await new Promise(r=>{let a=M.get({host:"127.0.0.1",port:o,path:"/health",timeout:Math.min(2e3,n.checkInterval*4)},u=>{r(u.statusCode>=200&&u.statusCode<300),u.resume()});a.on("timeout",()=>{a.destroy(),r(!1)}),a.on("error",()=>r(!1))}))return!0;await new Promise(r=>setTimeout(r,n.checkInterval))}return!1}async function N(e={}){let{env:t,port:o,config:s}=e,i=null;console.log(`Start service ${n.name}...`);try{i=z()}catch(r){return r.code==="EEXIST"?{success:!1,pid:null,message:"Another start operation is in progress, please retry later"}:{success:!1,pid:null,message:`Acquire start lock failed: ${r.message}`}}try{let r=Number(e.timeout)||n.defaultStopTimeout,a=await Y(r);if(!a.success)return{success:!1,pid:null,message:`Service start blocked: ${a.message}`};let u={...process.env};t&&(u.NODE_ENV=t,console.log(`Environment: ${t}`)),o&&(u.PORT=o,console.log(`Port: ${o}`)),s&&(u.CONFIG_FILE=s,console.log(`Configuration file: ${s}`));let l=J(),p=E("node",[l,...[]],{env:u,stdio:["pipe","pipe","pipe"],detached:!0,cwd:process.cwd()});if(p.stdout.on("data",m=>{process.stdout.write(m)}),p.stderr.on("data",m=>{process.stderr.write(m)}),p.on("error",m=>{console.error(`Start service failed: ${m.message}`)}),await new Promise(m=>setTimeout(m,2e3)),!g(p.pid))return{success:!1,pid:null,message:"Service start failed"};let S={pid:p.pid,startedAt:new Date().toISOString(),env:t||process.env.NODE_ENV||"production",port:o||process.env.PORT||"60000",version:W(),platform:process.platform};B(S);let P=Number(e.startTimeout)||n.defaultStartTimeout;return await Z(S.port,P)?(p.unref(),console.log(`Service started (PID: ${p.pid})`),console.log(`Service address: http://localhost:${S.port}`),{success:!0,pid:p.pid,message:"Service started successfully"}):(console.error(`Service health check timeout (${P}ms), stop failed instance...`),await h(p.pid,!0),v(),{success:!1,pid:null,message:`Service health check timeout (${P}ms)`})}finally{Q(i)}}async function T(e={}){let{force:t=!1,timeout:o=n.defaultStopTimeout}=e;console.log(`Stop service ${n.name}...`);let s=x();if(!s)return{success:!1,message:"Service not found"};if(!g(s.pid))return console.log("Service process has stopped, clean PID file..."),v(),{success:!0,message:"Service has stopped (process has exited)"};if(!await h(s.pid,t))return{success:!1,message:"Stop service failed"};let r=await D(s.pid,o);return v(),r?{success:!0,message:"Service has stopped"}:{success:!1,message:"Service stop timeout"}}async function O(e={}){console.log(`Restart service ${n.name}...`);let t=await T(e);!t.success&&t.message!=="Service not found"&&console.warn(`Stop service failed: ${t.message}`),await new Promise(s=>setTimeout(s,2e3));let o=await N(e);return o.success?{success:!0,pid:o.pid,message:"Service has restarted"}:{success:!1,pid:null,message:`Restart failed: ${o.message}`}}function _(){let e=x();return e?g(e.pid)?{running:!0,pidInfo:e,message:"Service running"}:{running:!1,pidInfo:e,message:"Service process does not exist"}:{running:!1,pidInfo:null,message:"Service not running"}}function L(e){try{let t=new Date(e),o=new Date;if(isNaN(t.getTime()))return"unknown";let s=Math.floor((o-t)/1e3),i=Math.floor(s/3600),r=Math.floor(s%3600/60),a=s%60;return i>0?`${i} hours ${r} minutes ${a} seconds`:r>0?`${r} minutes ${a} seconds`:`${a} seconds`}catch{return"unknown"}}var Pe=te(import.meta.url),A="1.5.8",d=new ee,w={reset:"\x1B[0m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",cyan:"\x1B[36m"};function j(e){console.error(`${w.red}ERROR: ${e}${w.reset}`)}function b(e){console.log(`${w.green}${e}${w.reset}`)}function oe(e){console.log(`${w.blue}${e}${w.reset}`)}function y(e){return async(...t)=>{try{let o=await e(...t);o&&o.success===!1?(j(o.message||"Command failed"),process.exitCode=1):process.exitCode=0}catch(o){j(o?.message||"Command failed with unexpected error"),process.exitCode=1}}}async function se(e){let t=await N({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(`Service started (PID: ${t.pid})`),t}async function re(e){let t=await T({force:e.force,timeout:Number(e.timeout)});return t.success&&b(t.message||"Service stopped"),t}async function ne(e){let t=await O({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(t.message||"Service restarted"),t}function ie(){let e=_();return oe(`${n.name} service status:`),console.log(""),console.log(` Service name: ${n.name}`),console.log(` Running status: ${e.running?"Running":"Stopped"}`),console.log(` Message: ${e.message}`),console.log(` PID file: ${$()}`),e.pidInfo&&(console.log(` Process ID: ${e.pidInfo.pid}`),console.log(` Environment: ${e.pidInfo.env||"Unknown"}`),console.log(` Port: ${e.pidInfo.port||"Unknown"}`),console.log(` Version: ${e.pidInfo.version||A}`),console.log(` Platform: ${e.pidInfo.platform||process.platform}`),console.log(` Started at: ${e.pidInfo.startedAt||"Unknown"}`),console.log(` Uptime: ${L(e.pidInfo.startedAt)}`)),console.log(""),{success:e.running,message:e.message}}function ce(){d.name("nuwax-file-server").description("Cross-platform file service deployment tool, supporting start/stop/restart/status").version(A,"-v, --version","Display version number").helpOption("-h, --help","Display help information"),d.command("start").allowUnknownOption().description("Start service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(se)),d.command("stop").description("Stop service").option("--force","Force stop").option("--timeout <ms>","\u505C\u6B62\u670D\u52A1\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).action(y(re)),d.command("restart").allowUnknownOption().description("Restart service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(ne)),d.command("status").description("View service status").action(y(ie)),d.command("help").description("Display help information").action(()=>{d.outputHelp()}),d.parse(process.argv),process.argv.slice(2).length||(d.outputHelp(),process.exitCode=0)}ce();
@@ -1 +1 @@
1
- import v from"express";import z from"multer";import g from"fs";import j from"path";import{ValidationError as P,asyncHandler as c}from"../utils/error/errorHandler.js";import{log as u}from"../utils/log/logUtils.js";import b from"../appConfig/index.js";import{createWorkspace as D,pushSkillsToWorkspace as T,initProjectTemplate as R,installProjectDependencies as M,executeCommand as _,deleteWorkspace as B,zipWorkspace as H,buildAgentPackage as L,cleanupBuildArtifacts as W}from"../utils/computer/computerUtils.js";import{getFileList as J,getFileMeta as K,resolveExistingFile as V,searchFiles as G,updateFiles as Z,uploadFile as X,uploadFiles as Y,generateFile as Q,downloadAllFiles as q,getLatestLogs as ee,importProject as te}from"../utils/computer/computerFileUtils.js";import{resolveServiceContext as p}from"../utils/computer/workspaceContext.js";import{listFsRoots as se,listFsChildren as ae,createFsDirectory as oe,renameFsDirectory as re}from"../utils/computer/fsBrowserUtils.js";import{trackMulterTempPath as ne,cleanupMulterFiles as ie}from"../utils/common/multerCleanup.js";const A=v.Router();A.use(ie);const I=z({storage:z.diskStorage({destination:(e,r,s)=>{try{const t=typeof e.headers["x-workspace-type"]=="string"?e.headers["x-workspace-type"].trim().toLowerCase():"",a=typeof e.headers["x-app-id"]=="string"?e.headers["x-app-id"].trim():"";let o;if(t==="userapp"&&a){const n=b.USERAPP_WORKSPACE_DIR;if(!n)return s(new Error("USERAPP_WORKSPACE_DIR is not configured, cannot determine upload temporary directory"));o=j.join(n,String(a),".tmp")}else{const n=b.COMPUTER_WORKSPACE_DIR;if(!n)return s(new Error("COMPUTER_WORKSPACE_DIR is not configured, cannot determine upload temporary directory"));const i=e.body?.userId||"unknown",l=e.body?.cId||"unknown";o=j.join(n,String(i),String(l),".tmp")}g.existsSync(o)||g.mkdirSync(o,{recursive:!0}),e._multerUploadDir=o,s(null,o)}catch(t){s(t)}},filename:(e,r,s)=>{const t=j.extname(r.originalname)||".zip",a=j.basename(r.originalname,t),o=`${Date.now()}_${Math.round(Math.random()*1e6)}`,n=`${a}_${o}${t}`;ne(e,j.join(e._multerUploadDir||"",n)),s(null,n)}}),limits:{fileSize:b.UPLOAD_MAX_FILE_SIZE_BYTES}});function le(e,r,s,t){if(e&&e.name==="MulterError"||e&&(e.name==="ValidationError"||e instanceof P))return t(e);t(e)}const ce=[{path:"/create-workspace",method:"post",middleware:I.single("file"),handler:c(async(e,r)=>{const{userId:s,cId:t}=e.body||{},a=e.file||null,o=p(e),n=`computer:${s}:${t}`;u(n,"INFO","Create workspace request",{userId:s,cId:t,workspaceType:o.workspaceType,appId:o.appId,hasFile:!!a,fileName:a?.originalname,fileSize:a?.size});const i=await D(s,t,a,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,o);r.status(200).json({success:!0,...i})})},{path:"/create-workspace-v2",method:"post",middleware:I.single("file"),handler:c(async(e,r)=>{const{userId:s,cId:t,agentId:a,skillUrls:o,skillUrlMap:n,skillNames:i,updateSkillNames:l,mcpServersConfig:d,hooksConfig:f,permissionsConfig:m,hookScripts:h}=e.body||{},k=e.file||null,C=p(e),O=`computer:${s}:${t}`;let F=o;if(typeof o=="string")try{F=JSON.parse(o)}catch{F=[o]}let w=n;if(typeof n=="string")try{w=JSON.parse(n)}catch{w=null}let N=i;if(typeof i=="string")try{const y=JSON.parse(i);N=Array.isArray(y)?y:[i]}catch{N=i.split(",").map(y=>y.trim()).filter(Boolean)}let S=l;if(typeof l=="string")try{const y=JSON.parse(l);S=Array.isArray(y)?y:[l]}catch{S=l.split(",").map(y=>y.trim()).filter(Boolean)}let $=h;if(typeof h=="string")try{$=JSON.parse(h)}catch{$=null}const U=Array.isArray(F)?F.length:0,x=w&&typeof w=="object"&&!Array.isArray(w)?Object.keys(w).length:0;u(O,"INFO","Create workspace v2 request",{userId:s,cId:t,agentId:a,workspaceType:C.workspaceType,appId:C.appId,hasFile:!!k,fileName:k?.originalname,fileSize:k?.size,skillUrlsCount:U,skillUrlMapCount:x,skillNamesCount:Array.isArray(N)?N.length:0,updateSkillNamesCount:Array.isArray(S)?S.length:0,hasMcpServersConfig:!!d,hasHooksConfig:!!f,hasPermissionsConfig:!!m,hookScriptsCount:Array.isArray($)?$.length:0});const E=await D(s,t,k,F,d,m,f,$,a,N,S,w,C);r.status(200).json({success:!0,...E})})},{path:"/push-skills-to-workspace",method:"post",middleware:I.single("file"),handler:c(async(e,r)=>{const{userId:s,cId:t}=e.body||{},a=e.file||null,o=p(e),n=`computer:${s}:${t}`;u(n,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42",{userId:s,cId:t,workspaceType:o.workspaceType,appId:o.appId,hasFile:!!a,fileName:a?.originalname,fileSize:a?.size});const i=await T(s,t,a,void 0,void 0,o);r.status(200).json({success:!0,...i})})},{path:"/push-skills-to-workspace-v2",method:"post",middleware:I.single("file"),handler:c(async(e,r)=>{const{userId:s,cId:t,agentId:a,skillUrls:o}=e.body||{},n=e.file||null,i=p(e),l=`computer:${s}:${t}`;let d=o;if(typeof o=="string")try{const m=JSON.parse(o);d=Array.isArray(m)?m:[o]}catch{d=[o]}u(l,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42(v2)",{userId:s,cId:t,agentId:a,workspaceType:i.workspaceType,appId:i.appId,hasFile:!!n,fileName:n?.originalname,fileSize:n?.size,skillUrlsCount:Array.isArray(d)?d.length:0});const f=await T(s,t,n,d,a,i);r.status(200).json({success:!0,...f})})},{path:"/fs/roots",method:"get",handler:c(async(e,r)=>{const s=await se();r.status(200).json({success:!0,...s})})},{path:"/fs/children",method:"get",handler:c(async(e,r)=>{const{path:s}=e.query,t=await ae(s);r.status(200).json({success:!0,...t})})},{path:"/fs/mkdir",method:"post",handler:c(async(e,r)=>{const{parentPath:s,dirName:t}=e.body||{},a=await oe(s,t);r.status(200).json({success:!0,...a})})},{path:"/fs/rename",method:"post",handler:c(async(e,r)=>{const{path:s,newName:t}=e.body||{},a=await re(s,t);r.status(200).json({success:!0,...a})})},{path:"/get-file-list",method:"get",handler:c(async(e,r)=>{const{userId:s,cId:t,proxyPath:a,customTargetDir:o,relativePath:n,recursive:i,depth:l,type:d,limit:f}=e.query,m=await J(s,t,a,o,n,i,l,d,f,p(e));r.status(200).json({success:!0,...m})})},{path:"/resolve-file",method:"get",handler:c(async(e,r)=>{const{userId:s,cId:t,proxyPath:a,customTargetDir:o,filePath:n}=e.query,i=await V(s,t,n,a,o,p(e));r.status(200).json({success:!0,...i})})},{path:"/get-file-meta",method:"post",handler:c(async(e,r)=>{const{userId:s,cId:t,customTargetDir:a,filePaths:o,fileMetaMaxBatch:n}=e.body||{},i=await K(s,t,o,a,p(e),n);r.status(200).json({success:!0,...i})})},{path:"/search-files",method:"get",handler:c(async(e,r)=>{const{userId:s,cId:t,proxyPath:a,customTargetDir:o,relativePath:n,type:i,kw:l,limit:d,maxVisit:f,timeoutMs:m}=e.query,h=await G(s,t,a,l,o,n,i,d,f,m,p(e));r.status(200).json({success:!0,...h})})},{path:"/files-update",method:"post",handler:c(async(e,r)=>{const{userId:s,cId:t,files:a,customTargetDir:o}=e.body||{},n=`computer:${s}:${t}`;u(n,"INFO","Files update",{userId:s,cId:t,filesCount:a?a.length:0}),a&&Array.isArray(a)&&a.forEach(l=>{if(l&&typeof l.contents=="string"&&l.contents)try{l.contents=decodeURIComponent(l.contents)}catch(d){u(n,"WARN","Decode file content failed",{fileName:l.name,error:d.message})}});const i=await Z(s,t,a,o,p(e));r.status(200).json(i)})},{path:"/upload-file",method:"post",middleware:I.single("file"),handler:c(async(e,r)=>{const{userId:s,cId:t,filePath:a,customTargetDir:o}=e.body||{},n=e.file,i=`computer:${s}:${t}`;u(i,"INFO","Upload single file",{userId:s,cId:t,filePath:a});const d={buffer:await g.promises.readFile(n.path),originalname:n.originalname,mimetype:n.mimetype,size:n.size};try{const f=await X(s,t,d,a,o,p(e));r.status(200).json(f)}finally{g.existsSync(n.path)&&await g.promises.unlink(n.path)}})},{path:"/generate-file",method:"post",handler:c(async(e,r)=>{const{userId:s,cId:t,fileName:a,content:o,customTargetDir:n}=e.body||{},i=`computer:${s}:${t}`;u(i,"INFO","Generate file request",{userId:s,cId:t,fileName:a,contentLength:typeof o=="string"?o.length:0});const l=await Q(s,t,a,o,n,p(e));r.status(200).json(l)})},{path:"/import-project",method:"post",middleware:I.single("file"),handler:c(async(e,r)=>{const{userId:s,cId:t,customTargetDir:a}=e.body||{},o=e.file,n=`computer:${s}:${t}`;if(u(n,"INFO","Import project request",{userId:s,cId:t,fileName:o?.originalname,fileSize:o?.size}),!o)throw new P("file is required",{field:"file"});const i=await te(s,t,o,a,p(e));r.status(200).json(i)})},{path:"/upload-files",method:"post",middleware:I.array("files"),handler:c(async(e,r)=>{const{userId:s,cId:t,filePaths:a,customTargetDir:o}=e.body||{},n=e.files||[],i=`computer:${s}:${t}`,l=Array.isArray(a)?a:typeof a=="string"?[a]:a;u(i,"INFO","Batch upload files request",{userId:s,cId:t,filesCount:n.length,filePathsCount:Array.isArray(l)?l.length:0});const d=[],f=[];try{for(const h of n){f.push(h.path);const k=await g.promises.readFile(h.path);d.push({buffer:k,originalname:h.originalname,mimetype:h.mimetype,size:h.size})}const m=await Y(s,t,d,l,o,p(e));r.status(200).json(m)}finally{for(const m of f)if(g.existsSync(m))try{await g.promises.unlink(m)}catch(h){u(i,"WARN","Clean temporary file failed",{tempPath:m,error:h.message})}}})},{path:"/install-project",method:"post",handler:c(async(e,r)=>{const{userId:s,cId:t,programmingLanguage:a}=e.body||{},o=`computer:${s}:${t}`;u(o,"INFO","Install project request",{userId:s,cId:t,programmingLanguage:a});const n=await M(s,t,a,p(e));r.status(200).json({success:!0,...n})})},{path:"/init-project-template",method:"post",middleware:I.single("file"),handler:c(async(e,r)=>{const{userId:s,cId:t,enableGit:a}=e.body||{},o=e.file||null,n=`computer:${s}:${t}`;u(n,"INFO","Init project template request",{userId:s,cId:t,hasFile:!!o,fileName:o?.originalname,fileSize:o?.size,enableGit:a});const i=await R(s,t,o,a,p(e));r.status(200).json({success:!0,...i})})},{path:"/download-all-files",method:"get",handler:c(async(e,r)=>{const{userId:s,cId:t,customTargetDir:a}=e.query||{},o=`computer:${s}:${t}`;u(o,"INFO","Download all files request",{userId:s,cId:t});const{archive:n,zipFileName:i}=await q(s,t,a,p(e));r.setHeader("Content-Type","application/zip");const l=encodeURIComponent(i);r.setHeader("Content-Disposition",`attachment; filename="${l}"; filename*=UTF-8''${l}`),n.on("error",d=>{r.destroy(d)}),n.pipe(r),n.finalize()})},{path:"/execute-command",method:"post",handler:c(async(e,r)=>{const{userId:s,cId:t,command:a}=e.body||{},o=`computer:${s}:${t}`;u(o,"INFO","Execute command request",{userId:s,cId:t,command:a});const n=await _(s,t,a,p(e));r.status(200).json({success:!0,...n})})},{path:"/delete-workspace",method:"post",handler:c(async(e,r)=>{const{userId:s,cId:t}=e.body||{},a=`computer:${s}:${t}`;u(a,"INFO","Delete workspace request",{userId:s,cId:t});const o=await B(s,t,p(e));r.status(200).json({success:!0,...o})})},{path:"/get-logs",method:"get",handler:c(async(e,r)=>{const{userId:s,cId:t,tailLines:a}=e.query,o=a?parseInt(a,10):200,n=Number.isFinite(o)&&o>0?o:200,i=await ee(s,t,n,p(e));r.status(200).json(i)})},{path:"/zip-workspace",method:"post",handler:c(async(e,r)=>{const{userId:s,cId:t,excludeDirs:a}=e.body||{},o=`computer:${s}:${t}`;u(o,"INFO","Zip workspace request",{userId:s,cId:t,hasExcludeDirs:!!a});const{archive:n,zipFileName:i}=await H(s,t,a||null,p(e));r.setHeader("Content-Type","application/zip");const l=encodeURIComponent(i);r.setHeader("Content-Disposition",`attachment; filename="${l}"; filename*=UTF-8''${l}`),n.on("error",d=>{r.destroy(d)}),n.pipe(r),n.finalize()})},{path:"/build-agent-package",method:"post",handler:c(async(e,r)=>{const{userId:s,cId:t,agentId:a,version:o}=e.body||{},n=`computer:${s}:${t}`;u(n,"INFO","Build agent package request",{userId:s,cId:t,agentId:a,version:o});const i=await L(s,t,a,o,p(e));r.status(200).json({success:!0,...i})})},{path:"/cleanup-build-artifacts",method:"post",handler:c(async(e,r)=>{const{userId:s,cId:t}=e.body||{},a=`computer:${s}:${t}`;u(a,"INFO","Cleanup build artifacts request",{userId:s,cId:t});const o=await W(s,t,p(e));r.status(200).json({success:!0,...o})})}];ce.forEach(e=>{if(e.middleware)A[e.method](e.path,e.middleware,e.handler);else if(e.customHandler){const r=[];e.handler&&r.push(e.handler),e.decodeMiddleware&&r.push(e.decodeMiddleware),r.push(le),r.push(e.customHandler),A[e.method](e.path,...r)}else A[e.method](e.path,e.handler)});export default A;
1
+ import v from"express";import z from"multer";import g from"fs";import j from"path";import{ValidationError as P,asyncHandler as c}from"../utils/error/errorHandler.js";import{log as u}from"../utils/log/logUtils.js";import b from"../appConfig/index.js";import{createWorkspace as D,pushSkillsToWorkspace as T,initProjectTemplate as R,installProjectDependencies as M,executeCommand as _,deleteWorkspace as B,zipWorkspace as H,buildAgentPackage as L,cleanupBuildArtifacts as W}from"../utils/computer/computerUtils.js";import{getFileList as J,getFileMeta as K,resolveExistingFile as V,searchFiles as G,updateFiles as Z,uploadFile as X,uploadFiles as Y,generateFile as Q,downloadAllFiles as q,getLatestLogs as ee,importProject as te}from"../utils/computer/computerFileUtils.js";import{resolveServiceContext as p}from"../utils/computer/workspaceContext.js";import{listFsRoots as se,listFsChildren as ae,createFsDirectory as oe,renameFsDirectory as re}from"../utils/computer/fsBrowserUtils.js";import{trackMulterTempPath as ne,cleanupMulterFiles as ie}from"../utils/common/multerCleanup.js";const A=v.Router();A.use(ie);const I=z({storage:z.diskStorage({destination:(e,r,s)=>{try{const t=typeof e.headers["x-workspace-type"]=="string"?e.headers["x-workspace-type"].trim().toLowerCase():"",a=typeof e.headers["x-app-id"]=="string"?e.headers["x-app-id"].trim():"";let o;if(t==="userapp"&&a){const n=b.USERAPP_WORKSPACE_DIR;if(!n)return s(new Error("USERAPP_WORKSPACE_DIR is not configured, cannot determine upload temporary directory"));o=j.join(n,String(a),".tmp")}else{const n=b.COMPUTER_WORKSPACE_DIR;if(!n)return s(new Error("COMPUTER_WORKSPACE_DIR is not configured, cannot determine upload temporary directory"));const i=e.body?.userId||"unknown",l=e.body?.cId||"unknown";o=j.join(n,String(i),String(l),".tmp")}g.existsSync(o)||g.mkdirSync(o,{recursive:!0}),e._multerUploadDir=o,s(null,o)}catch(t){s(t)}},filename:(e,r,s)=>{const t=j.extname(r.originalname)||".zip",a=j.basename(r.originalname,t),o=`${Date.now()}_${Math.round(Math.random()*1e6)}`,n=`${a}_${o}${t}`;e._multerUploadDir&&ne(e,j.join(e._multerUploadDir,n)),s(null,n)}}),limits:{fileSize:b.UPLOAD_MAX_FILE_SIZE_BYTES}});function le(e,r,s,t){if(e&&e.name==="MulterError"||e&&(e.name==="ValidationError"||e instanceof P))return t(e);t(e)}const ce=[{path:"/create-workspace",method:"post",middleware:I.single("file"),handler:c(async(e,r)=>{const{userId:s,cId:t}=e.body||{},a=e.file||null,o=p(e),n=`computer:${s}:${t}`;u(n,"INFO","Create workspace request",{userId:s,cId:t,workspaceType:o.workspaceType,appId:o.appId,hasFile:!!a,fileName:a?.originalname,fileSize:a?.size});const i=await D(s,t,a,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,o);r.status(200).json({success:!0,...i})})},{path:"/create-workspace-v2",method:"post",middleware:I.single("file"),handler:c(async(e,r)=>{const{userId:s,cId:t,agentId:a,skillUrls:o,skillUrlMap:n,skillNames:i,updateSkillNames:l,mcpServersConfig:d,hooksConfig:f,permissionsConfig:m,hookScripts:h}=e.body||{},k=e.file||null,C=p(e),O=`computer:${s}:${t}`;let F=o;if(typeof o=="string")try{F=JSON.parse(o)}catch{F=[o]}let w=n;if(typeof n=="string")try{w=JSON.parse(n)}catch{w=null}let N=i;if(typeof i=="string")try{const y=JSON.parse(i);N=Array.isArray(y)?y:[i]}catch{N=i.split(",").map(y=>y.trim()).filter(Boolean)}let S=l;if(typeof l=="string")try{const y=JSON.parse(l);S=Array.isArray(y)?y:[l]}catch{S=l.split(",").map(y=>y.trim()).filter(Boolean)}let $=h;if(typeof h=="string")try{$=JSON.parse(h)}catch{$=null}const U=Array.isArray(F)?F.length:0,x=w&&typeof w=="object"&&!Array.isArray(w)?Object.keys(w).length:0;u(O,"INFO","Create workspace v2 request",{userId:s,cId:t,agentId:a,workspaceType:C.workspaceType,appId:C.appId,hasFile:!!k,fileName:k?.originalname,fileSize:k?.size,skillUrlsCount:U,skillUrlMapCount:x,skillNamesCount:Array.isArray(N)?N.length:0,updateSkillNamesCount:Array.isArray(S)?S.length:0,hasMcpServersConfig:!!d,hasHooksConfig:!!f,hasPermissionsConfig:!!m,hookScriptsCount:Array.isArray($)?$.length:0});const E=await D(s,t,k,F,d,m,f,$,a,N,S,w,C);r.status(200).json({success:!0,...E})})},{path:"/push-skills-to-workspace",method:"post",middleware:I.single("file"),handler:c(async(e,r)=>{const{userId:s,cId:t}=e.body||{},a=e.file||null,o=p(e),n=`computer:${s}:${t}`;u(n,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42",{userId:s,cId:t,workspaceType:o.workspaceType,appId:o.appId,hasFile:!!a,fileName:a?.originalname,fileSize:a?.size});const i=await T(s,t,a,void 0,void 0,o);r.status(200).json({success:!0,...i})})},{path:"/push-skills-to-workspace-v2",method:"post",middleware:I.single("file"),handler:c(async(e,r)=>{const{userId:s,cId:t,agentId:a,skillUrls:o}=e.body||{},n=e.file||null,i=p(e),l=`computer:${s}:${t}`;let d=o;if(typeof o=="string")try{const m=JSON.parse(o);d=Array.isArray(m)?m:[o]}catch{d=[o]}u(l,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42(v2)",{userId:s,cId:t,agentId:a,workspaceType:i.workspaceType,appId:i.appId,hasFile:!!n,fileName:n?.originalname,fileSize:n?.size,skillUrlsCount:Array.isArray(d)?d.length:0});const f=await T(s,t,n,d,a,i);r.status(200).json({success:!0,...f})})},{path:"/fs/roots",method:"get",handler:c(async(e,r)=>{const s=await se();r.status(200).json({success:!0,...s})})},{path:"/fs/children",method:"get",handler:c(async(e,r)=>{const{path:s}=e.query,t=await ae(s);r.status(200).json({success:!0,...t})})},{path:"/fs/mkdir",method:"post",handler:c(async(e,r)=>{const{parentPath:s,dirName:t}=e.body||{},a=await oe(s,t);r.status(200).json({success:!0,...a})})},{path:"/fs/rename",method:"post",handler:c(async(e,r)=>{const{path:s,newName:t}=e.body||{},a=await re(s,t);r.status(200).json({success:!0,...a})})},{path:"/get-file-list",method:"get",handler:c(async(e,r)=>{const{userId:s,cId:t,proxyPath:a,customTargetDir:o,relativePath:n,recursive:i,depth:l,type:d,limit:f}=e.query,m=await J(s,t,a,o,n,i,l,d,f,p(e));r.status(200).json({success:!0,...m})})},{path:"/resolve-file",method:"get",handler:c(async(e,r)=>{const{userId:s,cId:t,proxyPath:a,customTargetDir:o,filePath:n}=e.query,i=await V(s,t,n,a,o,p(e));r.status(200).json({success:!0,...i})})},{path:"/get-file-meta",method:"post",handler:c(async(e,r)=>{const{userId:s,cId:t,customTargetDir:a,filePaths:o,fileMetaMaxBatch:n}=e.body||{},i=await K(s,t,o,a,p(e),n);r.status(200).json({success:!0,...i})})},{path:"/search-files",method:"get",handler:c(async(e,r)=>{const{userId:s,cId:t,proxyPath:a,customTargetDir:o,relativePath:n,type:i,kw:l,limit:d,maxVisit:f,timeoutMs:m}=e.query,h=await G(s,t,a,l,o,n,i,d,f,m,p(e));r.status(200).json({success:!0,...h})})},{path:"/files-update",method:"post",handler:c(async(e,r)=>{const{userId:s,cId:t,files:a,customTargetDir:o}=e.body||{},n=`computer:${s}:${t}`;u(n,"INFO","Files update",{userId:s,cId:t,filesCount:a?a.length:0}),a&&Array.isArray(a)&&a.forEach(l=>{if(l&&typeof l.contents=="string"&&l.contents)try{l.contents=decodeURIComponent(l.contents)}catch(d){u(n,"WARN","Decode file content failed",{fileName:l.name,error:d.message})}});const i=await Z(s,t,a,o,p(e));r.status(200).json(i)})},{path:"/upload-file",method:"post",middleware:I.single("file"),handler:c(async(e,r)=>{const{userId:s,cId:t,filePath:a,customTargetDir:o}=e.body||{},n=e.file,i=`computer:${s}:${t}`;u(i,"INFO","Upload single file",{userId:s,cId:t,filePath:a});const d={buffer:await g.promises.readFile(n.path),originalname:n.originalname,mimetype:n.mimetype,size:n.size};try{const f=await X(s,t,d,a,o,p(e));r.status(200).json(f)}finally{g.existsSync(n.path)&&await g.promises.unlink(n.path)}})},{path:"/generate-file",method:"post",handler:c(async(e,r)=>{const{userId:s,cId:t,fileName:a,content:o,customTargetDir:n}=e.body||{},i=`computer:${s}:${t}`;u(i,"INFO","Generate file request",{userId:s,cId:t,fileName:a,contentLength:typeof o=="string"?o.length:0});const l=await Q(s,t,a,o,n,p(e));r.status(200).json(l)})},{path:"/import-project",method:"post",middleware:I.single("file"),handler:c(async(e,r)=>{const{userId:s,cId:t,customTargetDir:a}=e.body||{},o=e.file,n=`computer:${s}:${t}`;if(u(n,"INFO","Import project request",{userId:s,cId:t,fileName:o?.originalname,fileSize:o?.size}),!o)throw new P("file is required",{field:"file"});const i=await te(s,t,o,a,p(e));r.status(200).json(i)})},{path:"/upload-files",method:"post",middleware:I.array("files"),handler:c(async(e,r)=>{const{userId:s,cId:t,filePaths:a,customTargetDir:o}=e.body||{},n=e.files||[],i=`computer:${s}:${t}`,l=Array.isArray(a)?a:typeof a=="string"?[a]:a;u(i,"INFO","Batch upload files request",{userId:s,cId:t,filesCount:n.length,filePathsCount:Array.isArray(l)?l.length:0});const d=[],f=[];try{for(const h of n){f.push(h.path);const k=await g.promises.readFile(h.path);d.push({buffer:k,originalname:h.originalname,mimetype:h.mimetype,size:h.size})}const m=await Y(s,t,d,l,o,p(e));r.status(200).json(m)}finally{for(const m of f)if(g.existsSync(m))try{await g.promises.unlink(m)}catch(h){u(i,"WARN","Clean temporary file failed",{tempPath:m,error:h.message})}}})},{path:"/install-project",method:"post",handler:c(async(e,r)=>{const{userId:s,cId:t,programmingLanguage:a}=e.body||{},o=`computer:${s}:${t}`;u(o,"INFO","Install project request",{userId:s,cId:t,programmingLanguage:a});const n=await M(s,t,a,p(e));r.status(200).json({success:!0,...n})})},{path:"/init-project-template",method:"post",middleware:I.single("file"),handler:c(async(e,r)=>{const{userId:s,cId:t,enableGit:a}=e.body||{},o=e.file||null,n=`computer:${s}:${t}`;u(n,"INFO","Init project template request",{userId:s,cId:t,hasFile:!!o,fileName:o?.originalname,fileSize:o?.size,enableGit:a});const i=await R(s,t,o,a,p(e));r.status(200).json({success:!0,...i})})},{path:"/download-all-files",method:"get",handler:c(async(e,r)=>{const{userId:s,cId:t,customTargetDir:a}=e.query||{},o=`computer:${s}:${t}`;u(o,"INFO","Download all files request",{userId:s,cId:t});const{archive:n,zipFileName:i}=await q(s,t,a,p(e));r.setHeader("Content-Type","application/zip");const l=encodeURIComponent(i);r.setHeader("Content-Disposition",`attachment; filename="${l}"; filename*=UTF-8''${l}`),n.on("error",d=>{r.destroy(d)}),n.pipe(r),n.finalize()})},{path:"/execute-command",method:"post",handler:c(async(e,r)=>{const{userId:s,cId:t,command:a}=e.body||{},o=`computer:${s}:${t}`;u(o,"INFO","Execute command request",{userId:s,cId:t,command:a});const n=await _(s,t,a,p(e));r.status(200).json({success:!0,...n})})},{path:"/delete-workspace",method:"post",handler:c(async(e,r)=>{const{userId:s,cId:t}=e.body||{},a=`computer:${s}:${t}`;u(a,"INFO","Delete workspace request",{userId:s,cId:t});const o=await B(s,t,p(e));r.status(200).json({success:!0,...o})})},{path:"/get-logs",method:"get",handler:c(async(e,r)=>{const{userId:s,cId:t,tailLines:a}=e.query,o=a?parseInt(a,10):200,n=Number.isFinite(o)&&o>0?o:200,i=await ee(s,t,n,p(e));r.status(200).json(i)})},{path:"/zip-workspace",method:"post",handler:c(async(e,r)=>{const{userId:s,cId:t,excludeDirs:a}=e.body||{},o=`computer:${s}:${t}`;u(o,"INFO","Zip workspace request",{userId:s,cId:t,hasExcludeDirs:!!a});const{archive:n,zipFileName:i}=await H(s,t,a||null,p(e));r.setHeader("Content-Type","application/zip");const l=encodeURIComponent(i);r.setHeader("Content-Disposition",`attachment; filename="${l}"; filename*=UTF-8''${l}`),n.on("error",d=>{r.destroy(d)}),n.pipe(r),n.finalize()})},{path:"/build-agent-package",method:"post",handler:c(async(e,r)=>{const{userId:s,cId:t,agentId:a,version:o}=e.body||{},n=`computer:${s}:${t}`;u(n,"INFO","Build agent package request",{userId:s,cId:t,agentId:a,version:o});const i=await L(s,t,a,o,p(e));r.status(200).json({success:!0,...i})})},{path:"/cleanup-build-artifacts",method:"post",handler:c(async(e,r)=>{const{userId:s,cId:t}=e.body||{},a=`computer:${s}:${t}`;u(a,"INFO","Cleanup build artifacts request",{userId:s,cId:t});const o=await W(s,t,p(e));r.status(200).json({success:!0,...o})})}];ce.forEach(e=>{if(e.middleware)A[e.method](e.path,e.middleware,e.handler);else if(e.customHandler){const r=[];e.handler&&r.push(e.handler),e.decodeMiddleware&&r.push(e.decodeMiddleware),r.push(le),r.push(e.customHandler),A[e.method](e.path,...r)}else A[e.method](e.path,e.handler)});export default A;
@@ -1 +1 @@
1
- import P from"express";import j from"multer";import u from"path";import d from"fs";import b from"iconv-lite";import m from"../service/projectService.js";import{getProjectContent as E,getProjectContentByVersion as D}from"../utils/project/getContentUtils.js";import{uploadAttachmentFile as C}from"../utils/project/uploadAttachmentFileUtils.js";import{copyProject as T}from"../utils/project/copyProjectUtils.js";import h from"../appConfig/index.js";import{log as y}from"../utils/log/logUtils.js";import{extractIsolationContext as l,resolveProjectPath as _}from"../utils/common/projectPathUtils.js";import{ValidationError as s,SystemError as R,asyncHandler as p}from"../utils/error/errorHandler.js";import{trackMulterTempPath as I,cleanupMulterFiles as O}from"../utils/common/multerCleanup.js";const w=P.Router();w.use(O);const A=j.diskStorage({destination:function(e,n,t){const o=u.join(h.UPLOAD_PROJECT_DIR,"temp");d.existsSync(o)||d.mkdirSync(o,{recursive:!0}),e._multerUploadDir=o,t(null,o)},filename:function(e,n,t){const o=Date.now()+"-"+Math.round(Math.random()*1e6),a=n.fieldname+"-"+o+u.extname(n.originalname);I(e,u.join(e._multerUploadDir||"",a)),t(null,a)}}),S=j({storage:A,fileFilter:function(e,n,t){const o=u.extname(n.originalname).toLowerCase();h.UPLOAD_ALLOWED_EXTENSIONS.includes(o)?t(null,!0):t(new s("File type not allowed",{fileExtension:o,allowedExtensions:h.UPLOAD_ALLOWED_EXTENSIONS}),!1)},limits:{fileSize:h.UPLOAD_MAX_FILE_SIZE_BYTES}}),L=j.diskStorage({destination:function(e,n,t){const o=u.join(h.UPLOAD_PROJECT_DIR,"temp");d.existsSync(o)||d.mkdirSync(o,{recursive:!0}),e._multerUploadDir=o,t(null,o)},filename:function(e,n,t){const o=Date.now()+"-"+Math.round(Math.random()*1e6),a=n.fieldname+"-"+o+u.extname(n.originalname);I(e,u.join(e._multerUploadDir||"",a)),t(null,a)}}),x=[".pdf",".doc",".docx",".xls",".xlsx",".ppt",".pptx",".txt",".md",".csv",".json",".xml",".png",".jpg",".jpeg",".gif",".bmp",".svg",".ico",".webp",".avif",".zip",".rar",".7z",".tar",".gz",".mp4",".avi",".mov",".wmv",".flv",".mp3",".wav",".ogg",".m4a"],v=j({storage:L,fileFilter:function(e,n,t){const o=u.extname(n.originalname).toLowerCase();x.includes(o)?t(null,!0):t(new s("\u9644\u4EF6\u6587\u4EF6\u7C7B\u578B\u4E0D\u88AB\u5141\u8BB8",{fileExtension:o,allowedExtensions:x}),!1)},limits:{fileSize:h.UPLOAD_MAX_FILE_SIZE_BYTES}});function z(e,n,t){if(e.file&&e.file.originalname)try{const o=e.file.originalname,a=b.decode(Buffer.from(e.file.originalname,"latin1"),"utf8");e.file.originalname=a,y("system","INFO","File name decoded successfully",{before:o,after:a})}catch(o){y("system","WARN","File name decoded failed",{originalName:e.file.originalname,error:o.message})}t()}const N=[{path:"/push-skills-to-workspace",method:"post",handler:S.single("file"),customHandler:p(async(e,n)=>{const{projectId:t,skillUrls:o}=e.body||{},a=e.file||null;if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});const i=l(e.body||{}),r=await m.pushSkillsToWorkspace(String(t),a,o,i);n.status(200).json({success:!0,...r})})},{path:"/create-project",method:"post",handler:p(async(e,n)=>{const{projectId:t,templateType:o}=e.body,a=l(e.body||{});if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});const i=await m.createProject(String(t),o,a);n.status(200).json(i)})},{path:"/upload-project",method:"post",handler:S.single("file"),customHandler:p(async(e,n)=>{const{projectId:t,codeVersion:o,pid:a,basePath:i}=e.body,r=l(e.body||{});if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});if(!o)throw new s("Code version cannot be empty",{field:"codeVersion"});if(!e.file)throw new s("Please upload a zip file",{field:"zipFile"});const c=await m.handleFileUpload(String(t),o,e.file);e.file.path=c.filePath;try{const f=await m.uploadProject(String(t),e.file.path,e,o,a,i,r);n.status(200).json(f)}catch(f){try{await m.cleanupProjectDirectory(String(t),r)}catch(g){y(t,"ERROR","Route layer cleanup project directory failed",{projectId:t,error:g.message})}if(e.file&&d.existsSync(e.file.path))try{d.unlinkSync(e.file.path)}catch(g){y(t,"ERROR","Clean upload file failed",{projectId:t,error:g.message})}throw f}})},{path:"/get-project-content",method:"get",handler:p(async(e,n)=>{const{projectId:t,command:o,proxyPath:a}=e.query,i=l(e.query||{});if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});const r=_(t,i);if(!d.existsSync(r))throw new s("Project does not exist",{field:"projectId"});try{const c=await E(r,o,a);n.status(200).json({success:!0,...c})}catch(c){const f=c?.message||"Query failed";n.status(500).json({success:!1,message:f})}})},{path:"/get-project-content-by-version",method:"get",handler:p(async(e,n)=>{const{projectId:t,codeVersion:o,command:a,proxyPath:i}=e.query,r=l(e.query||{});if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});if(!o)throw new s("Code version cannot be empty",{field:"codeVersion"});if(h.GIT_ENABLED)return n.status(200).json({success:!1,deprecated:!0,message:"\u6B64\u63A5\u53E3\u5DF2\u5E9F\u5F03\uFF0C\u8BF7\u4F7F\u7528 /api/git/log + /api/git/diff \u67E5\u770B\u5386\u53F2\u7248\u672C\u5185\u5BB9"});try{const c=await D(String(t),o,a,i,r);n.status(200).json({success:!0,...c})}catch(c){const f=c?.message||"Query failed";n.status(500).json({success:!1,message:f})}})},{path:"/backup-current-version",method:"post",handler:p(async(e,n)=>{const{projectId:t,codeVersion:o}=e.body,a=l(e.body||{});if(h.GIT_ENABLED)return n.status(200).json({success:!1,deprecated:!0,message:"\u6B64\u63A5\u53E3\u5DF2\u5E9F\u5F03\uFF0C\u8BF7\u4F7F\u7528 Git \u7248\u672C\u7BA1\u7406 API\uFF08/api/git/*\uFF09"});const i=await m.backupCurrentVersion(String(t),o,a);n.status(200).json({success:!0,...i})})},{path:"/export-project",method:"post",handler:p(async(e,n)=>{const{projectId:t,codeVersion:o,exportType:a,config:i}=e.body,r=l(e.body||{}),c=await m.exportProject(String(t),o,a,i,r);if(!d.existsSync(c.zipPath))throw new R("Exported zip file does not exist",{zipPath:c.zipPath});const f=u.basename(c.zipPath);n.setHeader("Content-Type","application/zip"),n.setHeader("Content-Disposition",`attachment; filename="${f}"`),n.sendFile(c.zipPath,g=>{g?(y(t,"ERROR","Send zip file failed",{projectId:t,error:g.message}),n.headersSent||n.status(500).json({success:!1,message:"File send failed"})):y(t,"INFO","Zip file send successfully",{projectId:t,zipPath:c.zipPath})})})},{path:"/delete-project",method:"get",handler:p(async(e,n)=>{const{projectId:t,pid:o}=e.query,a=l(e.query||{});if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});const i=await m.deleteProject(String(t),o,e,a);n.status(200).json(i)})},{path:"/upload-attachment-file",method:"post",handler:v.single("file"),decodeMiddleware:z,customHandler:p(async(e,n)=>{const{projectId:t,fileName:o}=e.body,a=l(e.body||{});if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});if(!e.file)throw new s("Please upload a file",{field:"file"});try{const i=await C(String(t),e.file,o,a);n.status(200).json({success:!0,...i})}catch(i){if(e.file&&d.existsSync(e.file.path))try{d.unlinkSync(e.file.path)}catch(r){y(t,"ERROR","Clean upload file failed",{projectId:t,error:r.message})}throw i}})},{path:"/copy-project",method:"post",handler:p(async(e,n)=>{const{sourceProjectId:t,targetProjectId:o}=e.body,a=l({tenantId:e.body?.sourceTenantId||e.body?.tenantId,spaceId:e.body?.sourceSpaceId||e.body?.spaceId,isolationType:e.body?.sourceIsolationType||e.body?.isolationType}),i=l({tenantId:e.body?.targetTenantId||e.body?.tenantId,spaceId:e.body?.targetSpaceId||e.body?.spaceId,isolationType:e.body?.targetIsolationType||e.body?.isolationType});if(!t)throw new s("Source project ID cannot be empty",{field:"sourceProjectId"});if(!o)throw new s("Target project ID cannot be empty",{field:"targetProjectId"});const r=await T(String(t),String(o),{sourceIsolationContext:a,targetIsolationContext:i});n.status(200).json(r)})}];function F(e,n,t,o){if(e instanceof j.MulterError||e.name==="ValidationError"||e instanceof s)return o(e);o(e)}N.forEach(e=>{if(e.customHandler){const n=[];e.handler&&n.push(e.handler),e.decodeMiddleware&&n.push(e.decodeMiddleware),n.push(F),n.push(e.customHandler),w[e.method](e.path,...n)}else w[e.method](e.path,e.handler)});export default w;
1
+ import P from"express";import j from"multer";import u from"path";import d from"fs";import b from"iconv-lite";import m from"../service/projectService.js";import{getProjectContent as E,getProjectContentByVersion as D}from"../utils/project/getContentUtils.js";import{uploadAttachmentFile as C}from"../utils/project/uploadAttachmentFileUtils.js";import{copyProject as T}from"../utils/project/copyProjectUtils.js";import h from"../appConfig/index.js";import{log as y}from"../utils/log/logUtils.js";import{extractIsolationContext as l,resolveProjectPath as _}from"../utils/common/projectPathUtils.js";import{ValidationError as s,SystemError as R,asyncHandler as p}from"../utils/error/errorHandler.js";import{trackMulterTempPath as I,cleanupMulterFiles as O}from"../utils/common/multerCleanup.js";const w=P.Router();w.use(O);const A=j.diskStorage({destination:function(e,n,t){const o=u.join(h.UPLOAD_PROJECT_DIR,"temp");d.existsSync(o)||d.mkdirSync(o,{recursive:!0}),e._multerUploadDir=o,t(null,o)},filename:function(e,n,t){const o=Date.now()+"-"+Math.round(Math.random()*1e6),a=n.fieldname+"-"+o+u.extname(n.originalname);e._multerUploadDir&&I(e,u.join(e._multerUploadDir,a)),t(null,a)}}),S=j({storage:A,fileFilter:function(e,n,t){const o=u.extname(n.originalname).toLowerCase();h.UPLOAD_ALLOWED_EXTENSIONS.includes(o)?t(null,!0):t(new s("File type not allowed",{fileExtension:o,allowedExtensions:h.UPLOAD_ALLOWED_EXTENSIONS}),!1)},limits:{fileSize:h.UPLOAD_MAX_FILE_SIZE_BYTES}}),L=j.diskStorage({destination:function(e,n,t){const o=u.join(h.UPLOAD_PROJECT_DIR,"temp");d.existsSync(o)||d.mkdirSync(o,{recursive:!0}),e._multerUploadDir=o,t(null,o)},filename:function(e,n,t){const o=Date.now()+"-"+Math.round(Math.random()*1e6),a=n.fieldname+"-"+o+u.extname(n.originalname);e._multerUploadDir&&I(e,u.join(e._multerUploadDir,a)),t(null,a)}}),x=[".pdf",".doc",".docx",".xls",".xlsx",".ppt",".pptx",".txt",".md",".csv",".json",".xml",".png",".jpg",".jpeg",".gif",".bmp",".svg",".ico",".webp",".avif",".zip",".rar",".7z",".tar",".gz",".mp4",".avi",".mov",".wmv",".flv",".mp3",".wav",".ogg",".m4a"],v=j({storage:L,fileFilter:function(e,n,t){const o=u.extname(n.originalname).toLowerCase();x.includes(o)?t(null,!0):t(new s("\u9644\u4EF6\u6587\u4EF6\u7C7B\u578B\u4E0D\u88AB\u5141\u8BB8",{fileExtension:o,allowedExtensions:x}),!1)},limits:{fileSize:h.UPLOAD_MAX_FILE_SIZE_BYTES}});function z(e,n,t){if(e.file&&e.file.originalname)try{const o=e.file.originalname,a=b.decode(Buffer.from(e.file.originalname,"latin1"),"utf8");e.file.originalname=a,y("system","INFO","File name decoded successfully",{before:o,after:a})}catch(o){y("system","WARN","File name decoded failed",{originalName:e.file.originalname,error:o.message})}t()}const N=[{path:"/push-skills-to-workspace",method:"post",handler:S.single("file"),customHandler:p(async(e,n)=>{const{projectId:t,skillUrls:o}=e.body||{},a=e.file||null;if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});const i=l(e.body||{}),r=await m.pushSkillsToWorkspace(String(t),a,o,i);n.status(200).json({success:!0,...r})})},{path:"/create-project",method:"post",handler:p(async(e,n)=>{const{projectId:t,templateType:o}=e.body,a=l(e.body||{});if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});const i=await m.createProject(String(t),o,a);n.status(200).json(i)})},{path:"/upload-project",method:"post",handler:S.single("file"),customHandler:p(async(e,n)=>{const{projectId:t,codeVersion:o,pid:a,basePath:i}=e.body,r=l(e.body||{});if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});if(!o)throw new s("Code version cannot be empty",{field:"codeVersion"});if(!e.file)throw new s("Please upload a zip file",{field:"zipFile"});const c=await m.handleFileUpload(String(t),o,e.file);e.file.path=c.filePath;try{const f=await m.uploadProject(String(t),e.file.path,e,o,a,i,r);n.status(200).json(f)}catch(f){try{await m.cleanupProjectDirectory(String(t),r)}catch(g){y(t,"ERROR","Route layer cleanup project directory failed",{projectId:t,error:g.message})}if(e.file&&d.existsSync(e.file.path))try{d.unlinkSync(e.file.path)}catch(g){y(t,"ERROR","Clean upload file failed",{projectId:t,error:g.message})}throw f}})},{path:"/get-project-content",method:"get",handler:p(async(e,n)=>{const{projectId:t,command:o,proxyPath:a}=e.query,i=l(e.query||{});if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});const r=_(t,i);if(!d.existsSync(r))throw new s("Project does not exist",{field:"projectId"});try{const c=await E(r,o,a);n.status(200).json({success:!0,...c})}catch(c){const f=c?.message||"Query failed";n.status(500).json({success:!1,message:f})}})},{path:"/get-project-content-by-version",method:"get",handler:p(async(e,n)=>{const{projectId:t,codeVersion:o,command:a,proxyPath:i}=e.query,r=l(e.query||{});if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});if(!o)throw new s("Code version cannot be empty",{field:"codeVersion"});if(h.GIT_ENABLED)return n.status(200).json({success:!1,deprecated:!0,message:"\u6B64\u63A5\u53E3\u5DF2\u5E9F\u5F03\uFF0C\u8BF7\u4F7F\u7528 /api/git/log + /api/git/diff \u67E5\u770B\u5386\u53F2\u7248\u672C\u5185\u5BB9"});try{const c=await D(String(t),o,a,i,r);n.status(200).json({success:!0,...c})}catch(c){const f=c?.message||"Query failed";n.status(500).json({success:!1,message:f})}})},{path:"/backup-current-version",method:"post",handler:p(async(e,n)=>{const{projectId:t,codeVersion:o}=e.body,a=l(e.body||{});if(h.GIT_ENABLED)return n.status(200).json({success:!1,deprecated:!0,message:"\u6B64\u63A5\u53E3\u5DF2\u5E9F\u5F03\uFF0C\u8BF7\u4F7F\u7528 Git \u7248\u672C\u7BA1\u7406 API\uFF08/api/git/*\uFF09"});const i=await m.backupCurrentVersion(String(t),o,a);n.status(200).json({success:!0,...i})})},{path:"/export-project",method:"post",handler:p(async(e,n)=>{const{projectId:t,codeVersion:o,exportType:a,config:i}=e.body,r=l(e.body||{}),c=await m.exportProject(String(t),o,a,i,r);if(!d.existsSync(c.zipPath))throw new R("Exported zip file does not exist",{zipPath:c.zipPath});const f=u.basename(c.zipPath);n.setHeader("Content-Type","application/zip"),n.setHeader("Content-Disposition",`attachment; filename="${f}"`),n.sendFile(c.zipPath,g=>{g?(y(t,"ERROR","Send zip file failed",{projectId:t,error:g.message}),n.headersSent||n.status(500).json({success:!1,message:"File send failed"})):y(t,"INFO","Zip file send successfully",{projectId:t,zipPath:c.zipPath})})})},{path:"/delete-project",method:"get",handler:p(async(e,n)=>{const{projectId:t,pid:o}=e.query,a=l(e.query||{});if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});const i=await m.deleteProject(String(t),o,e,a);n.status(200).json(i)})},{path:"/upload-attachment-file",method:"post",handler:v.single("file"),decodeMiddleware:z,customHandler:p(async(e,n)=>{const{projectId:t,fileName:o}=e.body,a=l(e.body||{});if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});if(!e.file)throw new s("Please upload a file",{field:"file"});try{const i=await C(String(t),e.file,o,a);n.status(200).json({success:!0,...i})}catch(i){if(e.file&&d.existsSync(e.file.path))try{d.unlinkSync(e.file.path)}catch(r){y(t,"ERROR","Clean upload file failed",{projectId:t,error:r.message})}throw i}})},{path:"/copy-project",method:"post",handler:p(async(e,n)=>{const{sourceProjectId:t,targetProjectId:o}=e.body,a=l({tenantId:e.body?.sourceTenantId||e.body?.tenantId,spaceId:e.body?.sourceSpaceId||e.body?.spaceId,isolationType:e.body?.sourceIsolationType||e.body?.isolationType}),i=l({tenantId:e.body?.targetTenantId||e.body?.tenantId,spaceId:e.body?.targetSpaceId||e.body?.spaceId,isolationType:e.body?.targetIsolationType||e.body?.isolationType});if(!t)throw new s("Source project ID cannot be empty",{field:"sourceProjectId"});if(!o)throw new s("Target project ID cannot be empty",{field:"targetProjectId"});const r=await T(String(t),String(o),{sourceIsolationContext:a,targetIsolationContext:i});n.status(200).json(r)})}];function F(e,n,t,o){if(e instanceof j.MulterError||e.name==="ValidationError"||e instanceof s)return o(e);o(e)}N.forEach(e=>{if(e.customHandler){const n=[];e.handler&&n.push(e.handler),e.decodeMiddleware&&n.push(e.decodeMiddleware),n.push(F),n.push(e.customHandler),w[e.method](e.path,...n)}else w[e.method](e.path,e.handler)});export default w;
package/dist/server.js CHANGED
@@ -1 +1 @@
1
- import f from"express";import"json-bigint";import T from"swagger-ui-express";import R from"./config/swagger.js";import a from"./appConfig/index.js";import{log as c,logger as w}from"./utils/log/logUtils.js";import P from"./utils/log/logCacheManager.js";import{errorHandler as y,notFoundHandler as F}from"./utils/error/errorHandler.js";import N from"./routes/router.js";import{cleanupInitProjectOnStartup as H}from"./utils/project/initProjectCleanupUtils.js";import{resolveServiceContext as D,resolveWorkspaceDir as v}from"./utils/computer/workspaceContext.js";import{startScheduler as x,stopScheduler as L}from"./scheduler/pnpmPruneScheduler.js";import A from"path";import M from"fs/promises";const r=f();r.use(f.json({limit:a.REQUEST_BODY_LIMIT,reviver:(t,e)=>typeof e=="number"&&!Number.isSafeInteger(e)?e.toString():e})),r.use(f.urlencoded({extended:!0,limit:a.REQUEST_BODY_LIMIT})),r.use(w);const O=t=>{let e=t;try{for(;;){const o=decodeURIComponent(e);if(o===e)break;e=o}}catch{}return e},E=async(t,e)=>{try{const o=await M.stat(e);o.isFile()&&t.setHeader("X-File-Size",o.size)}catch{}};r.use("/api/page/static/:projectId",async(t,e,o)=>{const{projectId:d}=t.params;let s=t.path||"/";if(!d||s==="/")return e.status(404).send("Not Found");const n=t.headers.origin,l=n||"*";if(e.header("Access-Control-Allow-Origin",l),e.header("Access-Control-Allow-Methods","HEAD,GET,POST,PUT,DELETE,OPTIONS"),e.header("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Fragment"),e.header("Access-Control-Expose-Headers","Content-Type, X-File-Size"),n&&(e.header("Access-Control-Allow-Credentials","true"),e.header("Vary","Origin")),t.method==="OPTIONS")return e.sendStatus(200);s=s.replace(/^\/+/,"");const g=O(s),u=A.join(a.PROJECT_SOURCE_DIR,d,g);await E(e,u);const i={"Access-Control-Allow-Origin":l,"Access-Control-Allow-Methods":"HEAD,GET,POST,PUT,DELETE,OPTIONS","Access-Control-Allow-Headers":"Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Fragment"};return n&&(i["Access-Control-Allow-Credentials"]="true",i.Vary="Origin"),e.sendFile(u,{dotfiles:"allow",headers:i},h=>{if(h)return o()})}),r.use("/api/computer/static/:userId/:cId",async(t,e,o)=>{const{userId:d,cId:s}=t.params;let n=t.path||"/";if(!d||!s||n==="/")return e.status(404).send("Not Found");const l=t.headers.origin,g=l||"*";if(e.header("Access-Control-Allow-Origin",g),e.header("Access-Control-Allow-Methods","HEAD,GET,POST,PUT,DELETE,OPTIONS"),e.header("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Range, If-Range"),e.header("Access-Control-Expose-Headers","Content-Type, Content-Length, Content-Range, Accept-Ranges, ETag, Last-Modified, X-File-Size"),l&&(e.header("Access-Control-Allow-Credentials","true"),e.header("Vary","Origin")),t.method==="OPTIONS")return e.sendStatus(200);n=n.replace(/^\/+/,"");const u=O(n),{customTargetDir:i}=t.query,h=D(t),C=i&&i.trim()?A.join(i,u):A.join(v(h,d,s),u);await E(e,C);const m={"Access-Control-Allow-Origin":g,"Access-Control-Allow-Methods":"HEAD,GET,POST,PUT,DELETE,OPTIONS","Access-Control-Allow-Headers":"Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Range, If-Range","Access-Control-Expose-Headers":"Content-Type, Content-Length, Content-Range, Accept-Ranges, ETag, Last-Modified, X-File-Size"};return l&&(m["Access-Control-Allow-Credentials"]="true",m.Vary="Origin"),e.sendFile(C,{dotfiles:"allow",headers:m},I=>{if(I)return o()})}),r.use("/api-docs",T.serve,T.setup(R,{customCss:".swagger-ui .topbar { display: none }",customSiteTitle:"nuwax-file-server API Documentation"})),r.use(N),r.use(F),r.use(y);const p=r.listen(a.PORT,async()=>{c("default","INFO",`Server is running on port ${a.PORT} (${a.NODE_ENV} mode)`),await H(a);try{x()}catch(t){c("default","ERROR",`pnpm prune scheduled task failed to start: ${t.message}`)}});p.timeout=6e5,p.keepAliveTimeout=61e4,p.headersTimeout=62e4,p.requestTimeout=63e4;const S=t=>{c("default","INFO",`Received ${t} signal, preparing graceful exit...`),L();try{P.destroy(),c("default","INFO","Log cache manager cleared")}catch(e){c("default","ERROR",`Failed to clear log cache manager: ${e.message}`)}p.close(()=>{c("default","INFO","Server closed"),process.exit(0)}),setTimeout(()=>{c("default","ERROR","Force exit (timeout)"),process.exit(1)},3e4)};process.on("SIGTERM",()=>S("SIGTERM")),process.on("SIGINT",()=>S("SIGINT"));
1
+ import C from"express";import"json-bigint";import O from"swagger-ui-express";import P from"./config/swagger.js";import a from"./appConfig/index.js";import{log as c,logger as y}from"./utils/log/logUtils.js";import F from"./utils/log/logCacheManager.js";import{errorHandler as N,notFoundHandler as H}from"./utils/error/errorHandler.js";import D from"./routes/router.js";import{cleanupInitProjectOnStartup as v}from"./utils/project/initProjectCleanupUtils.js";import{resolveServiceContext as x,resolveWorkspaceDir as L}from"./utils/computer/workspaceContext.js";import{escapesRoot as E}from"./utils/computer/computerFileUtils.js";import{startScheduler as M,stopScheduler as z}from"./scheduler/pnpmPruneScheduler.js";import T from"path";import U from"fs/promises";const r=C();r.use(C.json({limit:a.REQUEST_BODY_LIMIT,reviver:(t,e)=>typeof e=="number"&&!Number.isSafeInteger(e)?e.toString():e})),r.use(C.urlencoded({extended:!0,limit:a.REQUEST_BODY_LIMIT})),r.use(y);const S=t=>{let e=t;try{for(;;){const o=decodeURIComponent(e);if(o===e)break;e=o}}catch{}return e},R=async(t,e)=>{try{const o=await U.stat(e);o.isFile()&&t.setHeader("X-File-Size",o.size)}catch{}};r.use("/api/page/static/:projectId",async(t,e,o)=>{const{projectId:d}=t.params;let s=t.path||"/";if(!d||s==="/")return e.status(404).send("Not Found");const n=t.headers.origin,l=n||"*";if(e.header("Access-Control-Allow-Origin",l),e.header("Access-Control-Allow-Methods","HEAD,GET,POST,PUT,DELETE,OPTIONS"),e.header("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Fragment"),e.header("Access-Control-Expose-Headers","Content-Type, X-File-Size"),n&&(e.header("Access-Control-Allow-Credentials","true"),e.header("Vary","Origin")),t.method==="OPTIONS")return e.sendStatus(200);s=s.replace(/^\/+/,"");const g=S(s),m=T.join(a.PROJECT_SOURCE_DIR,d),i=T.join(m,g);if(await E(m,i))return e.status(404).send("Not Found");await R(e,i);const u={"Access-Control-Allow-Origin":l,"Access-Control-Allow-Methods":"HEAD,GET,POST,PUT,DELETE,OPTIONS","Access-Control-Allow-Headers":"Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Fragment"};return n&&(u["Access-Control-Allow-Credentials"]="true",u.Vary="Origin"),e.sendFile(i,{dotfiles:"allow",headers:u},h=>{if(h)return o()})}),r.use("/api/computer/static/:userId/:cId",async(t,e,o)=>{const{userId:d,cId:s}=t.params;let n=t.path||"/";if(!d||!s||n==="/")return e.status(404).send("Not Found");const l=t.headers.origin,g=l||"*";if(e.header("Access-Control-Allow-Origin",g),e.header("Access-Control-Allow-Methods","HEAD,GET,POST,PUT,DELETE,OPTIONS"),e.header("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Range, If-Range"),e.header("Access-Control-Expose-Headers","Content-Type, Content-Length, Content-Range, Accept-Ranges, ETag, Last-Modified, X-File-Size"),l&&(e.header("Access-Control-Allow-Credentials","true"),e.header("Vary","Origin")),t.method==="OPTIONS")return e.sendStatus(200);n=n.replace(/^\/+/,"");const m=S(n),{customTargetDir:i}=t.query,u=x(t),h=i&&i.trim()?i.trim():L(u,d,s),f=T.join(h,m);if(await E(h,f))return e.status(404).send("Not Found");await R(e,f);const A={"Access-Control-Allow-Origin":g,"Access-Control-Allow-Methods":"HEAD,GET,POST,PUT,DELETE,OPTIONS","Access-Control-Allow-Headers":"Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Range, If-Range","Access-Control-Expose-Headers":"Content-Type, Content-Length, Content-Range, Accept-Ranges, ETag, Last-Modified, X-File-Size"};return l&&(A["Access-Control-Allow-Credentials"]="true",A.Vary="Origin"),e.sendFile(f,{dotfiles:"allow",headers:A},w=>{if(w)return o()})}),r.use("/api-docs",O.serve,O.setup(P,{customCss:".swagger-ui .topbar { display: none }",customSiteTitle:"nuwax-file-server API Documentation"})),r.use(D),r.use(H),r.use(N);const p=r.listen(a.PORT,async()=>{c("default","INFO",`Server is running on port ${a.PORT} (${a.NODE_ENV} mode)`),await v(a);try{M()}catch(t){c("default","ERROR",`pnpm prune scheduled task failed to start: ${t.message}`)}});p.timeout=6e5,p.keepAliveTimeout=61e4,p.headersTimeout=62e4,p.requestTimeout=63e4;const I=t=>{c("default","INFO",`Received ${t} signal, preparing graceful exit...`),z();try{F.destroy(),c("default","INFO","Log cache manager cleared")}catch(e){c("default","ERROR",`Failed to clear log cache manager: ${e.message}`)}p.close(()=>{c("default","INFO","Server closed"),process.exit(0)}),setTimeout(()=>{c("default","ERROR","Force exit (timeout)"),process.exit(1)},3e4)};process.on("SIGTERM",()=>I("SIGTERM")),process.on("SIGINT",()=>I("SIGINT"));
@@ -1,2 +1,2 @@
1
- import c from"fs";import h from"path";import ue from"archiver";import{lookup as ke}from"mime-types";import _ from"../../appConfig/index.js";import{log as f}from"../log/logUtils.js";import{ValidationError as E,SystemError as T,FileError as Ne}from"../error/errorHandler.js";import{extractZip as xe}from"../common/zipUtils.js";import{moveDirectory as Ce,movePath as se}from"../common/fileSystemUtils.js";import{resolveLogDir as _e,resolveWorkspaceDir as O,resolveWorkspaceRoot as fe}from"./workspaceContext.js";const B=new Set([".git",".agents",".claude",".codex",".opencode",".tmp",".logs"]),Pe=100*1024*1024,G=_.DOWNLOAD_MAX_FILE_SIZE_BYTES||Pe;function H(e){return h.sep==="\\"?e.replace(/\\/g,"/"):e}function me(e,r){return H(h.relative(e,r))}function de(e,r){return e?`${e}/${r}`:r}function q(e){return e!=null&&e.limit!=null&&e.pushed>=e.limit}function Z(e){e!=null&&(e.pushed+=1)}async function pe(e,r,t,i,o,a={}){const{typeFilter:n=null,limitState:m=null,levelsLeft:u=1}=a,g=[],y=await c.promises.readdir(e,{withFileTypes:!0});y.sort((s,w)=>s.isDirectory()&&!w.isDirectory()?-1:!s.isDirectory()&&w.isDirectory()?1:s.name.toLowerCase().localeCompare(w.name.toLowerCase()));const l=me(r,e);for(const s of y){if(q(m))break;if(s.name.startsWith(".")&&s.name!==".gitignore"||(_.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(s.name)||s.isDirectory()&&_.TRAVERSE_EXCLUDE_DIRS.includes(s.name))continue;const d=de(l,s.name);if(s.isDirectory()){if(n!=="file"&&(g.push({name:d,isDir:!0}),Z(m)),u>1&&!q(m))try{const p=await pe(h.join(e,s.name),r,t,i,o,{typeFilter:n,limitState:m,levelsLeft:u-1});g.push(...p)}catch(p){f(t,"WARN",`Depth descent failed, skip subtree: ${d}`,{error:p.message})}continue}if(n!=="dir"&&!(s.isSymbolicLink()&&await oe(r,h.join(e,s.name))))try{g.push({name:d,isDir:!1,fileProxyUrl:J(i,d,o),isLink:s.isSymbolicLink()}),Z(m)}catch(p){f(t,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${e}/${s.name}`,{error:p.message})}}return g}async function he(e,r,t,i,o="",a={}){const{typeFilter:n=null,limitState:m=null}=a,u=a.rootDir||e,g=[];let y=0;const l=await c.promises.readdir(e,{withFileTypes:!0});l.sort((s,w)=>s.isDirectory()&&!w.isDirectory()?-1:!s.isDirectory()&&w.isDirectory()?1:s.name.toLowerCase().localeCompare(w.name.toLowerCase()));for(const s of l){if(q(m))break;const w=h.join(e,s.name);if(s.name.startsWith(".")&&s.name!==".gitignore"||(_.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(s.name)||s.isDirectory()&&_.TRAVERSE_EXCLUDE_DIRS.includes(s.name)||s.isSymbolicLink()&&await oe(u,w))continue;const p=de(o,s.name);if(y+=1,s.isDirectory()){const S=await he(w,r,t,i,p,a);S.naturalCount===0?n!=="file"&&!q(m)&&(g.push({name:p,isDir:!0}),Z(m)):g.push(...S.entries)}else{if(n==="dir")continue;try{g.push({name:p,isDir:!1,fileProxyUrl:J(t,p,i),isLink:s.isSymbolicLink()}),Z(m)}catch(S){f(r,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${w}`,{error:S.message})}}}return{entries:g,naturalCount:y}}const K=process.platform==="win32"?/^[\/\\]+/:/^\/+/;async function oe(e,r){let t;try{t=await c.promises.readlink(r)}catch{return!1}const i=h.resolve(h.dirname(r),t),o=h.resolve(e);return i!==o&&!i.startsWith(o+h.sep)}function we(e,r){const t=r==null?"":String(r),i=t.trim();if(!i||i==="."||i==="/")return h.resolve(e);const o=h.normalize(t).replace(K,"");if(!o||o===".")return h.resolve(e);if(h.isAbsolute(t)||o.split(h.sep).includes(".."))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:r});const a=h.resolve(e),n=h.resolve(e,o);if(n!==a&&!n.startsWith(a+h.sep))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:r});return n}function Y(e,r){const t=r==null?"":String(r);if(!t.trim())return null;const i=h.resolve(e);let o;if(h.isAbsolute(t))o=h.resolve(t);else{const n=h.normalize(t).replace(K,"");if(!n||n==="."||n.split(h.sep).includes(".."))return null;o=h.resolve(e,n)}if(o!==i&&!o.startsWith(i+h.sep))return null;const a=H(h.relative(i,o));return!a||a.startsWith("..")?null:{absPath:o,name:a}}function J(e,r,t){if(!e||!r)return null;const i=r.split("/").map(a=>encodeURIComponent(a)).join("/");let o=`${e}/${i}`;return t&&(o+=`?customTargetDir=${encodeURIComponent(t)}`),o}async function Le(e,r,t,i,o,a=null){if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!r)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});if(!t||!String(t).trim())throw new E("filePath \u4E0D\u80FD\u4E3A\u7A7A",{field:"filePath"});const n=String(e),m=String(r),u=o&&o.trim()?o.trim():null,g=u||O(a,n,m);if(!c.existsSync(g))return{exists:!1};let y=Y(g,t);if(!y&&String(t).startsWith("/")){const l=String(t).replace(K,"");l&&(y=Y(g,l))}if(!y)return{exists:!1};try{if((await c.promises.lstat(y.absPath)).isSymbolicLink()&&await oe(g,y.absPath))return{exists:!1};if(!(await c.promises.stat(y.absPath)).isFile())return{exists:!1}}catch{return{exists:!1}}return{exists:!0,name:y.name,fileProxyUrl:J(i,y.name,u)}}const Ae=100,ze=1e3,Te=8,ge=1e3;function Oe(e){return h.extname(e).replace(/^\./,"").toLowerCase()||""}async function $e(e,r,t){const i=new Array(e.length),o=Math.max(1,Math.min(r,e.length));let a=0;return await Promise.all(Array.from({length:o},async()=>{for(;;){const n=a++;if(n>=e.length)return;i[n]=await t(e[n],n)}})),i}function Me(e){return!(e.startsWith(".")&&e!==".gitignore"||(_.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(e)||_.TRAVERSE_EXCLUDE_DIRS.includes(e))}async function We(e){const r=await c.promises.opendir(e);let t=0;for await(const i of r)if(Me(i.name)&&(t+=1,t>=ge))return ge;return t}function ye(e,r){return{path:e,isDir:null,isLink:null,size:null,mtimeMs:null,extension:null,mimeType:null,linkTarget:null,childCount:null,...r?{error:r}:{}}}async function Ue(e,r,t){const i=r==null?"":String(r);let o=i.trim()?Y(e,i):null;if(!o&&i.startsWith("/")){const a=i.replace(K,"");a&&(o=Y(e,a))}if(!o)return ye(i,"illegal path");try{const a=await c.promises.lstat(o.absPath),n=a.isDirectory(),m=a.isSymbolicLink(),u={path:i,isDir:n,isLink:m,size:n?null:a.size,mtimeMs:a.mtimeMs,extension:n?null:Oe(i),mimeType:n?null:ke(i)||"application/octet-stream",linkTarget:null,childCount:null};if(m)try{u.linkTarget=await c.promises.readlink(o.absPath)}catch(g){f(t,"WARN","Readlink failed, linkTarget set to null",{filePath:i,error:g.message})}if(n)try{u.childCount=await We(o.absPath)}catch(g){f(t,"WARN","Count directory entries failed, childCount set to null",{filePath:i,error:g.message})}return u}catch(a){return ye(i,a.code||a.message)}}async function je(e,r,t,i,o=null,a=null){const n=Date.now(),m=`computer:${e}:${r}`;if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!r)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});if(!Array.isArray(t)||t.length===0)throw new E("filePaths \u5FC5\u987B\u4E3A\u975E\u7A7A\u6570\u7EC4",{field:"filePaths"});const u=Number.parseInt(a,10),g=Number.isFinite(u)&&u>0?Math.min(u,ze):Ae;if(t.length>g)throw new E(`filePaths \u5355\u6B21\u6700\u591A ${g} \u6761`,{field:"filePaths",count:t.length});const y=String(e),l=String(r),w=(i==null?"":String(i).trim())||null,d=w||O(o,y,l);if(!c.existsSync(d))return f(m,"INFO","Target directory does not exist, returning empty metas",{targetDir:d,userId:y,cId:l}),{metas:[]};const p=await $e(t,Te,S=>Ue(d,S,m));return f(m,"INFO","File metadata queried",{targetDir:d,count:p.length,failed:p.filter(S=>S.error).length,customTargetDir:w,userId:y,cId:l,elapsedMs:Date.now()-n}),{metas:p}}async function Ee(e,r,t,i,o=""){const a=await c.promises.readdir(h.join(e,o),{withFileTypes:!0});let n=0;for(const m of a){const u=o?h.join(o,m.name):m.name,g=u.split(h.sep).filter(Boolean);if(g.some(w=>w.startsWith(".")))continue;const y=g[g.length-1];if(r.includes(y)||g.some(w=>t.includes(w)))continue;const l=h.join(e,u);let s;try{s=await c.promises.lstat(l)}catch(w){f(i,"WARN","Error occurred when getting file stats, skipping",{filePath:u.replace(/\\/g,"/"),error:w.message});continue}s.isSymbolicLink()||s.nlink>1||(s.isDirectory()?n+=await Ee(e,r,t,i,u):s.isFile()&&(n+=s.size))}return n}async function Be(e,r,t,i,o,a,n,m,u,g=null){const y=Date.now(),l=`computer:${e}:${r}`,s=!(a===!1||a==="false");if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!r)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const w=m==null?"":String(m).trim().toLowerCase();let d=null;if(w&&w!=="all")if(w==="file")d="file";else if(w==="dir"||w==="directory")d="dir";else throw new E("type \u4EC5\u652F\u6301 file/dir/all",{field:"type",value:m});const p=D=>/^\d+$/.test(String(D))?parseInt(String(D),10):NaN;let S=null;if(u!=null&&u!==""){const D=p(u);if(!Number.isInteger(D)||D<0)throw new E("limit \u5FC5\u987B\u4E3A\u975E\u8D1F\u6574\u6570",{field:"limit",value:u});S=D}let F=null;if(!s&&n!=null&&n!==""){const D=p(n);if(!Number.isInteger(D)||D<1||D>10)throw new E("depth \u9700\u4E3A 1-10 \u7684\u6574\u6570",{field:"depth",value:n});F=D}const R=String(e),b=String(r),v=i&&i.trim()?i.trim():null,I=v||O(g,R,b);if(!c.existsSync(I))return f(l,"INFO","Directory does not exist, returning empty list",{targetDir:I,userId:R,cId:b}),{files:[],recursive:s,depth:F,type:d||"all",limit:S};const k=we(I,o);if(!c.existsSync(k))return f(l,"INFO","List path does not exist, returning empty list",{targetDir:I,listDir:k,relativePath:o||"",userId:R,cId:b}),{files:[],recursive:s,depth:F,type:d||"all",limit:S};if(!(await c.promises.stat(k)).isDirectory())throw new E("relativePath \u5FC5\u987B\u662F\u76EE\u5F55",{field:"relativePath",relativePath:o});f(l,"DEBUG","Start getting user file list",{targetDir:I,listDir:k,relativePath:o||"",recursive:s,userId:R,cId:b});try{const D={typeFilter:d,limitState:{limit:S,pushed:0},levelsLeft:!s&&F!=null?F:1},Q=s?(await he(k,l,t,v,me(I,k),{...D,rootDir:I})).entries:await pe(k,I,l,t,v,D);return f(l,"INFO","User file list obtained successfully",{fileCount:Q.length,targetDir:I,listDir:k,relativePath:o||"",recursive:s,depth:F,type:d||"all",limit:S,userId:R,cId:b,elapsedMs:Date.now()-y}),{files:Q,recursive:s,depth:F,type:d||"all",limit:S}}catch(D){throw D instanceof E?D:(f(l,"ERROR","Failed to get user file list",{targetDir:I,listDir:k,relativePath:o||"",recursive:s,userId:R,cId:b,error:D.message,elapsedMs:Date.now()-y}),new T(`Failed to get file list: ${D.message}`,{targetDir:I,originalError:D.message}))}}const Xe=5e3;function ae(e,r){const t=Number.parseInt(e,10);if(!Number.isFinite(t)||t<=0)throw new E(`${r} \u5FC5\u987B\u4E3A\u6B63\u6574\u6570`,{field:r,value:e});return t}function Se(e,r,t){return t?r.toLowerCase().includes(t)?!0:e.toLowerCase().includes(t):!1}const Ve=new Set([".git",".svn",".hg","node_modules","__pycache__",".venv","venv","virtualenv",".mypy_cache",".pytest_cache",".ruff_cache",".cache",".tmp"]),Ge=new Set([".DS_Store","Thumbs.db","desktop.ini"]);function Re(e,r,t){return!!(Ge.has(e.name)||r.includes(e.name)||e.isDirectory()&&(Ve.has(e.name)||t.includes(e.name)))}function Ie(e,r,t,i){for(let o=r;o<e.length;o++)if(!Re(e[o],t,i))return!0;return!1}function Fe(e,r){return Date.now()-e>=r}async function He(e,r,t,i,o,a,n,m,u,g,y=null){const l=Date.now(),s=`computer:${e}:${r}`;if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!r)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const w=(i==null?"":String(i)).trim();if(!w)throw new E("kw \u4E0D\u80FD\u4E3A\u7A7A",{field:"kw"});const d=ae(m,"limit"),p=ae(u,"maxVisit"),S=ae(g,"timeoutMs"),F=w.toLowerCase(),R=(()=>{const N=n==null?"":String(n).trim().toLowerCase();return N==="file"?"file":N==="dir"||N==="directory"?"dir":null})(),b=N=>R==null||R==="dir"===N,v=String(e),I=String(r),k=o&&o.trim()?o.trim():null,C=k||O(y,v,I);if(!c.existsSync(C))return{files:[],truncated:!1,visited:0};const D=we(C,a);if(!c.existsSync(D))return{files:[],truncated:!1,visited:0};if(!(await c.promises.stat(D)).isDirectory())throw new E("relativePath \u5FC5\u987B\u662F\u76EE\u5F55",{field:"relativePath",relativePath:a});const ve=H(h.relative(C,D)),ee=_.CONTENT_TRAVERSE_EXCLUDE_FILES||[],te=_.TRAVERSE_EXCLUDE_DIRS||[],P=[],re=new Set;let $=0,ie=!1,A=!1;const z=[ve],U=()=>$>=p||Fe(l,S);for(;z.length>0;){if(U()){A=!0;break}if(P.length>=d){z.length>0&&(A=!0);break}const N=z.shift();if(re.has(N))continue;re.add(N);const j=N?h.join(C,N):C;let M;try{M=await c.promises.readdir(j,{withFileTypes:!0})}catch(x){f(s,"WARN","search readdir failed",{dirRel:N,error:x.message});continue}M.sort((x,L)=>x.isDirectory()&&!L.isDirectory()?-1:!x.isDirectory()&&L.isDirectory()?1:x.name.toLowerCase().localeCompare(L.name.toLowerCase()));const ne=[];let X=!1,V=!1;for(let x=0;x<M.length;x++){const L=M[x];if(Re(L,ee,te))continue;if(U()){A=!0,X=!0,V=!0;break}$+=1;const ce=h.join(j,L.name),W=H(h.relative(C,ce));if(L.isDirectory()){if(ne.push(W),b(!0)&&P.length<d&&Se(W,L.name,F)&&(P.push({name:W,isDir:!0}),P.length>=d)){X=!0,V=Ie(M,x+1,ee,te);break}continue}try{if(b(!1)&&P.length<d&&Se(W,L.name,F)&&(P.push({name:W,isDir:!1,fileProxyUrl:J(t,W,k),isLink:L.isSymbolicLink()}),P.length>=d)){X=!0,V=Ie(M,x+1,ee,te);break}}catch(be){f(s,"WARN",`search \u5904\u7406\u6587\u4EF6\u5931\u8D25: ${ce}`,{error:be.message})}}if(X||U()){(U()||V||ne.length>0||z.length>0)&&(A=!0);continue}for(const x of ne){if(U()){A=!0;break}if(z.length>=Xe){A=!0;break}if($+z.length>=p){A=!0;break}re.has(x)||z.push(x)}}return P.sort((N,j)=>N.isDir!==j.isDir?N.isDir?-1:1:String(N.name).localeCompare(String(j.name))),ie=A||z.length>0||$>=p||Fe(l,S),f(s,"INFO","File search completed",{kw:w,matchCount:P.length,visited:$,truncated:ie,relativePath:a||"",elapsedMs:Date.now()-l}),{files:P,truncated:ie,visited:$}}async function qe(e,r,t,i,o=null){const a=Date.now(),n=`computer:${e}:${r}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!Array.isArray(t))throw new E("files must be an array",{field:"files"});const m=String(e),u=String(r),g=i&&i.trim()?i.trim():null,y=g||O(o,m,u);c.existsSync(y)||c.mkdirSync(y,{recursive:!0});for(let l=0;l<t.length;l++){const s=t[l];if(!s||typeof s.operation!="string")throw new E(`files[${l}].operation cannot be empty`,{field:`files[${l}].operation`});if(!s.name||typeof s.name!="string")throw new E(`files[${l}].name cannot be empty`,{field:`files[${l}].name`});const w=s.operation.toLowerCase();if(!["create","delete","rename","modify"].includes(w))throw new E(`files[${l}].operation must be one of create, delete, rename or modify`,{field:`files[${l}].operation`});if(w==="rename"&&!s.renameFrom)throw new E(`files[${l}].renameFrom cannot be empty (rename operation requires)`,{field:`files[${l}].renameFrom`});if(w==="modify"&&s.isDir!==!0&&typeof s.contents!="string")throw new E(`files[${l}].contents must be a string (modify operation requires)`,{field:`files[${l}].contents`})}f(n,"DEBUG","Start updating user files",{userId:m,cId:u,filesCount:t.length});try{for(const l of t){const s=l.operation.toLowerCase(),w=l.name,d=h.normalize(w).replace(/^[\/\\]+/,""),p=h.join(y,d),S=h.resolve(p),F=h.resolve(y);if(!S.startsWith(F+h.sep)&&S!==F){f(n,"WARN","File path is not secure, skipping",{filePath:d,resolvedPath:S});continue}switch(s){case"create":{if(l.isDir===!0){if(c.existsSync(p)){if((await c.promises.stat(p)).isFile())throw new E("Cannot create directory, file with the same name already exists",{filePath:d});f(n,"INFO","Directory already exists, skipping creation",{filePath:d});break}await c.promises.mkdir(p,{recursive:!0}),f(n,"INFO","Directory created successfully",{filePath:d});break}if(c.existsSync(p)){if((await c.promises.stat(p)).isDirectory())throw new E("Cannot create file, directory with the same name already exists",{filePath:d});f(n,"INFO","File already exists, skipping creation",{filePath:d});break}await c.promises.mkdir(h.dirname(p),{recursive:!0});const R=l.contents||"";await c.promises.writeFile(p,R,"utf8"),f(n,"INFO","File created successfully",{filePath:d});break}case"delete":{c.existsSync(p)?(await c.promises.stat(p)).isDirectory()?(await c.promises.rm(p,{recursive:!0,force:!0}),f(n,"INFO","Directory deleted successfully",{filePath:d})):(await c.promises.unlink(p),f(n,"INFO","File deleted successfully",{filePath:d})):f(n,"WARN","The file or directory to be deleted does not exist",{filePath:d});break}case"rename":{const R=l.renameFrom;if(!R||typeof R!="string"){f(n,"WARN","Rename operation missing renameFrom",{filePath:d});break}const b=h.normalize(R).replace(/^[\/\\]+/,""),v=h.join(y,b),I=h.resolve(v);if(!I.startsWith(F+h.sep)&&I!==F){f(n,"WARN","Source path is not secure, skipping rename",{sourcePath:b,targetPath:d});break}if(c.existsSync(v)){const C=(await c.promises.stat(v)).isDirectory();await c.promises.mkdir(h.dirname(p),{recursive:!0}),await c.promises.rename(v,p),f(n,"INFO",C?"Directory renamed successfully":"File renamed successfully",{sourcePath:b,targetPath:d})}else f(n,"WARN","The file or directory to be renamed does not exist",{sourcePath:b});break}case"modify":{if(!c.existsSync(p)){f(n,"WARN","The file to be modified does not exist",{filePath:d});break}if((await c.promises.stat(p)).isDirectory()){f(n,"INFO","The target is a directory, skipping modification",{filePath:d});break}const b=typeof l.contents=="string"?l.contents:"";if(await c.promises.readFile(p,"utf8")===b){f(n,"INFO","File content has no changes, skipping write",{filePath:d});break}await c.promises.writeFile(p,b,"utf8"),f(n,"INFO","File modified successfully",{filePath:d});break}default:{f(n,"WARN","Unsupported operation type",{operation:s,filePath:d});break}}}return f(n,"INFO","User files updated successfully",{userId:m,cId:u,filesCount:t.length,elapsedMs:Date.now()-a}),{success:!0,message:"User files updated successfully",userId:m,cId:u,filesCount:t.length}}catch(l){throw f(n,"ERROR","User files updated failed",{userId:m,cId:u,error:l.message,elapsedMs:Date.now()-a}),new T(`User files updated failed: ${l.message}`,{userId:m,cId:u,originalError:l.message})}}async function le(e,r,t,i,o,a=null){const n=Date.now(),m=`computer:${e}:${r}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!t)throw new E("file cannot be empty",{field:"file"});if(!i||typeof i!="string")throw new E("filePath cannot be empty",{field:"filePath"});const u=String(e),g=String(r),y=o&&o.trim()?o.trim():null,l=y||O(a,u,g);c.existsSync(l)||c.mkdirSync(l,{recursive:!0});const s=h.normalize(i).replace(/^[\/\\]+/,""),w=h.join(l,s),d=h.resolve(w),p=h.resolve(l);if(!d.startsWith(p+h.sep)&&d!==p)throw new E("File path is not secure, cannot exceed user directory",{field:"filePath",providedPath:i,resolvedPath:d});try{if(await c.promises.mkdir(h.dirname(w),{recursive:!0}),t.buffer)await c.promises.writeFile(w,t.buffer);else if(typeof t.contents=="string")await c.promises.writeFile(w,t.contents,"utf8");else throw new E("File content format is incorrect",{field:"file",hasBuffer:!!t.buffer,hasContents:typeof t.contents});return f(m,"INFO","File uploaded successfully",{userId:u,cId:g,filePath:s,targetPath:d,fileSize:t.buffer?t.buffer.length:t.contents?Buffer.byteLength(t.contents,"utf8"):0,elapsedMs:Date.now()-n}),{success:!0,message:"File uploaded successfully",fileSize:t.buffer?t.buffer.length:t.contents?Buffer.byteLength(t.contents,"utf8"):0}}catch(S){throw f(m,"ERROR","File upload failed",{userId:u,cId:g,filePath:s,error:S.message,elapsedMs:Date.now()-n}),new T(`File upload failed: ${S.message}`,{userId:u,cId:g,filePath:s,originalError:S.message})}}async function Ze(e,r,t,i,o,a=null){const n=Date.now(),m=`computer:${e}:${r}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!Array.isArray(t))throw new E("files must be an array",{field:"files"});if(!Array.isArray(i))throw new E("filePaths must be an array",{field:"filePaths"});if(t.length!==i.length)throw new E(`File count (${t.length}) does not match path count (${i.length})`,{field:"filePaths"});f(m,"DEBUG","Start batch uploading files",{userId:e,cId:r,filesCount:t.length});const u=[];try{for(let l=0;l<t.length;l++){const s=t[l],w=i[l];if(!s){f(m,"WARN","Empty file object encountered in batch upload, skipping",{index:l,filePath:w}),u.push({success:!1,filePath:w,error:"Empty file object"});continue}if(!w||typeof w!="string"){f(m,"WARN","Invalid file path in batch upload, skipping",{index:l,originalname:s.originalname}),u.push({success:!1,filePath:w||"",originalname:s.originalname,error:"Invalid file path"});continue}try{const d=await le(e,r,s,w,o,a);u.push({success:!0,filePath:w,originalname:s.originalname,...d})}catch(d){f(m,"ERROR","Single file upload failed in batch upload",{filePath:w,originalname:s.originalname,error:d.message}),u.push({success:!1,filePath:w,originalname:s.originalname,error:d.message})}}const g=u.filter(l=>l.success).length,y=u.filter(l=>!l.success).length;return f(m,"INFO","Batch upload files completed",{userId:e,cId:r,totalCount:t.length,successCount:g,failCount:y,elapsedMs:Date.now()-n}),{success:!0,message:"Batch upload completed",totalCount:t.length,successCount:g,failCount:y,results:u}}catch(g){throw f(m,"ERROR","Batch upload files failed",{userId:e,cId:r,error:g.message,elapsedMs:Date.now()-n}),new T(`Batch upload files failed: ${g.message}`,{userId:e,cId:r,originalError:g.message})}}async function Ke(e,r,t,i=null){const o=Date.now(),a=`computer:${e}:${r}`,n=fe(i);if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!n&&!i?.workspacePath)throw new T("COMPUTER_WORKSPACE_DIR is not configured, cannot create zip");const m=String(e),u=String(r),g=t&&t.trim()?t.trim():null,y=g||O(i,m,u);if(!c.existsSync(y)){const S=`${m}_${u}.zip`;f(a,"WARN","Workspace directory does not exist, returning empty zip",{targetDir:y,userId:m,cId:u,zipFileName:S});const F=ue("zip",{zlib:{level:9}});return F.append(null,{name:`${m}_${u}/`,type:"directory"}),F.on("warning",R=>{if(R.code==="ENOENT")f(a,"WARN","Encountered file problem when creating empty zip",{message:R.message,code:R.code});else throw f(a,"ERROR","Encountered warning when creating empty zip",{message:R.message,code:R.code}),R}),F.on("error",R=>{f(a,"ERROR","Failed to create empty zip",{message:R.message})}),{archive:F,zipFileName:S}}const l=`${m}_${u}.zip`;f(a,"DEBUG","Start creating workspace directory zip",{targetDir:y,zipFileName:l});const s=ue("zip",{zlib:{level:9}}),w=_.CONTENT_TRAVERSE_EXCLUDE_FILES||[],d=_.TRAVERSE_EXCLUDE_DIRS||[],p=await Ee(y,w,d,a);if(p>G){const S=G/1024/1024,F=(p/1024/1024).toFixed(2);throw f(a,"WARN","Download rejected due to oversized workspace",{targetDir:y,downloadableSize:p,maxSizeBytes:G}),new E(`Download failed: total file size ${F}MB exceeds limit ${S}MB`,{field:"downloadSize",downloadableSize:p,maxSizeBytes:G})}return s.directory(y,`${m}_${u}`,S=>{const F=S.name||"",R=F.split(/[\/\\]/).filter(Boolean);if(R.some(v=>v.startsWith(".")))return!1;const b=R[R.length-1];if(w.includes(b)||R.some(v=>d.includes(v)))return!1;try{const v=h.join(y,F),I=c.lstatSync(v);if(I.isSymbolicLink()||I.nlink>1)return!1}catch(v){return f(a,"WARN","Error occurred when detecting link file, skipping",{filePath:F,error:v.message}),!1}return S}),s.on("warning",S=>{if(S.code==="ENOENT")f(a,"WARN","Encountered file problem when creating zip",{message:S.message,code:S.code});else throw f(a,"ERROR","Encountered warning when creating zip",{message:S.message,code:S.code}),S}),s.on("error",S=>{f(a,"ERROR","Failed to create zip",{message:S.message,elapsedMs:Date.now()-o})}),s.on("end",()=>{f(a,"INFO","Workspace directory zip created successfully",{targetDir:y,zipFileName:l,elapsedMs:Date.now()-o})}),{archive:s,zipFileName:l}}async function Ye(e,r,t=200,i=null){const o=Date.now(),a=`computer:${e}:${r}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});const n=String(e),m=String(r),u=_e(i,n,m);if(!u)return f(a,"WARN","COMPUTER_WORKSPACE_DIR is not configured"),{success:!0,message:"Log workspace is not configured",logs:[],totalLines:0,startIndex:1,logFileName:null};if(!c.existsSync(u))return f(a,"DEBUG","Log directory does not exist",{logDir:u}),{success:!0,message:"Log directory does not exist",logs:[],totalLines:0,startIndex:1,logFileName:null};const y=(await c.promises.readdir(u,{withFileTypes:!0})).filter(I=>I.isFile()).map(I=>I.name);if(y.length===0)return f(a,"DEBUG","No log file found",{logDir:u}),{success:!0,message:"No log file found",logs:[],totalLines:0,startIndex:1,logFileName:null};const l=[];for(const I of y){const k=h.join(u,I),C=await c.promises.stat(k);l.push({name:I,fullPath:k,mtime:C.mtimeMs,size:C.size})}l.sort((I,k)=>k.mtime-I.mtime);const s=l[0],d=(await c.promises.readFile(s.fullPath,"utf8")).split(`
2
- `).filter(I=>I.length>0),p=d.length,S=Math.max(1,t),F=Math.max(0,p-S),R=F+1,v=d.slice(F).map((I,k)=>({line:R+k,content:I}));return f(a,"DEBUG","Get latest logs",{fileName:s.name,fileSize:s.size,totalLines:p,returnedLines:v.length,elapsedMs:Date.now()-o}),{success:!0,message:"Get log successfully",logs:v,totalLines:p,startIndex:R,logFileName:s.name}}async function De(e,r){try{const i=(await c.promises.readdir(e,{withFileTypes:!0})).filter(m=>!m.name.startsWith(".")&&m.name!=="node_modules");if(i.length!==1||!i[0].isDirectory())return;const o=h.join(e,i[0].name),a=h.join(e,`..tmp_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{await c.promises.rename(o,a)}catch(m){if(m.code==="EXDEV")await c.promises.cp(o,a,{recursive:!0}),await c.promises.rm(o,{recursive:!0,force:!0});else throw m}const n=await c.promises.readdir(a);for(const m of n){const u=h.join(a,m),g=h.join(e,m);try{await c.promises.rename(u,g)}catch(y){if(y.code==="EXDEV")(await c.promises.lstat(u)).isDirectory()?await Ce(u,g):(await c.promises.copyFile(u,g),await c.promises.unlink(u));else throw y}}await c.promises.rm(a,{recursive:!0,force:!0}),f(r,"INFO","Removed single top-level directory from imported zip",{removedDir:i[0].name})}catch(t){f(r,"WARN","Failed to remove top-level directory from imported zip, continuing",{error:t.message})}}async function Je(e,r){if(!c.existsSync(e)){await c.promises.mkdir(e,{recursive:!0});return}const t=await c.promises.readdir(e,{withFileTypes:!0});for(const i of t){if(B.has(i.name))continue;const o=h.join(e,i.name);await c.promises.rm(o,{recursive:!0,force:!0}),f(r,"DEBUG","Removed workspace entry during import",{name:i.name})}}async function Qe(e,r,t){if(await c.promises.mkdir(r,{recursive:!0}),!c.existsSync(e)){await c.promises.mkdir(e,{recursive:!0});return}const i=await c.promises.readdir(e,{withFileTypes:!0});for(const o of i){if(B.has(o.name))continue;const a=h.join(e,o.name),n=h.join(r,o.name);await se(a,n),f(t,"DEBUG","Backed up workspace entry before import",{name:o.name})}}async function et(e,r,t){if(!c.existsSync(e))return;const i=await c.promises.readdir(e,{withFileTypes:!0});for(const o of i){if(B.has(o.name))continue;const a=h.join(e,o.name),n=h.join(r,o.name);await se(a,n)}f(t,"INFO","Workspace restored from backup after import failure")}async function tt(e,r,t){const i=await c.promises.readdir(e,{withFileTypes:!0}),o=[];try{for(const a of i){if(B.has(a.name)){f(t,"INFO","Skipping preserved entry from imported zip",{name:a.name});continue}const n=h.join(e,a.name),m=h.join(r,a.name);await se(n,m),o.push(a.name)}}catch(a){throw a.mergedEntries=o,a}}async function rt(e,r,t,i,o=null){const a=Date.now(),n=`computer:${e}:${r}`,m=fe(o);if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!t||!t.path)throw new E("file is required",{field:"file"});if(!m&&!o?.workspacePath)throw new T("COMPUTER_WORKSPACE_DIR is not configured");if(h.extname(t.originalname||t.filename||"").toLowerCase()!==".zip")throw new E("Only zip files are supported",{field:"file",originalName:t.originalname});const g=String(e),y=String(r),l=i&&i.trim()?i.trim():O(o,g,y),s=h.join(l,".tmp"),w=h.join(s,`import_project_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);let d=null;f(n,"INFO","Start importing project from zip",{userId:g,cId:y,targetDir:l,fileName:t.originalname});try{await c.promises.mkdir(l,{recursive:!0}),await c.promises.mkdir(s,{recursive:!0}),await c.promises.mkdir(w,{recursive:!0}),await xe(t.path,w),await De(w,n),d=h.join(s,`import_backup_${Date.now()}_${Math.round(Math.random()*1e6)}`),await Qe(l,d,n);try{await tt(w,l,n)}catch(p){throw f(n,"ERROR","Merge imported project failed, restoring workspace",{error:p.message,mergedEntries:p.mergedEntries||[]}),await Je(l,n),await et(d,l,n),p}return c.existsSync(d)&&(await c.promises.rm(d,{recursive:!0,force:!0}),d=null),f(n,"INFO","Project imported successfully",{userId:g,cId:y,targetDir:l,elapsedMs:Date.now()-a}),{success:!0,message:"Project imported successfully",userId:g,cId:y,targetDir:l}}catch(p){throw f(n,"ERROR","Failed to import project",{userId:g,cId:y,targetDir:l,error:p.message,elapsedMs:Date.now()-a}),p instanceof E||p instanceof T||p instanceof Ne?p:new T(`Failed to import project: ${p.message}`,{userId:g,cId:y,originalError:p.message})}finally{if(c.existsSync(w))try{await c.promises.rm(w,{recursive:!0,force:!0})}catch(p){f(n,"WARN","Failed to clean import extract directory",{extractRoot:w,error:p.message})}if(d&&c.existsSync(d))try{await c.promises.rm(d,{recursive:!0,force:!0})}catch(p){f(n,"WARN","Failed to clean import backup directory",{backupDir:d,error:p.message})}if(t?.path&&c.existsSync(t.path))try{await c.promises.unlink(t.path)}catch(p){f(n,"WARN","Failed to clean uploaded zip file",{tempZipPath:t.path,error:p.message})}}}async function it(e,r,t,i,o,a=null){const n=`computer:${e}:${r}`;if(!t||typeof t!="string"||!t.trim())throw new E("fileName cannot be empty",{field:"fileName"});if(i!=null&&typeof i!="string")throw new E("content must be a string",{field:"content"});const m=t.trim(),u=i??"";return f(n,"INFO","Generate file from text content",{userId:e,cId:r,fileName:m,contentLength:u.length}),{...await le(e,r,{contents:u,originalname:h.basename(m),size:Buffer.byteLength(u,"utf8")},m,o,a),message:"File generated successfully",fileName:m}}export{Be as getFileList,je as getFileMeta,Le as resolveExistingFile,He as searchFiles,qe as updateFiles,le as uploadFile,Ze as uploadFiles,it as generateFile,Ke as downloadAllFiles,Ye as getLatestLogs,rt as importProject,B as IMPORT_PROJECT_PRESERVED_ENTRIES,De as removeTopLevelDir};
1
+ import c from"fs";import h from"path";import fe from"archiver";import{lookup as Ce}from"mime-types";import P from"../../appConfig/index.js";import{log as f}from"../log/logUtils.js";import{ValidationError as E,SystemError as z,FileError as Pe}from"../error/errorHandler.js";import{extractZip as _e}from"../common/zipUtils.js";import{moveDirectory as Le,movePath as oe}from"../common/fileSystemUtils.js";import{resolveLogDir as Ae,resolveWorkspaceDir as O,resolveWorkspaceRoot as me}from"./workspaceContext.js";const B=new Set([".git",".agents",".claude",".codex",".opencode",".tmp",".logs"]),Te=100*1024*1024,H=P.DOWNLOAD_MAX_FILE_SIZE_BYTES||Te;function q(e){return h.sep==="\\"?e.replace(/\\/g,"/"):e}function de(e,r){return q(h.relative(e,r))}function pe(e,r){return e?`${e}/${r}`:r}function Z(e){return e!=null&&e.limit!=null&&e.pushed>=e.limit}function K(e){e!=null&&(e.pushed+=1)}async function he(e,r,t,i,o,a={}){const{typeFilter:s=null,limitState:d=null,levelsLeft:m=1}=a,w=[],g=await c.promises.readdir(e,{withFileTypes:!0});g.sort((l,y)=>l.isDirectory()&&!y.isDirectory()?-1:!l.isDirectory()&&y.isDirectory()?1:l.name.toLowerCase().localeCompare(y.name.toLowerCase()));const n=de(r,e);for(const l of g){if(Z(d))break;if(l.name.startsWith(".")&&l.name!==".gitignore"||(P.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(l.name)||l.isDirectory()&&Ee(l.name))continue;const u=pe(n,l.name);if(l.isDirectory()){if(s!=="file"&&(w.push({name:u,isDir:!0}),K(d)),m>1&&!Z(d))try{const p=await he(h.join(e,l.name),r,t,i,o,{typeFilter:s,limitState:d,levelsLeft:m-1});w.push(...p)}catch(p){f(t,"WARN",`Depth descent failed, skip subtree: ${u}`,{error:p.message})}continue}if(s!=="dir"&&!(l.isSymbolicLink()&&await ge(r,h.join(e,l.name))))try{w.push({name:u,isDir:!1,fileProxyUrl:Q(i,u,o),isLink:l.isSymbolicLink()}),K(d)}catch(p){f(t,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${e}/${l.name}`,{error:p.message})}}return w}async function we(e,r,t,i,o="",a={}){const{typeFilter:s=null,limitState:d=null}=a,m=a.rootDir||e,w=[],g=await c.promises.readdir(e,{withFileTypes:!0});g.sort((n,l)=>n.isDirectory()&&!l.isDirectory()?-1:!n.isDirectory()&&l.isDirectory()?1:n.name.toLowerCase().localeCompare(l.name.toLowerCase()));for(const n of g){if(Z(d))break;const l=h.join(e,n.name);if(n.name.startsWith(".")&&n.name!==".gitignore"||(P.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(n.name)||n.isDirectory()&&Ee(n.name)||n.isSymbolicLink()&&await ge(m,l))continue;const u=pe(o,n.name);if(n.isDirectory()){s!=="file"&&!Z(d)&&(w.push({name:u,isDir:!0}),K(d));try{const p=await we(l,r,t,i,u,a);w.push(...p.entries)}catch(p){f(r,"WARN",`Depth descent failed, skip subtree: ${u}`,{error:p.message})}continue}if(s!=="dir")try{w.push({name:u,isDir:!1,fileProxyUrl:Q(t,u,i),isLink:n.isSymbolicLink()}),K(d)}catch(p){f(r,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${l}`,{error:p.message})}}return{entries:w}}const Y=process.platform==="win32"?/^[\/\\]+/:/^\/+/;async function X(e,r){try{const t=await c.promises.realpath(e),i=await c.promises.realpath(r);return i!==t&&!i.startsWith(t+h.sep)}catch{return!1}}async function ge(e,r){try{const t=await c.promises.realpath(e),i=await c.promises.realpath(r);return i!==t&&!i.startsWith(t+h.sep)}catch{return!0}}const ye=process.platform==="win32"?new Set((P.TRAVERSE_EXCLUDE_DIRS||[]).map(e=>String(e).toLowerCase())):new Set(P.TRAVERSE_EXCLUDE_DIRS||[]);function Ee(e){return process.platform==="win32"?ye.has(e.toLowerCase()):ye.has(e)}function Se(e,r){const t=r==null?"":String(r),i=t.trim();if(!i||i==="."||i==="/")return h.resolve(e);const o=h.normalize(t).replace(Y,"");if(!o||o===".")return h.resolve(e);if(h.isAbsolute(t)||o.split(h.sep).includes(".."))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:r});const a=h.resolve(e),s=h.resolve(e,o);if(s!==a&&!s.startsWith(a+h.sep))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:r});return s}function J(e,r){const t=r==null?"":String(r);if(!t.trim())return null;const i=h.resolve(e);let o;if(h.isAbsolute(t))o=h.resolve(t);else{const s=h.normalize(t).replace(Y,"");if(!s||s==="."||s.split(h.sep).includes(".."))return null;o=h.resolve(e,s)}if(o!==i&&!o.startsWith(i+h.sep))return null;const a=q(h.relative(i,o));return!a||a.startsWith("..")?null:{absPath:o,name:a}}function Q(e,r,t){if(!e||!r)return null;const i=r.split("/").map(a=>encodeURIComponent(a)).join("/");let o=`${e}/${i}`;return t&&(o+=`?customTargetDir=${encodeURIComponent(t)}`),o}async function ze(e,r,t,i,o,a=null){if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!r)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});if(!t||!String(t).trim())throw new E("filePath \u4E0D\u80FD\u4E3A\u7A7A",{field:"filePath"});const s=String(e),d=String(r),m=o&&o.trim()?o.trim():null,w=m||O(a,s,d);if(!c.existsSync(w))return{exists:!1};let g=J(w,t);if(!g&&String(t).startsWith("/")){const n=String(t).replace(Y,"");n&&(g=J(w,n))}if(!g)return{exists:!1};try{if(await X(w,g.absPath))return{exists:!1};if(!(await c.promises.stat(g.absPath)).isFile())return{exists:!1}}catch{return{exists:!1}}return{exists:!0,name:g.name,fileProxyUrl:Q(i,g.name,m)}}const Oe=100,$e=1e3,Me=8,Re=1e3;function We(e){return h.extname(e).replace(/^\./,"").toLowerCase()||""}async function Ue(e,r,t){const i=new Array(e.length),o=Math.max(1,Math.min(r,e.length));let a=0;return await Promise.all(Array.from({length:o},async()=>{for(;;){const s=a++;if(s>=e.length)return;i[s]=await t(e[s],s)}})),i}function je(e){return!(e.startsWith(".")&&e!==".gitignore"||(P.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(e)||P.TRAVERSE_EXCLUDE_DIRS.includes(e))}async function Be(e){const r=await c.promises.opendir(e);let t=0;for await(const i of r)if(je(i.name)&&(t+=1,t>=Re))return Re;return t}function ae(e,r){return{path:e,isDir:null,isLink:null,size:null,mtimeMs:null,extension:null,mimeType:null,linkTarget:null,childCount:null,...r?{error:r}:{}}}async function Xe(e,r,t){const i=r==null?"":String(r);let o=i.trim()?J(e,i):null;if(!o&&i.startsWith("/")){const a=i.replace(Y,"");a&&(o=J(e,a))}if(!o||await X(e,o.absPath))return ae(i,"illegal path");try{const a=await c.promises.lstat(o.absPath),s=a.isDirectory(),d=a.isSymbolicLink(),m={path:i,isDir:s,isLink:d,size:s?null:a.size,mtimeMs:a.mtimeMs,extension:s?null:We(i),mimeType:s?null:Ce(i)||"application/octet-stream",linkTarget:null,childCount:null};if(d)try{m.linkTarget=await c.promises.readlink(o.absPath)}catch(w){f(t,"WARN","Readlink failed, linkTarget set to null",{filePath:i,error:w.message})}if(s)try{m.childCount=await Be(o.absPath)}catch(w){f(t,"WARN","Count directory entries failed, childCount set to null",{filePath:i,error:w.message})}return m}catch(a){return ae(i,a.code||a.message)}}async function Ve(e,r,t,i,o=null,a=null){const s=Date.now(),d=`computer:${e}:${r}`;if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!r)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});if(!Array.isArray(t)||t.length===0)throw new E("filePaths \u5FC5\u987B\u4E3A\u975E\u7A7A\u6570\u7EC4",{field:"filePaths"});const m=Number.parseInt(a,10),w=Number.isFinite(m)&&m>0?Math.min(m,$e):Oe;if(t.length>w)throw new E(`filePaths \u5355\u6B21\u6700\u591A ${w} \u6761`,{field:"filePaths",count:t.length});const g=String(e),n=String(r),y=(i==null?"":String(i).trim())||null,u=y||O(o,g,n);if(!c.existsSync(u))return f(d,"INFO","Target directory does not exist, returning empty metas",{targetDir:u,userId:g,cId:n}),{metas:[]};const p=await Ue(t,Me,S=>Xe(u,S,d));return f(d,"INFO","File metadata queried",{targetDir:u,count:p.length,failed:p.filter(S=>S.error).length,customTargetDir:y,userId:g,cId:n,elapsedMs:Date.now()-s}),{metas:p}}async function Ie(e,r,t,i,o=""){const a=await c.promises.readdir(h.join(e,o),{withFileTypes:!0});let s=0;for(const d of a){const m=o?h.join(o,d.name):d.name,w=m.split(h.sep).filter(Boolean);if(w.some(y=>y.startsWith(".")))continue;const g=w[w.length-1];if(r.includes(g)||w.some(y=>t.includes(y)))continue;const n=h.join(e,m);let l;try{l=await c.promises.lstat(n)}catch(y){f(i,"WARN","Error occurred when getting file stats, skipping",{filePath:m.replace(/\\/g,"/"),error:y.message});continue}l.isSymbolicLink()||l.nlink>1||(l.isDirectory()?s+=await Ie(e,r,t,i,m):l.isFile()&&(s+=l.size))}return s}async function Ge(e,r,t,i,o,a,s,d,m,w=null){const g=Date.now(),n=`computer:${e}:${r}`,l=!(a===!1||a==="false");if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!r)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const y=d==null?"":String(d).trim().toLowerCase();let u=null;if(y&&y!=="all")if(y==="file")u="file";else if(y==="dir"||y==="directory")u="dir";else throw new E("type \u4EC5\u652F\u6301 file/dir/all",{field:"type",value:d});const p=D=>/^\d+$/.test(String(D))?parseInt(String(D),10):NaN;let S=null;if(m!=null&&m!==""){const D=p(m);if(!Number.isInteger(D)||D<0)throw new E("limit \u5FC5\u987B\u4E3A\u975E\u8D1F\u6574\u6570",{field:"limit",value:m});S=D}let F=null;if(!l&&s!=null&&s!==""){const D=p(s);if(!Number.isInteger(D)||D<1||D>10)throw new E("depth \u9700\u4E3A 1-10 \u7684\u6574\u6570",{field:"depth",value:s});F=D}const R=String(e),b=String(r),v=i&&i.trim()?i.trim():null,I=v||O(w,R,b);if(!c.existsSync(I))return f(n,"INFO","Directory does not exist, returning empty list",{targetDir:I,userId:R,cId:b}),{files:[],recursive:l,depth:F,type:u||"all",limit:S};const k=Se(I,o);if(await X(I,k))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:o});if(!c.existsSync(k))return f(n,"INFO","List path does not exist, returning empty list",{targetDir:I,listDir:k,relativePath:o||"",userId:R,cId:b}),{files:[],recursive:l,depth:F,type:u||"all",limit:S};if(!(await c.promises.stat(k)).isDirectory())throw new E("relativePath \u5FC5\u987B\u662F\u76EE\u5F55",{field:"relativePath",relativePath:o});f(n,"DEBUG","Start getting user file list",{targetDir:I,listDir:k,relativePath:o||"",recursive:l,userId:R,cId:b});try{const D={typeFilter:u,limitState:{limit:S,pushed:0},levelsLeft:!l&&F!=null?F:1},ee=l?(await we(k,n,t,v,de(I,k),{...D,rootDir:I})).entries:await he(k,I,n,t,v,D);return f(n,"INFO","User file list obtained successfully",{fileCount:ee.length,targetDir:I,listDir:k,relativePath:o||"",recursive:l,depth:F,type:u||"all",limit:S,userId:R,cId:b,elapsedMs:Date.now()-g}),{files:ee,recursive:l,depth:F,type:u||"all",limit:S}}catch(D){throw D instanceof E?D:(f(n,"ERROR","Failed to get user file list",{targetDir:I,listDir:k,relativePath:o||"",recursive:l,userId:R,cId:b,error:D.message,elapsedMs:Date.now()-g}),new z(`Failed to get file list: ${D.message}`,{targetDir:I,originalError:D.message}))}}const He=5e3;function le(e,r){const t=Number.parseInt(e,10);if(!Number.isFinite(t)||t<=0)throw new E(`${r} \u5FC5\u987B\u4E3A\u6B63\u6574\u6570`,{field:r,value:e});return t}function Fe(e,r,t){return t?r.toLowerCase().includes(t)?!0:e.toLowerCase().includes(t):!1}const qe=new Set([".git",".svn",".hg","node_modules","__pycache__",".venv","venv","virtualenv",".mypy_cache",".pytest_cache",".ruff_cache",".cache",".tmp"]),Ze=new Set([".DS_Store","Thumbs.db","desktop.ini"]);function De(e,r,t){return!!(Ze.has(e.name)||r.includes(e.name)||e.isDirectory()&&(qe.has(e.name)||t.includes(e.name)))}function ve(e,r,t,i){for(let o=r;o<e.length;o++)if(!De(e[o],t,i))return!0;return!1}function be(e,r){return Date.now()-e>=r}async function Ke(e,r,t,i,o,a,s,d,m,w,g=null){const n=Date.now(),l=`computer:${e}:${r}`;if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!r)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const y=(i==null?"":String(i)).trim();if(!y)throw new E("kw \u4E0D\u80FD\u4E3A\u7A7A",{field:"kw"});const u=le(d,"limit"),p=le(m,"maxVisit"),S=le(w,"timeoutMs"),F=y.toLowerCase(),R=(()=>{const N=s==null?"":String(s).trim().toLowerCase();return N==="file"?"file":N==="dir"||N==="directory"?"dir":null})(),b=N=>R==null||R==="dir"===N,v=String(e),I=String(r),k=o&&o.trim()?o.trim():null,C=k||O(g,v,I);if(!c.existsSync(C))return{files:[],truncated:!1,visited:0};const D=Se(C,a);if(!c.existsSync(D))return{files:[],truncated:!1,visited:0};if(await X(C,D))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:a});if(!(await c.promises.stat(D)).isDirectory())throw new E("relativePath \u5FC5\u987B\u662F\u76EE\u5F55",{field:"relativePath",relativePath:a});const Ne=q(h.relative(C,D)),te=P.CONTENT_TRAVERSE_EXCLUDE_FILES||[],re=P.TRAVERSE_EXCLUDE_DIRS||[],_=[],ie=new Set;let $=0,ne=!1,A=!1;const T=[Ne],U=()=>$>=p||be(n,S);for(;T.length>0;){if(U()){A=!0;break}if(_.length>=u){T.length>0&&(A=!0);break}const N=T.shift();if(ie.has(N))continue;ie.add(N);const j=N?h.join(C,N):C;let M;try{M=await c.promises.readdir(j,{withFileTypes:!0})}catch(x){f(l,"WARN","search readdir failed",{dirRel:N,error:x.message});continue}M.sort((x,L)=>x.isDirectory()&&!L.isDirectory()?-1:!x.isDirectory()&&L.isDirectory()?1:x.name.toLowerCase().localeCompare(L.name.toLowerCase()));const se=[];let V=!1,G=!1;for(let x=0;x<M.length;x++){const L=M[x];if(De(L,te,re))continue;if(U()){A=!0,V=!0,G=!0;break}$+=1;const ue=h.join(j,L.name),W=q(h.relative(C,ue));if(L.isDirectory()){if(se.push(W),b(!0)&&_.length<u&&Fe(W,L.name,F)&&(_.push({name:W,isDir:!0}),_.length>=u)){V=!0,G=ve(M,x+1,te,re);break}continue}try{if(b(!1)&&_.length<u&&Fe(W,L.name,F)&&(_.push({name:W,isDir:!1,fileProxyUrl:Q(t,W,k),isLink:L.isSymbolicLink()}),_.length>=u)){V=!0,G=ve(M,x+1,te,re);break}}catch(xe){f(l,"WARN",`search \u5904\u7406\u6587\u4EF6\u5931\u8D25: ${ue}`,{error:xe.message})}}if(V||U()){(U()||G||se.length>0||T.length>0)&&(A=!0);continue}for(const x of se){if(U()){A=!0;break}if(T.length>=He){A=!0;break}if($+T.length>=p){A=!0;break}ie.has(x)||T.push(x)}}return _.sort((N,j)=>N.isDir!==j.isDir?N.isDir?-1:1:String(N.name).localeCompare(String(j.name))),ne=A||T.length>0||$>=p||be(n,S),f(l,"INFO","File search completed",{kw:y,matchCount:_.length,visited:$,truncated:ne,relativePath:a||"",elapsedMs:Date.now()-n}),{files:_,truncated:ne,visited:$}}async function Ye(e,r,t,i,o=null){const a=Date.now(),s=`computer:${e}:${r}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!Array.isArray(t))throw new E("files must be an array",{field:"files"});const d=String(e),m=String(r),w=i&&i.trim()?i.trim():null,g=w||O(o,d,m);c.existsSync(g)||c.mkdirSync(g,{recursive:!0});for(let n=0;n<t.length;n++){const l=t[n];if(!l||typeof l.operation!="string")throw new E(`files[${n}].operation cannot be empty`,{field:`files[${n}].operation`});if(!l.name||typeof l.name!="string")throw new E(`files[${n}].name cannot be empty`,{field:`files[${n}].name`});const y=l.operation.toLowerCase();if(!["create","delete","rename","modify"].includes(y))throw new E(`files[${n}].operation must be one of create, delete, rename or modify`,{field:`files[${n}].operation`});if(y==="rename"&&!l.renameFrom)throw new E(`files[${n}].renameFrom cannot be empty (rename operation requires)`,{field:`files[${n}].renameFrom`});if(y==="modify"&&l.isDir!==!0&&typeof l.contents!="string")throw new E(`files[${n}].contents must be a string (modify operation requires)`,{field:`files[${n}].contents`})}f(s,"DEBUG","Start updating user files",{userId:d,cId:m,filesCount:t.length});try{for(const n of t){const l=n.operation.toLowerCase(),y=n.name,u=h.normalize(y).replace(/^[\/\\]+/,""),p=h.join(g,u),S=h.resolve(p),F=h.resolve(g);if(!S.startsWith(F+h.sep)&&S!==F){f(s,"WARN","File path is not secure, skipping",{filePath:u,resolvedPath:S});continue}switch(l){case"create":{if(n.isDir===!0){if(c.existsSync(p)){if((await c.promises.stat(p)).isFile())throw new E("Cannot create directory, file with the same name already exists",{filePath:u});f(s,"INFO","Directory already exists, skipping creation",{filePath:u});break}await c.promises.mkdir(p,{recursive:!0}),f(s,"INFO","Directory created successfully",{filePath:u});break}if(c.existsSync(p)){if((await c.promises.stat(p)).isDirectory())throw new E("Cannot create file, directory with the same name already exists",{filePath:u});f(s,"INFO","File already exists, skipping creation",{filePath:u});break}await c.promises.mkdir(h.dirname(p),{recursive:!0});const R=n.contents||"";await c.promises.writeFile(p,R,"utf8"),f(s,"INFO","File created successfully",{filePath:u});break}case"delete":{c.existsSync(p)?(await c.promises.stat(p)).isDirectory()?(await c.promises.rm(p,{recursive:!0,force:!0}),f(s,"INFO","Directory deleted successfully",{filePath:u})):(await c.promises.unlink(p),f(s,"INFO","File deleted successfully",{filePath:u})):f(s,"WARN","The file or directory to be deleted does not exist",{filePath:u});break}case"rename":{const R=n.renameFrom;if(!R||typeof R!="string"){f(s,"WARN","Rename operation missing renameFrom",{filePath:u});break}const b=h.normalize(R).replace(/^[\/\\]+/,""),v=h.join(g,b),I=h.resolve(v);if(!I.startsWith(F+h.sep)&&I!==F){f(s,"WARN","Source path is not secure, skipping rename",{sourcePath:b,targetPath:u});break}if(c.existsSync(v)){const C=(await c.promises.stat(v)).isDirectory();await c.promises.mkdir(h.dirname(p),{recursive:!0}),await c.promises.rename(v,p),f(s,"INFO",C?"Directory renamed successfully":"File renamed successfully",{sourcePath:b,targetPath:u})}else f(s,"WARN","The file or directory to be renamed does not exist",{sourcePath:b});break}case"modify":{if(!c.existsSync(p)){f(s,"WARN","The file to be modified does not exist",{filePath:u});break}if((await c.promises.stat(p)).isDirectory()){f(s,"INFO","The target is a directory, skipping modification",{filePath:u});break}const b=typeof n.contents=="string"?n.contents:"";if(await c.promises.readFile(p,"utf8")===b){f(s,"INFO","File content has no changes, skipping write",{filePath:u});break}await c.promises.writeFile(p,b,"utf8"),f(s,"INFO","File modified successfully",{filePath:u});break}default:{f(s,"WARN","Unsupported operation type",{operation:l,filePath:u});break}}}return f(s,"INFO","User files updated successfully",{userId:d,cId:m,filesCount:t.length,elapsedMs:Date.now()-a}),{success:!0,message:"User files updated successfully",userId:d,cId:m,filesCount:t.length}}catch(n){throw f(s,"ERROR","User files updated failed",{userId:d,cId:m,error:n.message,elapsedMs:Date.now()-a}),new z(`User files updated failed: ${n.message}`,{userId:d,cId:m,originalError:n.message})}}async function ce(e,r,t,i,o,a=null){const s=Date.now(),d=`computer:${e}:${r}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!t)throw new E("file cannot be empty",{field:"file"});if(!i||typeof i!="string")throw new E("filePath cannot be empty",{field:"filePath"});const m=String(e),w=String(r),g=o&&o.trim()?o.trim():null,n=g||O(a,m,w);c.existsSync(n)||c.mkdirSync(n,{recursive:!0});const l=h.normalize(i).replace(/^[\/\\]+/,""),y=h.join(n,l),u=h.resolve(y),p=h.resolve(n);if(!u.startsWith(p+h.sep)&&u!==p)throw new E("File path is not secure, cannot exceed user directory",{field:"filePath",providedPath:i,resolvedPath:u});try{if(await c.promises.mkdir(h.dirname(y),{recursive:!0}),t.buffer)await c.promises.writeFile(y,t.buffer);else if(typeof t.contents=="string")await c.promises.writeFile(y,t.contents,"utf8");else throw new E("File content format is incorrect",{field:"file",hasBuffer:!!t.buffer,hasContents:typeof t.contents});return f(d,"INFO","File uploaded successfully",{userId:m,cId:w,filePath:l,targetPath:u,fileSize:t.buffer?t.buffer.length:t.contents?Buffer.byteLength(t.contents,"utf8"):0,elapsedMs:Date.now()-s}),{success:!0,message:"File uploaded successfully",fileSize:t.buffer?t.buffer.length:t.contents?Buffer.byteLength(t.contents,"utf8"):0}}catch(S){throw f(d,"ERROR","File upload failed",{userId:m,cId:w,filePath:l,error:S.message,elapsedMs:Date.now()-s}),new z(`File upload failed: ${S.message}`,{userId:m,cId:w,filePath:l,originalError:S.message})}}async function Je(e,r,t,i,o,a=null){const s=Date.now(),d=`computer:${e}:${r}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!Array.isArray(t))throw new E("files must be an array",{field:"files"});if(!Array.isArray(i))throw new E("filePaths must be an array",{field:"filePaths"});if(t.length!==i.length)throw new E(`File count (${t.length}) does not match path count (${i.length})`,{field:"filePaths"});f(d,"DEBUG","Start batch uploading files",{userId:e,cId:r,filesCount:t.length});const m=[];try{for(let n=0;n<t.length;n++){const l=t[n],y=i[n];if(!l){f(d,"WARN","Empty file object encountered in batch upload, skipping",{index:n,filePath:y}),m.push({success:!1,filePath:y,error:"Empty file object"});continue}if(!y||typeof y!="string"){f(d,"WARN","Invalid file path in batch upload, skipping",{index:n,originalname:l.originalname}),m.push({success:!1,filePath:y||"",originalname:l.originalname,error:"Invalid file path"});continue}try{const u=await ce(e,r,l,y,o,a);m.push({success:!0,filePath:y,originalname:l.originalname,...u})}catch(u){f(d,"ERROR","Single file upload failed in batch upload",{filePath:y,originalname:l.originalname,error:u.message}),m.push({success:!1,filePath:y,originalname:l.originalname,error:u.message})}}const w=m.filter(n=>n.success).length,g=m.filter(n=>!n.success).length;return f(d,"INFO","Batch upload files completed",{userId:e,cId:r,totalCount:t.length,successCount:w,failCount:g,elapsedMs:Date.now()-s}),{success:!0,message:"Batch upload completed",totalCount:t.length,successCount:w,failCount:g,results:m}}catch(w){throw f(d,"ERROR","Batch upload files failed",{userId:e,cId:r,error:w.message,elapsedMs:Date.now()-s}),new z(`Batch upload files failed: ${w.message}`,{userId:e,cId:r,originalError:w.message})}}async function Qe(e,r,t,i=null){const o=Date.now(),a=`computer:${e}:${r}`,s=me(i);if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!s&&!i?.workspacePath)throw new z("COMPUTER_WORKSPACE_DIR is not configured, cannot create zip");const d=String(e),m=String(r),w=t&&t.trim()?t.trim():null,g=w||O(i,d,m);if(!c.existsSync(g)){const S=`${d}_${m}.zip`;f(a,"WARN","Workspace directory does not exist, returning empty zip",{targetDir:g,userId:d,cId:m,zipFileName:S});const F=fe("zip",{zlib:{level:9}});return F.append(null,{name:`${d}_${m}/`,type:"directory"}),F.on("warning",R=>{if(R.code==="ENOENT")f(a,"WARN","Encountered file problem when creating empty zip",{message:R.message,code:R.code});else throw f(a,"ERROR","Encountered warning when creating empty zip",{message:R.message,code:R.code}),R}),F.on("error",R=>{f(a,"ERROR","Failed to create empty zip",{message:R.message})}),{archive:F,zipFileName:S}}const n=`${d}_${m}.zip`;f(a,"DEBUG","Start creating workspace directory zip",{targetDir:g,zipFileName:n});const l=fe("zip",{zlib:{level:9}}),y=P.CONTENT_TRAVERSE_EXCLUDE_FILES||[],u=P.TRAVERSE_EXCLUDE_DIRS||[],p=await Ie(g,y,u,a);if(p>H){const S=H/1024/1024,F=(p/1024/1024).toFixed(2);throw f(a,"WARN","Download rejected due to oversized workspace",{targetDir:g,downloadableSize:p,maxSizeBytes:H}),new E(`Download failed: total file size ${F}MB exceeds limit ${S}MB`,{field:"downloadSize",downloadableSize:p,maxSizeBytes:H})}return l.directory(g,`${d}_${m}`,S=>{const F=S.name||"",R=F.split(/[\/\\]/).filter(Boolean);if(R.some(v=>v.startsWith(".")))return!1;const b=R[R.length-1];if(y.includes(b)||R.some(v=>u.includes(v)))return!1;try{const v=h.join(g,F),I=c.lstatSync(v);if(I.isSymbolicLink()||I.nlink>1)return!1}catch(v){return f(a,"WARN","Error occurred when detecting link file, skipping",{filePath:F,error:v.message}),!1}return S}),l.on("warning",S=>{if(S.code==="ENOENT")f(a,"WARN","Encountered file problem when creating zip",{message:S.message,code:S.code});else throw f(a,"ERROR","Encountered warning when creating zip",{message:S.message,code:S.code}),S}),l.on("error",S=>{f(a,"ERROR","Failed to create zip",{message:S.message,elapsedMs:Date.now()-o})}),l.on("end",()=>{f(a,"INFO","Workspace directory zip created successfully",{targetDir:g,zipFileName:n,elapsedMs:Date.now()-o})}),{archive:l,zipFileName:n}}async function et(e,r,t=200,i=null){const o=Date.now(),a=`computer:${e}:${r}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});const s=String(e),d=String(r),m=Ae(i,s,d);if(!m)return f(a,"WARN","COMPUTER_WORKSPACE_DIR is not configured"),{success:!0,message:"Log workspace is not configured",logs:[],totalLines:0,startIndex:1,logFileName:null};if(!c.existsSync(m))return f(a,"DEBUG","Log directory does not exist",{logDir:m}),{success:!0,message:"Log directory does not exist",logs:[],totalLines:0,startIndex:1,logFileName:null};const g=(await c.promises.readdir(m,{withFileTypes:!0})).filter(I=>I.isFile()).map(I=>I.name);if(g.length===0)return f(a,"DEBUG","No log file found",{logDir:m}),{success:!0,message:"No log file found",logs:[],totalLines:0,startIndex:1,logFileName:null};const n=[];for(const I of g){const k=h.join(m,I),C=await c.promises.stat(k);n.push({name:I,fullPath:k,mtime:C.mtimeMs,size:C.size})}n.sort((I,k)=>k.mtime-I.mtime);const l=n[0],u=(await c.promises.readFile(l.fullPath,"utf8")).split(`
2
+ `).filter(I=>I.length>0),p=u.length,S=Math.max(1,t),F=Math.max(0,p-S),R=F+1,v=u.slice(F).map((I,k)=>({line:R+k,content:I}));return f(a,"DEBUG","Get latest logs",{fileName:l.name,fileSize:l.size,totalLines:p,returnedLines:v.length,elapsedMs:Date.now()-o}),{success:!0,message:"Get log successfully",logs:v,totalLines:p,startIndex:R,logFileName:l.name}}async function ke(e,r){try{const i=(await c.promises.readdir(e,{withFileTypes:!0})).filter(d=>!d.name.startsWith(".")&&d.name!=="node_modules");if(i.length!==1||!i[0].isDirectory())return;const o=h.join(e,i[0].name),a=h.join(e,`..tmp_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{await c.promises.rename(o,a)}catch(d){if(d.code==="EXDEV")await c.promises.cp(o,a,{recursive:!0}),await c.promises.rm(o,{recursive:!0,force:!0});else throw d}const s=await c.promises.readdir(a);for(const d of s){const m=h.join(a,d),w=h.join(e,d);try{await c.promises.rename(m,w)}catch(g){if(g.code==="EXDEV")(await c.promises.lstat(m)).isDirectory()?await Le(m,w):(await c.promises.copyFile(m,w),await c.promises.unlink(m));else throw g}}await c.promises.rm(a,{recursive:!0,force:!0}),f(r,"INFO","Removed single top-level directory from imported zip",{removedDir:i[0].name})}catch(t){f(r,"WARN","Failed to remove top-level directory from imported zip, continuing",{error:t.message})}}async function tt(e,r){if(!c.existsSync(e)){await c.promises.mkdir(e,{recursive:!0});return}const t=await c.promises.readdir(e,{withFileTypes:!0});for(const i of t){if(B.has(i.name))continue;const o=h.join(e,i.name);await c.promises.rm(o,{recursive:!0,force:!0}),f(r,"DEBUG","Removed workspace entry during import",{name:i.name})}}async function rt(e,r,t){if(await c.promises.mkdir(r,{recursive:!0}),!c.existsSync(e)){await c.promises.mkdir(e,{recursive:!0});return}const i=await c.promises.readdir(e,{withFileTypes:!0});for(const o of i){if(B.has(o.name))continue;const a=h.join(e,o.name),s=h.join(r,o.name);await oe(a,s),f(t,"DEBUG","Backed up workspace entry before import",{name:o.name})}}async function it(e,r,t){if(!c.existsSync(e))return;const i=await c.promises.readdir(e,{withFileTypes:!0});for(const o of i){if(B.has(o.name))continue;const a=h.join(e,o.name),s=h.join(r,o.name);await oe(a,s)}f(t,"INFO","Workspace restored from backup after import failure")}async function nt(e,r,t){const i=await c.promises.readdir(e,{withFileTypes:!0}),o=[];try{for(const a of i){if(B.has(a.name)){f(t,"INFO","Skipping preserved entry from imported zip",{name:a.name});continue}const s=h.join(e,a.name),d=h.join(r,a.name);await oe(s,d),o.push(a.name)}}catch(a){throw a.mergedEntries=o,a}}async function st(e,r,t,i,o=null){const a=Date.now(),s=`computer:${e}:${r}`,d=me(o);if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!t||!t.path)throw new E("file is required",{field:"file"});if(!d&&!o?.workspacePath)throw new z("COMPUTER_WORKSPACE_DIR is not configured");if(h.extname(t.originalname||t.filename||"").toLowerCase()!==".zip")throw new E("Only zip files are supported",{field:"file",originalName:t.originalname});const w=String(e),g=String(r),n=i&&i.trim()?i.trim():O(o,w,g),l=h.join(n,".tmp"),y=h.join(l,`import_project_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);let u=null;f(s,"INFO","Start importing project from zip",{userId:w,cId:g,targetDir:n,fileName:t.originalname});try{await c.promises.mkdir(n,{recursive:!0}),await c.promises.mkdir(l,{recursive:!0}),await c.promises.mkdir(y,{recursive:!0}),await _e(t.path,y),await ke(y,s),u=h.join(l,`import_backup_${Date.now()}_${Math.round(Math.random()*1e6)}`),await rt(n,u,s);try{await nt(y,n,s)}catch(p){throw f(s,"ERROR","Merge imported project failed, restoring workspace",{error:p.message,mergedEntries:p.mergedEntries||[]}),await tt(n,s),await it(u,n,s),p}return c.existsSync(u)&&(await c.promises.rm(u,{recursive:!0,force:!0}),u=null),f(s,"INFO","Project imported successfully",{userId:w,cId:g,targetDir:n,elapsedMs:Date.now()-a}),{success:!0,message:"Project imported successfully",userId:w,cId:g,targetDir:n}}catch(p){throw f(s,"ERROR","Failed to import project",{userId:w,cId:g,targetDir:n,error:p.message,elapsedMs:Date.now()-a}),p instanceof E||p instanceof z||p instanceof Pe?p:new z(`Failed to import project: ${p.message}`,{userId:w,cId:g,originalError:p.message})}finally{if(c.existsSync(y))try{await c.promises.rm(y,{recursive:!0,force:!0})}catch(p){f(s,"WARN","Failed to clean import extract directory",{extractRoot:y,error:p.message})}if(u&&c.existsSync(u))try{await c.promises.rm(u,{recursive:!0,force:!0})}catch(p){f(s,"WARN","Failed to clean import backup directory",{backupDir:u,error:p.message})}if(t?.path&&c.existsSync(t.path))try{await c.promises.unlink(t.path)}catch(p){f(s,"WARN","Failed to clean uploaded zip file",{tempZipPath:t.path,error:p.message})}}}async function ot(e,r,t,i,o,a=null){const s=`computer:${e}:${r}`;if(!t||typeof t!="string"||!t.trim())throw new E("fileName cannot be empty",{field:"fileName"});if(i!=null&&typeof i!="string")throw new E("content must be a string",{field:"content"});const d=t.trim(),m=i??"";return f(s,"INFO","Generate file from text content",{userId:e,cId:r,fileName:d,contentLength:m.length}),{...await ce(e,r,{contents:m,originalname:h.basename(d),size:Buffer.byteLength(m,"utf8")},d,o,a),message:"File generated successfully",fileName:d}}export{X as escapesRoot,Ge as getFileList,Ve as getFileMeta,ze as resolveExistingFile,Ke as searchFiles,Ye as updateFiles,ce as uploadFile,Je as uploadFiles,ot as generateFile,Qe as downloadAllFiles,et as getLatestLogs,st as importProject,B as IMPORT_PROJECT_PRESERVED_ENTRIES,ke as removeTopLevelDir};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuwax-file-server",
3
3
  "displayName": "nuwax-file-server",
4
- "version": "1.5.6",
4
+ "version": "1.5.8",
5
5
  "description": "Cross-platform file service deployment tool with start/stop/restart CLI commands",
6
6
  "type": "module",
7
7
  "main": "index.js",