nuwax-file-server 1.3.0-beta.18 → 1.3.0-beta.20

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.3.0-beta.18",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.3.0-beta.20",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();
@@ -93,7 +93,7 @@ COMPUTER_LOG_DIR=/app/logs/computer_logs
93
93
  GIT_DEFAULT_AUTHOR_NAME=Nuwax File Server
94
94
  GIT_DEFAULT_AUTHOR_EMAIL=git@nuwax.com
95
95
  GIT_AUTO_GITIGNORE=true
96
- GIT_GITIGNORE_ENTRIES=node_modules/|.pnpm-store/|dist/|build/|.idea/|.vscode/|.DS_Store|.agents/|.claude/|.opencode/|.codex/
96
+ GIT_GITIGNORE_ENTRIES=node_modules/|.pnpm-store/|dist/|dist-packages/|build/|.idea/|.vscode/|.DS_Store|.agents/|.claude/|.opencode/|.codex/|.tmp/
97
97
  GIT_ENABLED=true
98
98
 
99
99
  # Windows 专用路径(Linux/macOS 留空)
@@ -93,7 +93,7 @@ COMPUTER_LOG_DIR=/app/logs/computer_logs
93
93
  GIT_DEFAULT_AUTHOR_NAME=Nuwax File Server
94
94
  GIT_DEFAULT_AUTHOR_EMAIL=git@nuwax.com
95
95
  GIT_AUTO_GITIGNORE=true
96
- GIT_GITIGNORE_ENTRIES=node_modules/|.pnpm-store/|dist/|build/|.idea/|.vscode/|.DS_Store|.agents/|.claude/|.opencode/|.codex/
96
+ GIT_GITIGNORE_ENTRIES=node_modules/|.pnpm-store/|dist/|dist-packages/|build/|.idea/|.vscode/|.DS_Store|.agents/|.claude/|.opencode/|.codex/|.tmp/
97
97
  GIT_ENABLED=true
98
98
 
99
99
  # Windows 专用路径(Linux/macOS 留空)
@@ -1 +1 @@
1
- import z from"express";import $ from"multer";import f from"fs";import w from"path";import{ValidationError as C,asyncHandler as d}from"../utils/error/errorHandler.js";import{log as u}from"../utils/log/logUtils.js";import F from"../appConfig/index.js";import{createWorkspace as N,pushSkillsToWorkspace as S,initProjectTemplate as b,executeCommand as A,deleteWorkspace as j,zipWorkspace as O,buildAgentPackage as D,cleanupBuildArtifacts as E}from"../utils/computer/computerUtils.js";import{getFileList as U,updateFiles as P,uploadFile as T,uploadFiles as R,downloadAllFiles as x,getLatestLogs as v}from"../utils/computer/computerFileUtils.js";const g=z.Router(),h=$({storage:$.diskStorage({destination:(t,o,s)=>{try{const e=F.COMPUTER_WORKSPACE_DIR;if(!e)return s(new Error("COMPUTER_WORKSPACE_DIR is not configured, cannot determine upload temporary directory"));const a=t.body?.userId||"unknown",i=t.body?.cId||"unknown",n=w.join(e,String(a),String(i),".tmp");f.existsSync(n)||f.mkdirSync(n,{recursive:!0}),s(null,n)}catch(e){s(e)}},filename:(t,o,s)=>{const e=w.extname(o.originalname)||".zip",a=w.basename(o.originalname,e),i=`${Date.now()}_${Math.round(Math.random()*1e6)}`;s(null,`${a}_${i}${e}`)}}),limits:{fileSize:F.UPLOAD_MAX_FILE_SIZE_BYTES}});function H(t,o,s,e){if(t&&t.name==="MulterError"||t&&(t.name==="ValidationError"||t instanceof C))return e(t);e(t)}const _=[{path:"/create-workspace",method:"post",middleware:h.single("file"),handler:d(async(t,o)=>{const{userId:s,cId:e}=t.body||{},a=t.file||null,i=`computer:${s}:${e}`;u(i,"INFO","Create workspace request",{userId:s,cId:e,hasFile:!!a,fileName:a?.originalname,fileSize:a?.size});const n=await N(s,e,a);o.status(200).json({success:!0,...n})})},{path:"/create-workspace-v2",method:"post",middleware:h.single("file"),handler:d(async(t,o)=>{const{userId:s,cId:e,skillUrls:a,mcpServersConfig:i,hooksConfig:n,permissionsConfig:r,hookScripts:l}=t.body||{},c=t.file||null,y=`computer:${s}:${e}`;let m=a;if(typeof a=="string")try{const k=JSON.parse(a);m=Array.isArray(k)?k:[a]}catch{m=[a]}let p=l;if(typeof l=="string")try{p=JSON.parse(l)}catch{p=null}u(y,"INFO","Create workspace v2 request",{userId:s,cId:e,hasFile:!!c,fileName:c?.originalname,fileSize:c?.size,skillUrlsCount:Array.isArray(m)?m.length:0,hasMcpServersConfig:!!i,hasHooksConfig:!!n,hasPermissionsConfig:!!r,hookScriptsCount:Array.isArray(p)?p.length:0});const I=await N(s,e,c,m,i,r,n,p);o.status(200).json({success:!0,...I})})},{path:"/push-skills-to-workspace",method:"post",middleware:h.single("file"),handler:d(async(t,o)=>{const{userId:s,cId:e}=t.body||{},a=t.file||null,i=`computer:${s}:${e}`;u(i,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42",{userId:s,cId:e,hasFile:!!a,fileName:a?.originalname,fileSize:a?.size});const n=await S(s,e,a);o.status(200).json({success:!0,...n})})},{path:"/push-skills-to-workspace-v2",method:"post",middleware:h.single("file"),handler:d(async(t,o)=>{const{userId:s,cId:e,skillUrls:a}=t.body||{},i=t.file||null,n=`computer:${s}:${e}`;let r=a;if(typeof a=="string")try{const c=JSON.parse(a);r=Array.isArray(c)?c:[a]}catch{r=[a]}u(n,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42(v2)",{userId:s,cId:e,hasFile:!!i,fileName:i?.originalname,fileSize:i?.size,skillUrlsCount:Array.isArray(r)?r.length:0});const l=await S(s,e,i,r);o.status(200).json({success:!0,...l})})},{path:"/get-file-list",method:"get",handler:d(async(t,o)=>{const{userId:s,cId:e,proxyPath:a,customTargetDir:i}=t.query,n=await U(s,e,a,i);o.status(200).json({success:!0,...n})})},{path:"/files-update",method:"post",handler:d(async(t,o)=>{const{userId:s,cId:e,files:a,customTargetDir:i}=t.body||{},n=`computer:${s}:${e}`;u(n,"INFO","Files update",{userId:s,cId:e,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(c){u(n,"WARN","Decode file content failed",{fileName:l.name,error:c.message})}});const r=await P(s,e,a,i);o.status(200).json(r)})},{path:"/upload-file",method:"post",middleware:h.single("file"),handler:d(async(t,o)=>{const{userId:s,cId:e,filePath:a,customTargetDir:i}=t.body||{},n=t.file,r=`computer:${s}:${e}`;u(r,"INFO","Upload single file",{userId:s,cId:e,filePath:a});const c={buffer:await f.promises.readFile(n.path),originalname:n.originalname,mimetype:n.mimetype,size:n.size};try{const y=await T(s,e,c,a,i);o.status(200).json(y)}finally{f.existsSync(n.path)&&await f.promises.unlink(n.path)}})},{path:"/upload-files",method:"post",middleware:h.array("files"),handler:d(async(t,o)=>{const{userId:s,cId:e,filePaths:a,customTargetDir:i}=t.body||{},n=t.files||[],r=`computer:${s}:${e}`,l=Array.isArray(a)?a:typeof a=="string"?[a]:a;u(r,"INFO","Batch upload files request",{userId:s,cId:e,filesCount:n.length,filePathsCount:Array.isArray(l)?l.length:0});const c=[],y=[];try{for(const p of n){y.push(p.path);const I=await f.promises.readFile(p.path);c.push({buffer:I,originalname:p.originalname,mimetype:p.mimetype,size:p.size})}const m=await R(s,e,c,l,i);o.status(200).json(m)}finally{for(const m of y)if(f.existsSync(m))try{await f.promises.unlink(m)}catch(p){u(r,"WARN","Clean temporary file failed",{tempPath:m,error:p.message})}}})},{path:"/init-project-template",method:"post",middleware:h.single("file"),handler:d(async(t,o)=>{const{userId:s,cId:e,enableGit:a}=t.body||{},i=t.file||null,n=`computer:${s}:${e}`;u(n,"INFO","Init project template request",{userId:s,cId:e,hasFile:!!i,fileName:i?.originalname,fileSize:i?.size,enableGit:a});const r=await b(s,e,i,a);o.status(200).json({success:!0,...r})})},{path:"/download-all-files",method:"get",handler:d(async(t,o)=>{const{userId:s,cId:e,customTargetDir:a}=t.query||{},i=`computer:${s}:${e}`;u(i,"INFO","Download all files request",{userId:s,cId:e});const{archive:n,zipFileName:r}=await x(s,e,a);o.setHeader("Content-Type","application/zip");const l=encodeURIComponent(r);o.setHeader("Content-Disposition",`attachment; filename="${l}"; filename*=UTF-8''${l}`),n.on("error",c=>{o.destroy(c)}),n.pipe(o),n.finalize()})},{path:"/execute-command",method:"post",handler:d(async(t,o)=>{const{userId:s,cId:e,command:a}=t.body||{},i=`computer:${s}:${e}`;u(i,"INFO","Execute command request",{userId:s,cId:e,command:a});const n=await A(s,e,a);o.status(200).json({success:!0,...n})})},{path:"/delete-workspace",method:"post",handler:d(async(t,o)=>{const{userId:s,cId:e}=t.body||{},a=`computer:${s}:${e}`;u(a,"INFO","Delete workspace request",{userId:s,cId:e});const i=await j(s,e);o.status(200).json({success:!0,...i})})},{path:"/get-logs",method:"get",handler:d(async(t,o)=>{const{userId:s,cId:e,tailLines:a}=t.query,i=a?parseInt(a,10):200,n=Number.isFinite(i)&&i>0?i:200,r=await v(s,e,n);o.status(200).json({success:!0,...r})})},{path:"/zip-workspace",method:"post",handler:d(async(t,o)=>{const{userId:s,cId:e,excludeDirs:a}=t.body||{},i=`computer:${s}:${e}`;u(i,"INFO","Zip workspace request",{userId:s,cId:e,hasExcludeDirs:!!a});const{archive:n,zipFileName:r}=await O(s,e,a||null);o.setHeader("Content-Type","application/zip");const l=encodeURIComponent(r);o.setHeader("Content-Disposition",`attachment; filename="${l}"; filename*=UTF-8''${l}`),n.on("error",c=>{o.destroy(c)}),n.pipe(o),n.finalize()})},{path:"/build-agent-package",method:"post",handler:d(async(t,o)=>{const{userId:s,cId:e,agentId:a,version:i}=t.body||{},n=`computer:${s}:${e}`;u(n,"INFO","Build agent package request",{userId:s,cId:e,agentId:a,version:i});const r=await D(s,e,a,i);o.status(200).json({success:!0,...r})})},{path:"/cleanup-build-artifacts",method:"post",handler:d(async(t,o)=>{const{userId:s,cId:e}=t.body||{},a=`computer:${s}:${e}`;u(a,"INFO","Cleanup build artifacts request",{userId:s,cId:e});const i=await E(s,e);o.status(200).json({success:!0,...i})})}];_.forEach(t=>{if(t.middleware)g[t.method](t.path,t.middleware,t.handler);else if(t.customHandler){const o=[];t.handler&&o.push(t.handler),t.decodeMiddleware&&o.push(t.decodeMiddleware),o.push(H),o.push(t.customHandler),g[t.method](t.path,...o)}else g[t.method](t.path,t.handler)});export default g;
1
+ import z from"express";import $ from"multer";import f from"fs";import w from"path";import{ValidationError as C,asyncHandler as d}from"../utils/error/errorHandler.js";import{log as u}from"../utils/log/logUtils.js";import F from"../appConfig/index.js";import{createWorkspace as N,pushSkillsToWorkspace as S,initProjectTemplate as b,executeCommand as A,deleteWorkspace as j,zipWorkspace as O,buildAgentPackage as D,cleanupBuildArtifacts as E}from"../utils/computer/computerUtils.js";import{getFileList as U,updateFiles as P,uploadFile as T,uploadFiles as R,downloadAllFiles as x,getLatestLogs as v}from"../utils/computer/computerFileUtils.js";const g=z.Router(),h=$({storage:$.diskStorage({destination:(t,o,s)=>{try{const e=F.COMPUTER_WORKSPACE_DIR;if(!e)return s(new Error("COMPUTER_WORKSPACE_DIR is not configured, cannot determine upload temporary directory"));const a=t.body?.userId||"unknown",i=t.body?.cId||"unknown",n=w.join(e,String(a),String(i),".tmp");f.existsSync(n)||f.mkdirSync(n,{recursive:!0}),s(null,n)}catch(e){s(e)}},filename:(t,o,s)=>{const e=w.extname(o.originalname)||".zip",a=w.basename(o.originalname,e),i=`${Date.now()}_${Math.round(Math.random()*1e6)}`;s(null,`${a}_${i}${e}`)}}),limits:{fileSize:F.UPLOAD_MAX_FILE_SIZE_BYTES}});function H(t,o,s,e){if(t&&t.name==="MulterError"||t&&(t.name==="ValidationError"||t instanceof C))return e(t);e(t)}const _=[{path:"/create-workspace",method:"post",middleware:h.single("file"),handler:d(async(t,o)=>{const{userId:s,cId:e}=t.body||{},a=t.file||null,i=`computer:${s}:${e}`;u(i,"INFO","Create workspace request",{userId:s,cId:e,hasFile:!!a,fileName:a?.originalname,fileSize:a?.size});const n=await N(s,e,a);o.status(200).json({success:!0,...n})})},{path:"/create-workspace-v2",method:"post",middleware:h.single("file"),handler:d(async(t,o)=>{const{userId:s,cId:e,skillUrls:a,mcpServersConfig:i,hooksConfig:n,permissionsConfig:r,hookScripts:l}=t.body||{},c=t.file||null,y=`computer:${s}:${e}`;let m=a;if(typeof a=="string")try{const k=JSON.parse(a);m=Array.isArray(k)?k:[a]}catch{m=[a]}let p=l;if(typeof l=="string")try{p=JSON.parse(l)}catch{p=null}u(y,"INFO","Create workspace v2 request",{userId:s,cId:e,hasFile:!!c,fileName:c?.originalname,fileSize:c?.size,skillUrlsCount:Array.isArray(m)?m.length:0,hasMcpServersConfig:!!i,hasHooksConfig:!!n,hasPermissionsConfig:!!r,hookScriptsCount:Array.isArray(p)?p.length:0});const I=await N(s,e,c,m,i,r,n,p);o.status(200).json({success:!0,...I})})},{path:"/push-skills-to-workspace",method:"post",middleware:h.single("file"),handler:d(async(t,o)=>{const{userId:s,cId:e}=t.body||{},a=t.file||null,i=`computer:${s}:${e}`;u(i,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42",{userId:s,cId:e,hasFile:!!a,fileName:a?.originalname,fileSize:a?.size});const n=await S(s,e,a);o.status(200).json({success:!0,...n})})},{path:"/push-skills-to-workspace-v2",method:"post",middleware:h.single("file"),handler:d(async(t,o)=>{const{userId:s,cId:e,skillUrls:a}=t.body||{},i=t.file||null,n=`computer:${s}:${e}`;let r=a;if(typeof a=="string")try{const c=JSON.parse(a);r=Array.isArray(c)?c:[a]}catch{r=[a]}u(n,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42(v2)",{userId:s,cId:e,hasFile:!!i,fileName:i?.originalname,fileSize:i?.size,skillUrlsCount:Array.isArray(r)?r.length:0});const l=await S(s,e,i,r);o.status(200).json({success:!0,...l})})},{path:"/get-file-list",method:"get",handler:d(async(t,o)=>{const{userId:s,cId:e,proxyPath:a,customTargetDir:i}=t.query,n=await U(s,e,a,i);o.status(200).json({success:!0,...n})})},{path:"/files-update",method:"post",handler:d(async(t,o)=>{const{userId:s,cId:e,files:a,customTargetDir:i}=t.body||{},n=`computer:${s}:${e}`;u(n,"INFO","Files update",{userId:s,cId:e,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(c){u(n,"WARN","Decode file content failed",{fileName:l.name,error:c.message})}});const r=await P(s,e,a,i);o.status(200).json(r)})},{path:"/upload-file",method:"post",middleware:h.single("file"),handler:d(async(t,o)=>{const{userId:s,cId:e,filePath:a,customTargetDir:i}=t.body||{},n=t.file,r=`computer:${s}:${e}`;u(r,"INFO","Upload single file",{userId:s,cId:e,filePath:a});const c={buffer:await f.promises.readFile(n.path),originalname:n.originalname,mimetype:n.mimetype,size:n.size};try{const y=await T(s,e,c,a,i);o.status(200).json(y)}finally{f.existsSync(n.path)&&await f.promises.unlink(n.path)}})},{path:"/upload-files",method:"post",middleware:h.array("files"),handler:d(async(t,o)=>{const{userId:s,cId:e,filePaths:a,customTargetDir:i}=t.body||{},n=t.files||[],r=`computer:${s}:${e}`,l=Array.isArray(a)?a:typeof a=="string"?[a]:a;u(r,"INFO","Batch upload files request",{userId:s,cId:e,filesCount:n.length,filePathsCount:Array.isArray(l)?l.length:0});const c=[],y=[];try{for(const p of n){y.push(p.path);const I=await f.promises.readFile(p.path);c.push({buffer:I,originalname:p.originalname,mimetype:p.mimetype,size:p.size})}const m=await R(s,e,c,l,i);o.status(200).json(m)}finally{for(const m of y)if(f.existsSync(m))try{await f.promises.unlink(m)}catch(p){u(r,"WARN","Clean temporary file failed",{tempPath:m,error:p.message})}}})},{path:"/init-project-template",method:"post",middleware:h.single("file"),handler:d(async(t,o)=>{const{userId:s,cId:e,enableGit:a}=t.body||{},i=t.file||null,n=`computer:${s}:${e}`;u(n,"INFO","Init project template request",{userId:s,cId:e,hasFile:!!i,fileName:i?.originalname,fileSize:i?.size,enableGit:a});const r=await b(s,e,i,a);o.status(200).json({success:!0,...r})})},{path:"/download-all-files",method:"get",handler:d(async(t,o)=>{const{userId:s,cId:e,customTargetDir:a}=t.query||{},i=`computer:${s}:${e}`;u(i,"INFO","Download all files request",{userId:s,cId:e});const{archive:n,zipFileName:r}=await x(s,e,a);o.setHeader("Content-Type","application/zip");const l=encodeURIComponent(r);o.setHeader("Content-Disposition",`attachment; filename="${l}"; filename*=UTF-8''${l}`),n.on("error",c=>{o.destroy(c)}),n.pipe(o),n.finalize()})},{path:"/execute-command",method:"post",handler:d(async(t,o)=>{const{userId:s,cId:e,command:a}=t.body||{},i=`computer:${s}:${e}`;u(i,"INFO","Execute command request",{userId:s,cId:e,command:a});const n=await A(s,e,a);o.status(200).json({success:!0,...n})})},{path:"/delete-workspace",method:"post",handler:d(async(t,o)=>{const{userId:s,cId:e}=t.body||{},a=`computer:${s}:${e}`;u(a,"INFO","Delete workspace request",{userId:s,cId:e});const i=await j(s,e);o.status(200).json({success:!0,...i})})},{path:"/get-logs",method:"get",handler:d(async(t,o)=>{const{userId:s,cId:e,tailLines:a}=t.query,i=a?parseInt(a,10):200,n=Number.isFinite(i)&&i>0?i:200,r=await v(s,e,n);o.status(200).json(r)})},{path:"/zip-workspace",method:"post",handler:d(async(t,o)=>{const{userId:s,cId:e,excludeDirs:a}=t.body||{},i=`computer:${s}:${e}`;u(i,"INFO","Zip workspace request",{userId:s,cId:e,hasExcludeDirs:!!a});const{archive:n,zipFileName:r}=await O(s,e,a||null);o.setHeader("Content-Type","application/zip");const l=encodeURIComponent(r);o.setHeader("Content-Disposition",`attachment; filename="${l}"; filename*=UTF-8''${l}`),n.on("error",c=>{o.destroy(c)}),n.pipe(o),n.finalize()})},{path:"/build-agent-package",method:"post",handler:d(async(t,o)=>{const{userId:s,cId:e,agentId:a,version:i}=t.body||{},n=`computer:${s}:${e}`;u(n,"INFO","Build agent package request",{userId:s,cId:e,agentId:a,version:i});const r=await D(s,e,a,i);o.status(200).json({success:!0,...r})})},{path:"/cleanup-build-artifacts",method:"post",handler:d(async(t,o)=>{const{userId:s,cId:e}=t.body||{},a=`computer:${s}:${e}`;u(a,"INFO","Cleanup build artifacts request",{userId:s,cId:e});const i=await E(s,e);o.status(200).json({success:!0,...i})})}];_.forEach(t=>{if(t.middleware)g[t.method](t.path,t.middleware,t.handler);else if(t.customHandler){const o=[];t.handler&&o.push(t.handler),t.decodeMiddleware&&o.push(t.decodeMiddleware),o.push(H),o.push(t.customHandler),g[t.method](t.path,...o)}else g[t.method](t.path,t.handler)});export default g;
@@ -1 +1 @@
1
- import l from"express";import{asyncHandler as n,ValidationError as m}from"../utils/error/errorHandler.js";import c from"../service/gitService.js";import{log as r}from"../utils/log/logUtils.js";import{extractIsolationContext as I}from"../utils/common/projectPathUtils.js";const u=l.Router();function d(a){const{workspaceType:o,projectId:t,userId:e,cId:s}=a||{},i=I(a||{});return{workspaceType:o?String(o):void 0,projectId:t?String(t):void 0,userId:e?String(e):void 0,cId:s?String(s):void 0,isolationContext:i}}const y=[{path:"/init",method:"post",handler:n(async(a,o)=>{const t=d(a.body);r(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git init",t);const e=await c.init(t);o.status(200).json(e)})},{path:"/status",method:"get",handler:n(async(a,o)=>{const t=d(a.query);r(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git status",t);const e=await c.status(t);o.status(200).json(e)})},{path:"/commit",method:"post",handler:n(async(a,o)=>{const{message:t,files:e,authorName:s,authorEmail:i}=a.body||{},p={...d(a.body),message:t,files:e,authorName:s,authorEmail:i};if(!t)throw new m("Commit message cannot be empty",{field:"message"});r(p.projectId||`computer:${p.userId}:${p.cId}`,"INFO","Git commit",{...p,filesCount:e?e.length:"all"});const h=await c.commit(p);o.status(200).json(h)})},{path:"/add",method:"post",handler:n(async(a,o)=>{const{files:t}=a.body||{},e={...d(a.body),files:t};r(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git add",{...e,filesCount:t?t.length:"all"});const s=await c.add(e);o.status(200).json(s)})},{path:"/unstage",method:"post",handler:n(async(a,o)=>{const{files:t}=a.body||{},e={...d(a.body),files:t};r(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git unstage",{...e,filesCount:t?t.length:"all"});const s=await c.unstage(e);o.status(200).json(s)})},{path:"/discard",method:"post",handler:n(async(a,o)=>{const{files:t}=a.body||{},e={...d(a.body),files:t};r(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git discard",{...e,filesCount:t?t.length:"all"});const s=await c.discard(e);o.status(200).json(s)})},{path:"/log",method:"get",handler:n(async(a,o)=>{const{maxCount:t,branch:e,skip:s,filePath:i}=a.query||{},p={...d(a.query),maxCount:t?parseInt(t,10):50,skip:s?parseInt(s,10):0,branch:e,filePath:i};r(p.projectId||`computer:${p.userId}:${p.cId}`,"INFO","Git log",p);const h=await c.logHistory(p);o.status(200).json(h)})},{path:"/diff",method:"post",handler:n(async(a,o)=>{const{from:t,to:e,paths:s,source:i}=a.body||{},p={...d(a.body),from:t,to:e,paths:s,source:i};r(p.projectId||`computer:${p.userId}:${p.cId}`,"INFO","Git diff",{from:t,to:e});const h=await c.diff(p);o.status(200).json(h)})},{path:"/file-content",method:"post",handler:n(async(a,o)=>{const{ref:t,filePath:e}=a.body||{},s={...d(a.body),ref:t,filePath:e};r(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git file content",{ref:t,filePath:e});const i=await c.fileContent(s);o.status(200).json(i)})},{path:"/reset",method:"post",handler:n(async(a,o)=>{const{target:t,mode:e}=a.body||{},s={...d(a.body),target:t,mode:e||"mixed"};if(!t)throw new m("Reset target cannot be empty",{field:"target"});r(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git reset",{target:t,mode:e});const i=await c.reset(s);o.status(200).json(i)})},{path:"/checkout",method:"post",handler:n(async(a,o)=>{const{target:t}=a.body||{},e={...d(a.body),target:t};if(!t)throw new m("Checkout target cannot be empty",{field:"target"});r(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git checkout files",{target:t});const s=await c.checkout(e);o.status(200).json(s)})},{path:"/tags",method:"get",handler:n(async(a,o)=>{const t=d(a.query);r(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git list tags",t);const e=await c.listTags(t);o.status(200).json(e)})},{path:"/tag-create",method:"post",handler:n(async(a,o)=>{const{tagName:t,message:e}=a.body||{},s={...d(a.body),tagName:t,message:e};if(!t)throw new m("Tag name cannot be empty",{field:"tagName"});r(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git create tag",{tagName:t});const i=await c.createTag(s);o.status(200).json(i)})},{path:"/tag-delete",method:"post",handler:n(async(a,o)=>{const{tagName:t}=a.body||{},e={...d(a.body),tagName:t};if(!t)throw new m("Tag name cannot be empty",{field:"tagName"});r(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git delete tag",{tagName:t});const s=await c.deleteTag(e);o.status(200).json(s)})},{path:"/branches",method:"get",handler:n(async(a,o)=>{const t=d(a.query);r(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git list branches",t);const e=await c.listBranches(t);o.status(200).json(e)})},{path:"/branch-create",method:"post",handler:n(async(a,o)=>{const{branchName:t,startPoint:e}=a.body||{},s={...d(a.body),branchName:t,startPoint:e};if(!t)throw new m("Branch name cannot be empty",{field:"branchName"});r(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git create branch",{branchName:t,startPoint:e});const i=await c.createBranch(s);o.status(200).json(i)})},{path:"/branch-switch",method:"post",handler:n(async(a,o)=>{const{branchName:t}=a.body||{},e={...d(a.body),branchName:t};if(!t)throw new m("Branch name cannot be empty",{field:"branchName"});r(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git switch branch",{branchName:t});const s=await c.switchBranch(e);o.status(200).json(s)})},{path:"/branch-delete",method:"post",handler:n(async(a,o)=>{const{branchName:t,force:e}=a.body||{},s={...d(a.body),branchName:t,force:e===!0};if(!t)throw new m("Branch name cannot be empty",{field:"branchName"});r(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git delete branch",{branchName:t,force:e});const i=await c.deleteBranch(s);o.status(200).json(i)})}];y.forEach(a=>{a.middleware?u[a.method](a.path,a.middleware,a.handler):u[a.method](a.path,a.handler)});export default u;
1
+ import l from"express";import{asyncHandler as c,ValidationError as p}from"../utils/error/errorHandler.js";import d from"../service/gitService.js";import{log as i}from"../utils/log/logUtils.js";import{extractIsolationContext as I}from"../utils/common/projectPathUtils.js";const u=l.Router();function m(a){const{workspaceType:o,projectId:t,userId:e,cId:s}=a||{},r=I(a||{});return{workspaceType:o?String(o):void 0,projectId:t?String(t):void 0,userId:e?String(e):void 0,cId:s?String(s):void 0,isolationContext:r}}const y=[{path:"/init",method:"post",handler:c(async(a,o)=>{const t=m(a.body);i(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git init",t);const e=await d.init(t);o.status(200).json(e)})},{path:"/status",method:"get",handler:c(async(a,o)=>{const t=m(a.query);i(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git status",t);const e=await d.status(t);o.status(200).json(e)})},{path:"/commit",method:"post",handler:c(async(a,o)=>{const{message:t,files:e,authorName:s,authorEmail:r}=a.body||{},n={...m(a.body),message:t,files:e,authorName:s,authorEmail:r};if(!t)throw new p("Commit message cannot be empty",{field:"message"});i(n.projectId||`computer:${n.userId}:${n.cId}`,"INFO","Git commit",{...n,filesCount:e?e.length:"all"});const h=await d.commit(n);o.status(200).json(h)})},{path:"/add",method:"post",handler:c(async(a,o)=>{const{files:t}=a.body||{},e={...m(a.body),files:t};i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git add",{...e,filesCount:t?t.length:"all"});const s=await d.add(e);o.status(200).json(s)})},{path:"/unstage",method:"post",handler:c(async(a,o)=>{const{files:t}=a.body||{},e={...m(a.body),files:t};i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git unstage",{...e,filesCount:t?t.length:"all"});const s=await d.unstage(e);o.status(200).json(s)})},{path:"/discard",method:"post",handler:c(async(a,o)=>{const{files:t}=a.body||{},e={...m(a.body),files:t};i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git discard",{...e,filesCount:t?t.length:"all"});const s=await d.discard(e);o.status(200).json(s)})},{path:"/log",method:"get",handler:c(async(a,o)=>{const{maxCount:t,branch:e,skip:s,filePath:r}=a.query||{},n={...m(a.query),maxCount:t?parseInt(t,10):50,skip:s?parseInt(s,10):0,branch:e,filePath:r};i(n.projectId||`computer:${n.userId}:${n.cId}`,"INFO","Git log",n);const h=await d.logHistory(n);o.status(200).json(h)})},{path:"/diff",method:"post",handler:c(async(a,o)=>{const{from:t,to:e,paths:s,source:r}=a.body||{},n={...m(a.body),from:t,to:e,paths:s,source:r};i(n.projectId||`computer:${n.userId}:${n.cId}`,"INFO","Git diff",{from:t,to:e});const h=await d.diff(n);o.status(200).json(h)})},{path:"/file-content",method:"post",handler:c(async(a,o)=>{const{ref:t,filePath:e}=a.body||{},s={...m(a.body),ref:t,filePath:e};i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git file content",{ref:t,filePath:e});const r=await d.fileContent(s);o.status(200).json(r)})},{path:"/reset",method:"post",handler:c(async(a,o)=>{const{target:t,mode:e}=a.body||{},s={...m(a.body),target:t,mode:e||"mixed"};if(!t)throw new p("Reset target cannot be empty",{field:"target"});i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git reset",{target:t,mode:e});const r=await d.reset(s);o.status(200).json(r)})},{path:"/revert",method:"post",handler:c(async(a,o)=>{const{target:t,message:e,authorName:s,authorEmail:r}=a.body||{},n={...m(a.body),target:t,message:e,authorName:s,authorEmail:r};if(!t)throw new p("Revert target cannot be empty",{field:"target"});i(n.projectId||`computer:${n.userId}:${n.cId}`,"INFO","Git revert",{target:t});const h=await d.revert(n);o.status(200).json(h)})},{path:"/checkout",method:"post",handler:c(async(a,o)=>{const{target:t}=a.body||{},e={...m(a.body),target:t};if(!t)throw new p("Checkout target cannot be empty",{field:"target"});i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git checkout files",{target:t});const s=await d.checkout(e);o.status(200).json(s)})},{path:"/tags",method:"get",handler:c(async(a,o)=>{const t=m(a.query);i(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git list tags",t);const e=await d.listTags(t);o.status(200).json(e)})},{path:"/tag-create",method:"post",handler:c(async(a,o)=>{const{tagName:t,message:e}=a.body||{},s={...m(a.body),tagName:t,message:e};if(!t)throw new p("Tag name cannot be empty",{field:"tagName"});i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git create tag",{tagName:t});const r=await d.createTag(s);o.status(200).json(r)})},{path:"/tag-delete",method:"post",handler:c(async(a,o)=>{const{tagName:t}=a.body||{},e={...m(a.body),tagName:t};if(!t)throw new p("Tag name cannot be empty",{field:"tagName"});i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git delete tag",{tagName:t});const s=await d.deleteTag(e);o.status(200).json(s)})},{path:"/branches",method:"get",handler:c(async(a,o)=>{const t=m(a.query);i(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git list branches",t);const e=await d.listBranches(t);o.status(200).json(e)})},{path:"/branch-create",method:"post",handler:c(async(a,o)=>{const{branchName:t,startPoint:e}=a.body||{},s={...m(a.body),branchName:t,startPoint:e};if(!t)throw new p("Branch name cannot be empty",{field:"branchName"});i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git create branch",{branchName:t,startPoint:e});const r=await d.createBranch(s);o.status(200).json(r)})},{path:"/branch-switch",method:"post",handler:c(async(a,o)=>{const{branchName:t}=a.body||{},e={...m(a.body),branchName:t};if(!t)throw new p("Branch name cannot be empty",{field:"branchName"});i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git switch branch",{branchName:t});const s=await d.switchBranch(e);o.status(200).json(s)})},{path:"/branch-delete",method:"post",handler:c(async(a,o)=>{const{branchName:t,force:e}=a.body||{},s={...m(a.body),branchName:t,force:e===!0};if(!t)throw new p("Branch name cannot be empty",{field:"branchName"});i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git delete branch",{branchName:t,force:e});const r=await d.deleteBranch(s);o.status(200).json(r)})}];y.forEach(a=>{a.middleware?u[a.method](a.path,a.middleware,a.handler):u[a.method](a.path,a.handler)});export default u;
@@ -1,8 +1,8 @@
1
- import A from"path";import n from"fs";import ce from"crypto";import f from"isomorphic-git";import{createPatch as le}from"diff";import _ from"../appConfig/index.js";import{log as m}from"../utils/log/logUtils.js";import{ValidationError as O,BusinessError as H,SystemError as I,ResourceError as C}from"../utils/error/errorHandler.js";import{resolveProjectPath as fe}from"../utils/common/projectPathUtils.js";import{isGitRepo as de,ensureGitRepo as k,ensureGitignore as ue,addAll as D,getDefaultAuthor as he}from"../utils/git/gitUtils.js";function x(i){const{workspaceType:e,projectId:s,isolationContext:t,userId:a,cId:r}=i||{};if(!e||!["pageApp","taskAgent"].includes(e))throw new O("workspaceType is required and must be pageApp or taskAgent",{field:"workspaceType"});if(e==="taskAgent"){if(!a||!r)throw new O("taskAgent mode requires userId and cId",{field:"userId/cId"});const c=A.join(_.COMPUTER_WORKSPACE_DIR,String(a),String(r));if(!n.existsSync(c))throw new C("Computer workspace does not exist",{userId:a,cId:r});return{targetPath:c,logId:`computer:${a}:${r}`}}if(!s)throw new O("pageApp mode requires projectId",{field:"projectId"});const o=fe(s,t||{});if(!n.existsSync(o))throw new C("Project does not exist",{projectId:s});return{targetPath:o,logId:s}}function j(i){for(let e=0;e<Math.min(i.length,8e3);e++)if(i[e]===0)return!0;return!1}function v(i){const e=Buffer.from(`blob ${i.length}\0`);return ce.createHash("sha1").update(Buffer.concat([e,i])).digest("hex")}function ge(i,e,s,t,a,r,o){const u=le(i,t?e:"",a?s:"",t?`a/${i}`:"/dev/null",a?`b/${i}`:"/dev/null").split(`
2
- `).slice(4);u.length>0&&u[u.length-1]===""&&u.pop();const h=[];let g=0,d=0;for(const P of u){if(P.startsWith("@@")){const R=P.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);if(R){const $=R[1],N=R[2]!==void 0?parseInt(R[2],10):1,T=R[3],p=R[4]!==void 0?parseInt(R[4],10):1,B=N===1?$:`${$},${N}`,G=p===1?T:`${T},${p}`;h.push(`@@ -${B} +${G} @@`);continue}}P.startsWith("+")&&!P.startsWith("+++")?g++:P.startsWith("-")&&!P.startsWith("---")&&d++,h.push(P)}const l=t&&e!==""&&!e.endsWith(`
3
- `),y=a&&s!==""&&!s.endsWith(`
4
- `);if((l||y)&&h.length>0){let P=0;for(let p=0;p<h.length;p++)h[p].startsWith("@@")&&(P=p);let R=-1,$=-1,N=-1;for(let p=h.length-1;p>P;p--){const B=h[p];B.startsWith("-")&&!B.startsWith("---")&&R<0?R=p:B.startsWith("+")&&!B.startsWith("++")&&$<0?$=p:B.startsWith(" ")&&N<0&&(N=p)}const T=Math.max(R,$,N);if(T>=0){const p=h[T];if(p.startsWith(" "))(l||y)&&h.splice(T+1,0,"\");else if(p.startsWith("+")){let B=-1;for(let G=T-1;G>P;G--)if(h[G].startsWith("-")&&!h[G].startsWith("---")){B=G;break}B>=0&&l?(h.splice(B+1,0,"\"),y&&h.splice(T+2,0,"\")):y&&h.splice(T+1,0,"\")}else p.startsWith("-")&&l&&h.splice(T+1,0,"\")}}const S=t&&r?v(r).substring(0,7):"0000000",E=a&&o?v(o).substring(0,7):"0000000",F=[];return F.push(`diff --git a/${i} b/${i}`),t?a?F.push(`index ${S}..${E} 100644`):(F.push("deleted file mode 100644"),F.push(`index ${S}..0000000`)):(F.push("new file mode 100644"),F.push(`index 0000000..${E}`)),F.push(`--- ${t?`a/${i}`:"/dev/null"}`),F.push(`+++ ${a?`b/${i}`:"/dev/null"}`),{diff:[...F,...h].join(`
1
+ import A from"path";import i from"fs";import ft from"crypto";import l from"isomorphic-git";import{createPatch as dt}from"diff";import B from"../appConfig/index.js";import{log as w}from"../utils/log/logUtils.js";import{ValidationError as v,BusinessError as G,SystemError as k,ResourceError as D}from"../utils/error/errorHandler.js";import{resolveProjectPath as ut}from"../utils/common/projectPathUtils.js";import{isGitRepo as ht,ensureGitRepo as T,ensureGitignore as M,addAll as U,getDefaultAuthor as gt}from"../utils/git/gitUtils.js";function P(n){const{workspaceType:t,projectId:a,isolationContext:e,userId:s,cId:r}=n||{};if(!t||!["pageApp","taskAgent"].includes(t))throw new v("workspaceType is required and must be pageApp or taskAgent",{field:"workspaceType"});if(t==="taskAgent"){if(!s||!r)throw new v("taskAgent mode requires userId and cId",{field:"userId/cId"});const c=A.join(B.COMPUTER_WORKSPACE_DIR,String(s),String(r));if(!i.existsSync(c))throw new D("Computer workspace does not exist",{userId:s,cId:r});return{targetPath:c,logId:`computer:${s}:${r}`}}if(!a)throw new v("pageApp mode requires projectId",{field:"projectId"});const o=ut(a,e||{});if(!i.existsSync(o))throw new D("Project does not exist",{projectId:a});return{targetPath:o,logId:a}}function L(n){for(let t=0;t<Math.min(n.length,8e3);t++)if(n[t]===0)return!0;return!1}function C(n){const t=Buffer.from(`blob ${n.length}\0`);return ft.createHash("sha1").update(Buffer.concat([t,n])).digest("hex")}function mt(n,t,a,e,s,r,o){const h=dt(n,e?t:"",s?a:"",e?`a/${n}`:"/dev/null",s?`b/${n}`:"/dev/null").split(`
2
+ `).slice(4);h.length>0&&h[h.length-1]===""&&h.pop();const g=[];let u=0,d=0;for(const m of h){if(m.startsWith("@@")){const y=m.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);if(y){const S=y[1],F=y[2]!==void 0?parseInt(y[2],10):1,N=y[3],R=y[4]!==void 0?parseInt(y[4],10):1,$=F===1?S:`${S},${F}`,H=R===1?N:`${N},${R}`;g.push(`@@ -${$} +${H} @@`);continue}}m.startsWith("+")&&!m.startsWith("+++")?u++:m.startsWith("-")&&!m.startsWith("---")&&d++,g.push(m)}const f=e&&t!==""&&!t.endsWith(`
3
+ `),b=s&&a!==""&&!a.endsWith(`
4
+ `);if((f||b)&&g.length>0){let m=0;for(let R=0;R<g.length;R++)g[R].startsWith("@@")&&(m=R);let y=-1,S=-1,F=-1;for(let R=g.length-1;R>m;R--){const $=g[R];$.startsWith("-")&&!$.startsWith("---")&&y<0?y=R:$.startsWith("+")&&!$.startsWith("++")&&S<0?S=R:$.startsWith(" ")&&F<0&&(F=R)}const N=Math.max(y,S,F);if(N>=0){const R=g[N];if(R.startsWith(" "))(f||b)&&g.splice(N+1,0,"\");else if(R.startsWith("+")){let $=-1;for(let H=N-1;H>m;H--)if(g[H].startsWith("-")&&!g[H].startsWith("---")){$=H;break}$>=0&&f?(g.splice($+1,0,"\"),b&&g.splice(N+2,0,"\")):b&&g.splice(N+1,0,"\")}else R.startsWith("-")&&f&&g.splice(N+1,0,"\")}}const O=e&&r?C(r).substring(0,7):"0000000",I=s&&o?C(o).substring(0,7):"0000000",E=[];return E.push(`diff --git a/${n} b/${n}`),e?s?E.push(`index ${O}..${I} 100644`):(E.push("deleted file mode 100644"),E.push(`index ${O}..0000000`)):(E.push("new file mode 100644"),E.push(`index 0000000..${I}`)),E.push(`--- ${e?`a/${n}`:"/dev/null"}`),E.push(`+++ ${s?`b/${n}`:"/dev/null"}`),{diff:[...E,...g].join(`
5
5
  `)+`
6
- `,insertions:g,deletions:d}}async function M(i){return f.resolveRef({fs:n,dir:i,ref:"HEAD"})}async function W(i,e,s){try{const{blob:t}=await f.readBlob({fs:n,dir:i,oid:e,filepath:s}),a=Buffer.from(t);return{content:a.toString("utf8"),buf:a}}catch{return null}}async function L(i={}){const{targetPath:e,logId:s}=x(i);if(de(e))return{success:!0,message:"Git repository already initialized",logId:s,alreadyExists:!0};try{return await f.init({fs:n,dir:e,defaultBranch:"main"}),ue(e),await f.setConfig({fs:n,dir:e,path:"user.name",value:_.GIT_DEFAULT_AUTHOR_NAME}),await f.setConfig({fs:n,dir:e,path:"user.email",value:_.GIT_DEFAULT_AUTHOR_EMAIL}),m(s,"INFO","Git repository initialized",{logId:s,targetPath:e}),{success:!0,message:"Git repository initialized successfully",logId:s,alreadyExists:!1}}catch(t){throw m(s,"ERROR","Failed to initialize Git repository",{logId:s,error:t.message}),new I("Failed to initialize Git repository",{originalError:t.message})}}async function U(i={}){const{targetPath:e,logId:s}=x(i);await k(e);try{const t=await f.currentBranch({fs:n,dir:e,fullname:!1}),a=await f.statusMatrix({fs:n,dir:e}),r=[],o=[],c=[],w=[],u=[];for(const[h,g,d,l]of a)l===3?(r.push(h),c.push(h)):l===2?r.push(h):l===0&&g===1&&(r.push(h),w.push(h)),d===2&&l!==0?o.push(h):d===0&&l!==0&&w.push(h),l===0&&d!==0&&u.push(h);return{success:!0,logId:s,current:t,staged:[...new Set(r)],modified:[...new Set(o)],created:[...new Set(c)],deleted:[...new Set(w)],untracked:[...new Set(u)],conflicted:[],ahead:0,behind:0,tracking:null}}catch(t){throw m(s,"ERROR","Failed to get Git status",{logId:s,error:t.message}),new I("Failed to get Git status",{originalError:t.message})}}async function q(i={}){const{message:e,files:s,authorName:t,authorEmail:a}=i;if(!e||typeof e!="string")throw new O("Commit message cannot be empty",{field:"message"});const{targetPath:r,logId:o}=x(i);await k(r);try{if(Array.isArray(s)&&s.length>0){const g=s.filter(d=>n.existsSync(A.join(r,d)));await Promise.all(g.map(d=>f.add({fs:n,dir:r,filepath:d})))}else await D(r);const w=(await f.statusMatrix({fs:n,dir:r})).some(([,,,g])=>g!==1);if(!w)return{success:!0,message:"Nothing to commit",logId:o,nothingToCommit:!0};const u={name:t||_.GIT_DEFAULT_AUTHOR_NAME,email:a||_.GIT_DEFAULT_AUTHOR_EMAIL},h=await f.commit({fs:n,dir:r,message:e,author:u});return m(o,"INFO","Git commit successful",{logId:o,commitHash:h,message:e}),{success:!0,message:"Commit successful",logId:o,commit:h,summary:{changes:w?1:0}}}catch(c){throw m(o,"ERROR","Failed to commit",{logId:o,error:c.message}),new I("Failed to commit",{originalError:c.message})}}async function z(i={}){const{files:e}=i,{targetPath:s,logId:t}=x(i);await k(s);try{return Array.isArray(e)&&e.length>0?await Promise.all(e.map(a=>f.add({fs:n,dir:s,filepath:a}))):await D(s),m(t,"INFO","Git add successful",{logId:t,filesCount:e?e.length:"all"}),{success:!0,message:"Files staged successfully",logId:t}}catch(a){throw m(t,"ERROR","Failed to add files",{logId:t,error:a.message}),new I("Failed to add files",{originalError:a.message})}}async function K(i={}){const{files:e}=i,{targetPath:s,logId:t}=x(i);await k(s);try{if(Array.isArray(e)&&e.length>0){for(const o of e)await f.resetIndex({fs:n,dir:s,filepath:o});return m(t,"INFO","Git unstage specified files",{logId:t,files:e}),{success:!0,message:"Specified files unstaged successfully",logId:t,files:e}}const r=(await f.statusMatrix({fs:n,dir:s})).filter(([,,,o])=>o!==1).map(([o])=>o);for(const o of r)await f.resetIndex({fs:n,dir:s,filepath:o});return m(t,"INFO","Git unstage all files",{logId:t}),{success:!0,message:"All files unstaged successfully",logId:t,files:"all"}}catch(a){throw m(t,"ERROR","Failed to unstage",{logId:t,error:a.message}),new I("Failed to unstage files",{originalError:a.message})}}async function V(i,e){const s=new Set;for(const a of e){let r=A.dirname(a);for(;r&&r!==".";)s.add(r),r=A.dirname(r)}const t=[...s].sort((a,r)=>{const o=a.split(/[/\\]/).length;return r.split(/[/\\]/).length-o});for(const a of t){const r=A.join(i,a);try{(await n.promises.readdir(r)).length===0&&await n.promises.rmdir(r)}catch{}}}async function J(i={}){const{files:e}=i,{targetPath:s,logId:t}=x(i);await k(s);try{const a=await f.statusMatrix({fs:n,dir:s}),r=Array.isArray(e)&&e.length>0?new Set(e):null,o=[],c=[],w=[];for(const[g,d,l,y]of a)r&&!r.has(g)||(d!==0?(y!==1||l!==1)&&o.push(g):d===0&&y!==0?c.push(g):d===0&&y===0&&l!==0&&w.push(g));const u=await M(s);for(const g of o){const d=await W(s,u,g);if(!d)continue;const l=A.join(s,g);n.mkdirSync(A.dirname(l),{recursive:!0}),n.writeFileSync(l,d.buf),await f.add({fs:n,dir:s,filepath:g})}for(const g of c){try{await f.remove({fs:n,dir:s,filepath:g})}catch{}const d=A.join(s,g);n.existsSync(d)&&await n.promises.unlink(d)}c.length>0&&await V(s,c);for(const g of w){const d=A.join(s,g);n.existsSync(d)&&await n.promises.unlink(d)}w.length>0&&await V(s,w);const h=o.length+c.length+w.length;return m(t,"INFO","Git discard",{logId:t,trackedFiles:o.length,newFiles:c.length,untrackedFiles:w.length}),{success:!0,message:"Files discarded successfully",logId:t,discardedCount:h,trackedFiles:o,newFiles:c,untrackedFiles:w}}catch(a){throw m(t,"ERROR","Failed to discard",{logId:t,error:a.message}),new I("Failed to discard files",{originalError:a.message})}}async function Q(i={}){const{maxCount:e=50,branch:s,skip:t=0,filePath:a}=i,{targetPath:r,logId:o}=x(i);await k(r);try{const c=Math.min(Math.max(1,e),500),w=Math.max(0,t),u={fs:n,dir:r,depth:c+w};s&&(u.ref=s),a&&(u.filepath=a);const d=(await f.log(u)).slice(w,w+c).map(l=>({hash:l.oid,date:new Date(l.commit.author.timestamp*1e3).toISOString(),message:l.commit.message,author_name:l.commit.author.name,author_email:l.commit.author.email}));return{success:!0,logId:o,commits:d,total:d.length}}catch(c){throw m(o,"ERROR","Failed to get Git log",{logId:o,error:c.message}),new I("Failed to get Git log",{originalError:c.message})}}async function X(i={}){const{source:e="worktree",from:s,to:t,paths:a}=i,{targetPath:r,logId:o}=x(i);await k(r);try{const c=Array.isArray(a)&&a.length>0?new Set(a):null;let w="";const u=[];let h=0,g=0;async function d(l,y,S){const E=!!y,F=!!S;if(E&&F&&y.content===S.content)return;let b=!1;if(E&&j(y.buf)&&(b=!0),F&&!b&&j(S.buf)&&(b=!0),b){const N=E?v(y.buf).substring(0,7):"0000000",T=F?v(S.buf).substring(0,7):"0000000",p=[`diff --git a/${l} b/${l}`];E?F?p.push(`index ${N}..${T} 100644`):(p.push("deleted file mode 100644"),p.push(`index ${N}..0000000`)):(p.push("new file mode 100644"),p.push(`index 0000000..${T}`)),p.push(`Binary files ${E?`a/${l}`:"/dev/null"} and ${F?`b/${l}`:"/dev/null"} differ`),w+=p.join(`
6
+ `,insertions:u,deletions:d}}async function W(n){return l.resolveRef({fs:i,dir:n,ref:"HEAD"})}async function _(n,t,a){try{const{blob:e}=await l.readBlob({fs:i,dir:n,oid:t,filepath:a}),s=Buffer.from(e);return{content:s.toString("utf8"),buf:s}}catch{return null}}async function q(n={}){const{targetPath:t,logId:a}=P(n);if(ht(t))return{success:!0,message:"Git repository already initialized",logId:a,alreadyExists:!0};try{return await l.init({fs:i,dir:t,defaultBranch:"main"}),M(t),await l.setConfig({fs:i,dir:t,path:"user.name",value:B.GIT_DEFAULT_AUTHOR_NAME}),await l.setConfig({fs:i,dir:t,path:"user.email",value:B.GIT_DEFAULT_AUTHOR_EMAIL}),w(a,"INFO","Git repository initialized",{logId:a,targetPath:t}),{success:!0,message:"Git repository initialized successfully",logId:a,alreadyExists:!1}}catch(e){throw w(a,"ERROR","Failed to initialize Git repository",{logId:a,error:e.message}),new k("Failed to initialize Git repository",{originalError:e.message})}}async function z(n={}){const{targetPath:t,logId:a}=P(n);await T(t);try{const e=await l.currentBranch({fs:i,dir:t,fullname:!1}),s=await l.statusMatrix({fs:i,dir:t}),r=[],o=[],c=[],p=[],h=[];for(const[g,u,d,f]of s)u!==f&&r.push(g),u===0&&f!==0&&c.push(g),(u===1&&f===0||d===0&&f!==0)&&p.push(g),f!==0&&d!==0&&d!==f&&o.push(g),u===0&&f===0&&d!==0&&h.push(g);return{success:!0,logId:a,current:e,staged:[...new Set(r)],modified:[...new Set(o)],created:[...new Set(c)],deleted:[...new Set(p)],untracked:[...new Set(h)],conflicted:[],ahead:0,behind:0,tracking:null}}catch(e){throw w(a,"ERROR","Failed to get Git status",{logId:a,error:e.message}),new k("Failed to get Git status",{originalError:e.message})}}async function K(n={}){const{message:t,files:a,authorName:e,authorEmail:s}=n;if(!t||typeof t!="string")throw new v("Commit message cannot be empty",{field:"message"});const{targetPath:r,logId:o}=P(n);await T(r);try{if(Array.isArray(a)&&a.length>0){const u=a.filter(d=>i.existsSync(A.join(r,d)));await Promise.all(u.map(d=>l.add({fs:i,dir:r,filepath:d})))}else await U(r);const p=(await l.statusMatrix({fs:i,dir:r})).some(([,,,u])=>u!==1);if(!p)return{success:!0,message:"Nothing to commit",logId:o,nothingToCommit:!0};const h={name:e||B.GIT_DEFAULT_AUTHOR_NAME,email:s||B.GIT_DEFAULT_AUTHOR_EMAIL},g=await l.commit({fs:i,dir:r,message:t,author:h});return w(o,"INFO","Git commit successful",{logId:o,commitHash:g,message:t}),{success:!0,message:"Commit successful",logId:o,commit:g,summary:{changes:p?1:0}}}catch(c){throw w(o,"ERROR","Failed to commit",{logId:o,error:c.message}),new k("Failed to commit",{originalError:c.message})}}async function V(n={}){const{files:t}=n,{targetPath:a,logId:e}=P(n);await T(a);try{return Array.isArray(t)&&t.length>0?await Promise.all(t.map(s=>l.add({fs:i,dir:a,filepath:s}))):await U(a),w(e,"INFO","Git add successful",{logId:e,filesCount:t?t.length:"all"}),{success:!0,message:"Files staged successfully",logId:e}}catch(s){throw w(e,"ERROR","Failed to add files",{logId:e,error:s.message}),new k("Failed to add files",{originalError:s.message})}}async function J(n={}){const{files:t}=n,{targetPath:a,logId:e}=P(n);await T(a);try{if(Array.isArray(t)&&t.length>0){for(const o of t)await l.resetIndex({fs:i,dir:a,filepath:o});return w(e,"INFO","Git unstage specified files",{logId:e,files:t}),{success:!0,message:"Specified files unstaged successfully",logId:e,files:t}}const r=(await l.statusMatrix({fs:i,dir:a})).filter(([,,,o])=>o!==1).map(([o])=>o);for(const o of r)await l.resetIndex({fs:i,dir:a,filepath:o});return w(e,"INFO","Git unstage all files",{logId:e}),{success:!0,message:"All files unstaged successfully",logId:e,files:"all"}}catch(s){throw w(e,"ERROR","Failed to unstage",{logId:e,error:s.message}),new k("Failed to unstage files",{originalError:s.message})}}async function j(n,t){const a=new Set;for(const s of t){let r=A.dirname(s);for(;r&&r!==".";)a.add(r),r=A.dirname(r)}const e=[...a].sort((s,r)=>{const o=s.split(/[/\\]/).length;return r.split(/[/\\]/).length-o});for(const s of e){const r=A.join(n,s);try{(await i.promises.readdir(r)).length===0&&await i.promises.rmdir(r)}catch{}}}async function Q(n={}){const{files:t}=n,{targetPath:a,logId:e}=P(n);await T(a);try{const s=await l.statusMatrix({fs:i,dir:a}),r=Array.isArray(t)&&t.length>0?new Set(t):null,o=[],c=[],p=[];for(const[u,d,f,b]of s)r&&!r.has(u)||(d!==0?(b!==1||f!==1)&&o.push(u):d===0&&b!==0?c.push(u):d===0&&b===0&&f!==0&&p.push(u));const h=await W(a);for(const u of o){const d=await _(a,h,u);if(!d)continue;const f=A.join(a,u);i.mkdirSync(A.dirname(f),{recursive:!0}),i.writeFileSync(f,d.buf),await l.add({fs:i,dir:a,filepath:u})}for(const u of c){try{await l.remove({fs:i,dir:a,filepath:u})}catch{}const d=A.join(a,u);i.existsSync(d)&&await i.promises.unlink(d)}c.length>0&&await j(a,c);for(const u of p){const d=A.join(a,u);i.existsSync(d)&&await i.promises.unlink(d)}p.length>0&&await j(a,p);const g=o.length+c.length+p.length;return w(e,"INFO","Git discard",{logId:e,trackedFiles:o.length,newFiles:c.length,untrackedFiles:p.length}),{success:!0,message:"Files discarded successfully",logId:e,discardedCount:g,trackedFiles:o,newFiles:c,untrackedFiles:p}}catch(s){throw w(e,"ERROR","Failed to discard",{logId:e,error:s.message}),new k("Failed to discard files",{originalError:s.message})}}async function X(n={}){const{maxCount:t=50,branch:a,skip:e=0,filePath:s}=n,{targetPath:r,logId:o}=P(n);await T(r);try{const c=Math.min(Math.max(1,t),500),p=Math.max(0,e),h={fs:i,dir:r,depth:c+p};a&&(h.ref=a),s&&(h.filepath=s);const d=(await l.log(h)).slice(p,p+c).map(f=>({hash:f.oid,date:new Date(f.commit.author.timestamp*1e3).toISOString(),message:f.commit.message,author_name:f.commit.author.name,author_email:f.commit.author.email}));return{success:!0,logId:o,commits:d,total:d.length}}catch(c){throw w(o,"ERROR","Failed to get Git log",{logId:o,error:c.message}),new k("Failed to get Git log",{originalError:c.message})}}async function Y(n={}){const{source:t="worktree",from:a,to:e,paths:s}=n,{targetPath:r,logId:o}=P(n);await T(r);try{const c=Array.isArray(s)&&s.length>0?new Set(s):null;let p="";const h=[];let g=0,u=0;async function d(f,b,O){const I=!!b,E=!!O;if(I&&E&&b.content===O.content)return;let x=!1;if(I&&L(b.buf)&&(x=!0),E&&!x&&L(O.buf)&&(x=!0),x){const F=I?C(b.buf).substring(0,7):"0000000",N=E?C(O.buf).substring(0,7):"0000000",R=[`diff --git a/${f} b/${f}`];I?E?R.push(`index ${F}..${N} 100644`):(R.push("deleted file mode 100644"),R.push(`index ${F}..0000000`)):(R.push("new file mode 100644"),R.push(`index 0000000..${N}`)),R.push(`Binary files ${I?`a/${f}`:"/dev/null"} and ${E?`b/${f}`:"/dev/null"} differ`),p+=R.join(`
7
7
  `)+`
8
- `,u.push({file:l,changes:0,insertions:0,deletions:0,binary:!0});return}const{diff:P,insertions:R,deletions:$}=ge(l,E?y.content:"",F?S.content:"",E,F,E?y.buf:null,F?S.buf:null);w+=P,u.push({file:l,changes:R+$,insertions:R,deletions:$,binary:!1}),h+=R,g+=$}if(e==="commit"){let l,y;if(s&&t)l=s,y=t;else if(s){const b=await f.readCommit({fs:n,dir:r,oid:s});b.commit.parent&&b.commit.parent.length>0?(l=b.commit.parent[0],y=s):(l=null,y=s)}else throw new O("source=commit requires at least 'from'",{field:"from"});const S=l?new Set(await f.listFiles({fs:n,dir:r,ref:l})):new Set,E=new Set(await f.listFiles({fs:n,dir:r,ref:y})),F=new Set([...S,...E]);for(const b of F){if(c&&!c.has(b))continue;const P=S.has(b)?await W(r,l,b):null,R=E.has(b)?await W(r,y,b):null;await d(b,P,R)}}else{const l=await M(r),y=await f.statusMatrix({fs:n,dir:r});for(const[S,E,F,b]of y){if(c&&!c.has(S))continue;if(e==="staged"){if(b===1||E===0&&b===0)continue}else if(E===0&&b===0||E===1&&F===1&&b===1)continue;const P=E!==0?await W(r,l,S):null;let R=null;if(F!==0){const $=A.join(r,S);if(n.existsSync($))try{const N=n.readFileSync($);R={content:N.toString("utf8"),buf:N}}catch{}}await d(S,P,R)}}return{success:!0,logId:o,source:e,diff:w,summary:{files:u,insertions:h,deletions:g}}}catch(c){throw c instanceof O?c:(m(o,"ERROR","Failed to get Git diff",{logId:o,error:c.message}),new I("Failed to get Git diff",{originalError:c.message}))}}async function Y(i={}){const{ref:e="HEAD",filePath:s}=i;if(!s)throw new O("filePath is required",{field:"filePath"});const{targetPath:t,logId:a}=x(i);await k(t);try{let r;if(e==="worktree"||e==="staged"||e===""){const o=A.join(t,s);r=n.existsSync(o)?n.readFileSync(o,"utf8"):""}else try{const o=await f.resolveRef({fs:n,dir:t,ref:e}),{blob:c}=await f.readBlob({fs:n,dir:t,oid:o,filepath:s});r=Buffer.from(c).toString("utf8")}catch{r=""}return{success:!0,logId:a,filePath:s,ref:e,content:r}}catch(r){throw r instanceof C?r:(m(a,"ERROR","Failed to get file content",{logId:a,ref:e,filePath:s,error:r.message}),new I("Failed to get file content",{originalError:r.message}))}}async function Z(i={}){const{target:e,mode:s="mixed"}=i;if(!e)throw new O("Reset target cannot be empty",{field:"target"});if(!["soft","mixed","hard"].includes(s))throw new O("Mode must be soft, mixed or hard",{field:"mode"});const{targetPath:t,logId:a}=x(i);await k(t);try{const r=await f.log({fs:n,dir:t,depth:1}),o=r.length>0?r[0].oid:null,c=await f.currentBranch({fs:n,dir:t,fullname:!1});if(!c)throw new H("Cannot reset: detached HEAD");const w=s==="hard"?new Set(await f.listFiles({fs:n,dir:t,ref:"HEAD"})):null;if(await f.writeRef({fs:n,dir:t,ref:`refs/heads/${c}`,value:e,force:!0}),s==="mixed"||s==="hard"){const u=await f.listFiles({fs:n,dir:t,ref:e}),h=new Set(u);for(const d of u)await f.resetIndex({fs:n,dir:t,filepath:d,ref:e});const g=await f.statusMatrix({fs:n,dir:t});for(const[d,,,l]of g)if(!h.has(d)&&l!==0)try{await f.remove({fs:n,dir:t,filepath:d})}catch{}}if(s==="hard"){const u=await f.listFiles({fs:n,dir:t,ref:e}),h=new Set(u);for(const g of u){const d=await W(t,e,g);if(!d)continue;const l=A.join(t,g);n.mkdirSync(A.dirname(l),{recursive:!0}),n.writeFileSync(l,d.buf)}if(w){for(const g of w)if(!h.has(g)){const d=A.join(t,g);n.existsSync(d)&&n.unlinkSync(d)}}}return m(a,"INFO","Git reset successful",{logId:a,target:e,mode:s,previousHead:o}),{success:!0,message:`Reset (${s}) to ${e} successful`,logId:a,target:e,mode:s,previousHead:o}}catch(r){throw r instanceof H?r:(m(a,"ERROR","Failed to reset",{logId:a,target:e,mode:s,error:r.message}),new I("Failed to reset",{originalError:r.message}))}}async function ee(i={}){const{target:e}=i;if(!e)throw new O("Checkout target cannot be empty",{field:"target"});const{targetPath:s,logId:t}=x(i);await k(s);try{const a=await f.listFiles({fs:n,dir:s,ref:e});for(const r of a){const o=await W(s,e,r);if(!o)continue;const c=A.join(s,r);n.mkdirSync(A.dirname(c),{recursive:!0}),n.writeFileSync(c,o.buf),await f.add({fs:n,dir:s,filepath:r})}return m(t,"INFO","Git checkout files successful",{logId:t,target:e}),{success:!0,message:`Checkout files from ${e} successful`,logId:t,target:e}}catch(a){throw m(t,"ERROR","Failed to checkout files",{logId:t,target:e,error:a.message}),new I("Failed to checkout files",{originalError:a.message})}}async function te(i={}){const{targetPath:e,logId:s}=x(i);await k(e);try{const t=await f.listTags({fs:n,dir:e});return{success:!0,logId:s,tags:t,latest:t.length>0?t[t.length-1]:null}}catch(t){throw m(s,"ERROR","Failed to list tags",{logId:s,error:t.message}),new I("Failed to list tags",{originalError:t.message})}}async function se(i={}){const{tagName:e,message:s}=i;if(!e)throw new O("Tag name cannot be empty",{field:"tagName"});const{targetPath:t,logId:a}=x(i);await k(t);try{return s?await f.annotatedTag({fs:n,dir:t,ref:e,message:s,tagger:he()}):await f.tag({fs:n,dir:t,ref:e}),m(a,"INFO","Git tag created",{logId:a,tagName:e,annotated:!!s}),{success:!0,message:"Tag created successfully",logId:a,tagName:e}}catch(r){throw m(a,"ERROR","Failed to create tag",{logId:a,tagName:e,error:r.message}),new I("Failed to create tag",{originalError:r.message})}}async function ae(i={}){const{tagName:e}=i;if(!e)throw new O("Tag name cannot be empty",{field:"tagName"});const{targetPath:s,logId:t}=x(i);await k(s);try{return await f.deleteRef({fs:n,dir:s,ref:`refs/tags/${e}`}),m(t,"INFO","Git tag deleted",{logId:t,tagName:e}),{success:!0,message:"Tag deleted successfully",logId:t,tagName:e}}catch(a){throw m(t,"ERROR","Failed to delete tag",{logId:t,tagName:e,error:a.message}),new I("Failed to delete tag",{originalError:a.message})}}async function re(i={}){const{targetPath:e,logId:s}=x(i);await k(e);try{const t=await f.listBranches({fs:n,dir:e}),a=await f.currentBranch({fs:n,dir:e,fullname:!1}),r={};for(const o of t)r[o]={name:o,current:o===a};return{success:!0,logId:s,branches:r,current:a}}catch(t){throw m(s,"ERROR","Failed to list branches",{logId:s,error:t.message}),new I("Failed to list branches",{originalError:t.message})}}async function ie(i={}){const{branchName:e,startPoint:s}=i;if(!e)throw new O("Branch name cannot be empty",{field:"branchName"});const{targetPath:t,logId:a}=x(i);await k(t);try{const r=s||"HEAD";return await f.branch({fs:n,dir:t,ref:e,object:r,checkout:!0}),m(a,"INFO","Git branch created",{logId:a,branchName:e,startPoint:s}),{success:!0,message:"Branch created and switched to",logId:a,branchName:e}}catch(r){throw m(a,"ERROR","Failed to create branch",{logId:a,branchName:e,error:r.message}),new I("Failed to create branch",{originalError:r.message})}}async function ne(i={}){const{branchName:e}=i;if(!e)throw new O("Branch name cannot be empty",{field:"branchName"});const{targetPath:s,logId:t}=x(i);await k(s);try{const a=await f.statusMatrix({fs:n,dir:s});if(a.some(([o,c,w,u])=>c===0&&u===0?!1:w!==1||u!==1)){const o=a.filter(([,u])=>u!==0),c=o.filter(([,,u])=>u!==1).map(([u])=>u),w=o.filter(([,,,u])=>u!==1).map(([u])=>u);throw new H("Working directory is not clean, please commit or stash your changes before switching branches",{staged:w,modified:c})}return await f.checkout({fs:n,dir:s,ref:e}),m(t,"INFO","Git branch switched",{logId:t,branchName:e}),{success:!0,message:"Branch switched successfully",logId:t,branchName:e}}catch(a){throw a instanceof H?a:(m(t,"ERROR","Failed to switch branch",{logId:t,branchName:e,error:a.message}),new I("Failed to switch branch",{originalError:a.message}))}}async function oe(i={}){const{branchName:e,force:s=!1}=i;if(!e)throw new O("Branch name cannot be empty",{field:"branchName"});const{targetPath:t,logId:a}=x(i);await k(t);try{if(await f.currentBranch({fs:n,dir:t,fullname:!1})===e)throw new H("Cannot delete the current branch, please switch to another branch first");return await f.deleteBranch({fs:n,dir:t,ref:e,force:s}),m(a,"INFO","Git branch deleted",{logId:a,branchName:e,force:s}),{success:!0,message:"Branch deleted successfully",logId:a,branchName:e}}catch(r){throw r instanceof H?r:(m(a,"ERROR","Failed to delete branch",{logId:a,branchName:e,error:r.message}),new I("Failed to delete branch",{originalError:r.message}))}}export{L as init,U as status,q as commit,z as add,K as unstage,J as discard,Q as logHistory,X as diff,Y as fileContent,Z as reset,ee as checkout,te as listTags,se as createTag,ae as deleteTag,re as listBranches,ie as createBranch,ne as switchBranch,oe as deleteBranch};export default{init:L,status:U,commit:q,add:z,unstage:K,discard:J,logHistory:Q,diff:X,fileContent:Y,reset:Z,checkout:ee,listTags:te,createTag:se,deleteTag:ae,listBranches:re,createBranch:ie,switchBranch:ne,deleteBranch:oe};
8
+ `,h.push({file:f,changes:0,insertions:0,deletions:0,binary:!0});return}const{diff:m,insertions:y,deletions:S}=mt(f,I?b.content:"",E?O.content:"",I,E,I?b.buf:null,E?O.buf:null);p+=m,h.push({file:f,changes:y+S,insertions:y,deletions:S,binary:!1}),g+=y,u+=S}if(t==="commit"){let f,b;if(a&&e)f=a,b=e;else if(a){const x=await l.readCommit({fs:i,dir:r,oid:a});x.commit.parent&&x.commit.parent.length>0?(f=x.commit.parent[0],b=a):(f=null,b=a)}else throw new v("source=commit requires at least 'from'",{field:"from"});const O=f?new Set(await l.listFiles({fs:i,dir:r,ref:f})):new Set,I=new Set(await l.listFiles({fs:i,dir:r,ref:b})),E=new Set([...O,...I]);for(const x of E){if(c&&!c.has(x))continue;const m=O.has(x)?await _(r,f,x):null,y=I.has(x)?await _(r,b,x):null;await d(x,m,y)}}else{const f=await W(r),b=await l.statusMatrix({fs:i,dir:r});for(const[O,I,E,x]of b){if(c&&!c.has(O))continue;if(t==="staged"){if(x===1||I===0&&x===0)continue}else if(I===0&&x===0||I===1&&E===1&&x===1)continue;const m=I!==0?await _(r,f,O):null;let y=null;if(E!==0){const S=A.join(r,O);if(i.existsSync(S))try{const F=i.readFileSync(S);y={content:F.toString("utf8"),buf:F}}catch{}}await d(O,m,y)}}return{success:!0,logId:o,source:t,diff:p,summary:{files:h,insertions:g,deletions:u}}}catch(c){throw c instanceof v?c:(w(o,"ERROR","Failed to get Git diff",{logId:o,error:c.message}),new k("Failed to get Git diff",{originalError:c.message}))}}async function Z(n={}){const{ref:t="HEAD",filePath:a}=n;if(!a)throw new v("filePath is required",{field:"filePath"});const{targetPath:e,logId:s}=P(n);await T(e);try{let r;if(t==="worktree"||t==="staged"||t===""){const o=A.join(e,a);r=i.existsSync(o)?i.readFileSync(o,"utf8"):""}else try{const o=await l.resolveRef({fs:i,dir:e,ref:t}),{blob:c}=await l.readBlob({fs:i,dir:e,oid:o,filepath:a});r=Buffer.from(c).toString("utf8")}catch{r=""}return{success:!0,logId:s,filePath:a,ref:t,content:r}}catch(r){throw r instanceof D?r:(w(s,"ERROR","Failed to get file content",{logId:s,ref:t,filePath:a,error:r.message}),new k("Failed to get file content",{originalError:r.message}))}}async function tt(n={}){const{target:t,mode:a="mixed"}=n;if(!t)throw new v("Reset target cannot be empty",{field:"target"});if(!["soft","mixed","hard"].includes(a))throw new v("Mode must be soft, mixed or hard",{field:"mode"});const{targetPath:e,logId:s}=P(n);await T(e);try{const r=await l.log({fs:i,dir:e,depth:1}),o=r.length>0?r[0].oid:null,c=await l.currentBranch({fs:i,dir:e,fullname:!1});if(!c)throw new G("Cannot reset: detached HEAD");const p=a==="hard"?new Set(await l.listFiles({fs:i,dir:e,ref:"HEAD"})):null;if(await l.writeRef({fs:i,dir:e,ref:`refs/heads/${c}`,value:t,force:!0}),a==="mixed"||a==="hard"){const h=await l.listFiles({fs:i,dir:e,ref:t}),g=new Set(h);for(const d of h)await l.resetIndex({fs:i,dir:e,filepath:d,ref:t});const u=await l.statusMatrix({fs:i,dir:e});for(const[d,,,f]of u)if(!g.has(d)&&f!==0)try{await l.remove({fs:i,dir:e,filepath:d})}catch{}}if(a==="hard"){const h=await l.listFiles({fs:i,dir:e,ref:t}),g=new Set(h);for(const u of h){const d=await _(e,t,u);if(!d)continue;const f=A.join(e,u);i.mkdirSync(A.dirname(f),{recursive:!0}),i.writeFileSync(f,d.buf)}if(p){for(const u of p)if(!g.has(u)){const d=A.join(e,u);i.existsSync(d)&&i.unlinkSync(d)}}M(e),await l.add({fs:i,dir:e,filepath:".gitignore"})}return w(s,"INFO","Git reset successful",{logId:s,target:t,mode:a,previousHead:o}),{success:!0,message:`Reset (${a}) to ${t} successful`,logId:s,target:t,mode:a,previousHead:o}}catch(r){throw r instanceof G?r:(w(s,"ERROR","Failed to reset",{logId:s,target:t,mode:a,error:r.message}),new k("Failed to reset",{originalError:r.message}))}}async function et(n={}){const{target:t,message:a,authorName:e,authorEmail:s}=n;if(!t)throw new v("Revert target cannot be empty",{field:"target"});const{targetPath:r,logId:o}=P(n);await T(r);try{let c;try{c=(await l.readCommit({fs:i,dir:r,oid:t})).oid}catch{throw new v("Revert target commit does not exist",{field:"target",target:t})}const p=await l.statusMatrix({fs:i,dir:r});if(p.some(([m,y,S,F])=>y===0&&F===0?!1:S!==1||F!==1)){const m=p.filter(([,F])=>F!==0),y=m.filter(([,,F])=>F!==1).map(([F])=>F),S=m.filter(([,,,F])=>F!==1).map(([F])=>F);throw new G("Working directory is not clean, please commit or stash your changes before reverting",{staged:S,modified:y})}const g=new Set(await l.listFiles({fs:i,dir:r,ref:c})),u=await W(r),d=new Set(await l.listFiles({fs:i,dir:r,ref:u})),f=[];for(const m of g){const y=await _(r,c,m);if(!y)continue;const S=A.join(r,m);i.mkdirSync(A.dirname(S),{recursive:!0}),i.writeFileSync(S,y.buf),await l.add({fs:i,dir:r,filepath:m})}for(const m of d)if(!g.has(m)){f.push(m);try{await l.remove({fs:i,dir:r,filepath:m})}catch{}const y=A.join(r,m);i.existsSync(y)&&await i.promises.unlink(y)}if(f.length>0&&await j(r,f),M(r),await l.add({fs:i,dir:r,filepath:".gitignore"}),!(await l.statusMatrix({fs:i,dir:r})).some(([,,,m])=>m!==1))return w(o,"INFO","Nothing to revert, current HEAD already matches target",{logId:o,target:c}),{success:!0,message:"Nothing to revert, already at target state",logId:o,nothingToCommit:!0,target:c};const I={name:e||B.GIT_DEFAULT_AUTHOR_NAME,email:s||B.GIT_DEFAULT_AUTHOR_EMAIL},E=a||`Revert to ${c.substring(0,7)}`,x=await l.commit({fs:i,dir:r,message:E,author:I});return w(o,"INFO","Git revert successful",{logId:o,target:c,commitHash:x,previousHead:u,removedFilesCount:f.length}),{success:!0,message:"Revert successful",logId:o,commit:x,target:c,previousHead:u}}catch(c){throw c instanceof v||c instanceof G?c:(w(o,"ERROR","Failed to revert",{logId:o,target:t,error:c.message}),new k("Failed to revert",{originalError:c.message}))}}async function at(n={}){const{target:t}=n;if(!t)throw new v("Checkout target cannot be empty",{field:"target"});const{targetPath:a,logId:e}=P(n);await T(a);try{const s=await l.listFiles({fs:i,dir:a,ref:t});for(const r of s){const o=await _(a,t,r);if(!o)continue;const c=A.join(a,r);i.mkdirSync(A.dirname(c),{recursive:!0}),i.writeFileSync(c,o.buf),await l.add({fs:i,dir:a,filepath:r})}return M(a),await l.add({fs:i,dir:a,filepath:".gitignore"}),w(e,"INFO","Git checkout files successful",{logId:e,target:t}),{success:!0,message:`Checkout files from ${t} successful`,logId:e,target:t}}catch(s){throw w(e,"ERROR","Failed to checkout files",{logId:e,target:t,error:s.message}),new k("Failed to checkout files",{originalError:s.message})}}async function rt(n={}){const{targetPath:t,logId:a}=P(n);await T(t);try{const e=await l.listTags({fs:i,dir:t});return{success:!0,logId:a,tags:e,latest:e.length>0?e[e.length-1]:null}}catch(e){throw w(a,"ERROR","Failed to list tags",{logId:a,error:e.message}),new k("Failed to list tags",{originalError:e.message})}}async function st(n={}){const{tagName:t,message:a}=n;if(!t)throw new v("Tag name cannot be empty",{field:"tagName"});const{targetPath:e,logId:s}=P(n);await T(e);try{return a?await l.annotatedTag({fs:i,dir:e,ref:t,message:a,tagger:gt()}):await l.tag({fs:i,dir:e,ref:t}),w(s,"INFO","Git tag created",{logId:s,tagName:t,annotated:!!a}),{success:!0,message:"Tag created successfully",logId:s,tagName:t}}catch(r){throw w(s,"ERROR","Failed to create tag",{logId:s,tagName:t,error:r.message}),new k("Failed to create tag",{originalError:r.message})}}async function it(n={}){const{tagName:t}=n;if(!t)throw new v("Tag name cannot be empty",{field:"tagName"});const{targetPath:a,logId:e}=P(n);await T(a);try{return await l.deleteRef({fs:i,dir:a,ref:`refs/tags/${t}`}),w(e,"INFO","Git tag deleted",{logId:e,tagName:t}),{success:!0,message:"Tag deleted successfully",logId:e,tagName:t}}catch(s){throw w(e,"ERROR","Failed to delete tag",{logId:e,tagName:t,error:s.message}),new k("Failed to delete tag",{originalError:s.message})}}async function nt(n={}){const{targetPath:t,logId:a}=P(n);await T(t);try{const e=await l.listBranches({fs:i,dir:t}),s=await l.currentBranch({fs:i,dir:t,fullname:!1}),r={};for(const o of e)r[o]={name:o,current:o===s};return{success:!0,logId:a,branches:r,current:s}}catch(e){throw w(a,"ERROR","Failed to list branches",{logId:a,error:e.message}),new k("Failed to list branches",{originalError:e.message})}}async function ot(n={}){const{branchName:t,startPoint:a}=n;if(!t)throw new v("Branch name cannot be empty",{field:"branchName"});const{targetPath:e,logId:s}=P(n);await T(e);try{const r=a||"HEAD";return await l.branch({fs:i,dir:e,ref:t,object:r,checkout:!0}),w(s,"INFO","Git branch created",{logId:s,branchName:t,startPoint:a}),{success:!0,message:"Branch created and switched to",logId:s,branchName:t}}catch(r){throw w(s,"ERROR","Failed to create branch",{logId:s,branchName:t,error:r.message}),new k("Failed to create branch",{originalError:r.message})}}async function ct(n={}){const{branchName:t}=n;if(!t)throw new v("Branch name cannot be empty",{field:"branchName"});const{targetPath:a,logId:e}=P(n);await T(a);try{const s=await l.statusMatrix({fs:i,dir:a});if(s.some(([o,c,p,h])=>c===0&&h===0?!1:p!==1||h!==1)){const o=s.filter(([,h])=>h!==0),c=o.filter(([,,h])=>h!==1).map(([h])=>h),p=o.filter(([,,,h])=>h!==1).map(([h])=>h);throw new G("Working directory is not clean, please commit or stash your changes before switching branches",{staged:p,modified:c})}return await l.checkout({fs:i,dir:a,ref:t}),w(e,"INFO","Git branch switched",{logId:e,branchName:t}),{success:!0,message:"Branch switched successfully",logId:e,branchName:t}}catch(s){throw s instanceof G?s:(w(e,"ERROR","Failed to switch branch",{logId:e,branchName:t,error:s.message}),new k("Failed to switch branch",{originalError:s.message}))}}async function lt(n={}){const{branchName:t,force:a=!1}=n;if(!t)throw new v("Branch name cannot be empty",{field:"branchName"});const{targetPath:e,logId:s}=P(n);await T(e);try{if(await l.currentBranch({fs:i,dir:e,fullname:!1})===t)throw new G("Cannot delete the current branch, please switch to another branch first");return await l.deleteBranch({fs:i,dir:e,ref:t,force:a}),w(s,"INFO","Git branch deleted",{logId:s,branchName:t,force:a}),{success:!0,message:"Branch deleted successfully",logId:s,branchName:t}}catch(r){throw r instanceof G?r:(w(s,"ERROR","Failed to delete branch",{logId:s,branchName:t,error:r.message}),new k("Failed to delete branch",{originalError:r.message}))}}export{q as init,z as status,K as commit,V as add,J as unstage,Q as discard,X as logHistory,Y as diff,Z as fileContent,tt as reset,et as revert,at as checkout,rt as listTags,st as createTag,it as deleteTag,nt as listBranches,ot as createBranch,ct as switchBranch,lt as deleteBranch};export default{init:q,status:z,commit:K,add:V,unstage:J,discard:Q,logHistory:X,diff:Y,fileContent:Z,reset:tt,revert:et,checkout:at,listTags:rt,createTag:st,deleteTag:it,listBranches:nt,createBranch:ot,switchBranch:ct,deleteBranch:lt};
@@ -1,2 +1,2 @@
1
- import m from"fs";import h from"path";import N from"archiver";import P from"../../appConfig/index.js";import{log as i}from"../log/logUtils.js";import{ValidationError as y,SystemError as D}from"../error/errorHandler.js";const $=100*1024*1024,b=P.DOWNLOAD_MAX_FILE_SIZE_BYTES||$;async function O(d,u,n,w,f){const r=[],g=await m.promises.readdir(d,{withFileTypes:!0});g.sort((s,t)=>s.isDirectory()&&!t.isDirectory()?-1:!s.isDirectory()&&t.isDirectory()?1:s.name.toLowerCase().localeCompare(t.name.toLowerCase()));for(const s of g){const t=h.join(d,s.name);if(!(s.name.startsWith(".")&&s.name!==".gitignore"||(P.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(s.name))&&!(s.isDirectory()&&P.TRAVERSE_EXCLUDE_DIRS.includes(s.name)))if(s.isDirectory()){const e=await O(t,u,n,w,f);if(e.length===0){const c=u||d,l=h.relative(c,t).replace(/\\/g,"/");r.push({name:l,isDir:!0})}else r.push(...e)}else try{const e=u||d,c=h.relative(e,t).replace(/\\/g,"/"),l=s.isSymbolicLink();let o=null;if(w){const E=c.split("/").map(R=>encodeURIComponent(R)).join("/");o=`${w}/${E}`,f&&(o+=`?customTargetDir=${encodeURIComponent(f)}`)}const a={name:c,isDir:!1,fileProxyUrl:o,isLink:l};r.push(a)}catch(e){i(n,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${t}`,{error:e.message})}}return r}async function k(d,u,n,w,f=""){const r=await m.promises.readdir(h.join(d,f),{withFileTypes:!0});let g=0;for(const s of r){const t=f?h.join(f,s.name):s.name,p=t.split(h.sep).filter(Boolean);if(p.some(o=>o.startsWith(".")))continue;const e=p[p.length-1];if(u.includes(e)||p.some(o=>n.includes(o)))continue;const c=h.join(d,t);let l;try{l=await m.promises.lstat(c)}catch(o){i(w,"WARN","Error occurred when getting file stats, skipping",{filePath:t.replace(/\\/g,"/"),error:o.message});continue}l.isSymbolicLink()||l.nlink>1||(l.isDirectory()?g+=await k(d,u,n,w,t):l.isFile()&&(g+=l.size))}return g}async function _(d,u,n,w){const f=Date.now(),r=`computer:${d}:${u}`,g=P.COMPUTER_WORKSPACE_DIR;if(!d)throw new y("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!u)throw new y("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const s=String(d),t=String(u),p=w&&w.trim()?w:h.join(g,s,t);if(!m.existsSync(p))return i(r,"INFO","Directory does not exist, returning empty list",{targetDir:p,userId:s,cId:t}),{files:[]};i(r,"DEBUG","Start getting user file list",{targetDir:p,userId:s,cId:t});try{const e=await O(p,p,r,n,w);return i(r,"INFO","User file list obtained successfully",{fileCount:e.length,targetDir:p,userId:s,cId:t,elapsedMs:Date.now()-f}),{files:e}}catch(e){throw i(r,"ERROR","Failed to get user file list",{targetDir:p,userId:s,cId:t,error:e.message,elapsedMs:Date.now()-f}),new D(`Failed to get file list: ${e.message}`,{targetDir:p,originalError:e.message})}}async function x(d,u,n,w){const f=Date.now(),r=`computer:${d}:${u}`,g=P.COMPUTER_WORKSPACE_DIR;if(!d)throw new y("userId cannot be empty",{field:"userId"});if(!u)throw new y("cId cannot be empty",{field:"cId"});if(!Array.isArray(n))throw new y("files must be an array",{field:"files"});const s=String(d),t=String(u),p=w&&w.trim()?w:h.join(g,s,t);m.existsSync(p)||m.mkdirSync(p,{recursive:!0});for(let e=0;e<n.length;e++){const c=n[e];if(!c||typeof c.operation!="string")throw new y(`files[${e}].operation cannot be empty`,{field:`files[${e}].operation`});if(!c.name||typeof c.name!="string")throw new y(`files[${e}].name cannot be empty`,{field:`files[${e}].name`});const l=c.operation.toLowerCase();if(!["create","delete","rename","modify"].includes(l))throw new y(`files[${e}].operation must be one of create, delete, rename or modify`,{field:`files[${e}].operation`});if(l==="rename"&&!c.renameFrom)throw new y(`files[${e}].renameFrom cannot be empty (rename operation requires)`,{field:`files[${e}].renameFrom`});if(l==="modify"&&c.isDir!==!0&&typeof c.contents!="string")throw new y(`files[${e}].contents must be a string (modify operation requires)`,{field:`files[${e}].contents`})}i(r,"DEBUG","Start updating user files",{userId:s,cId:t,filesCount:n.length});try{for(const e of n){const c=e.operation.toLowerCase(),l=e.name,o=h.normalize(l).replace(/^[\/\\]+/,""),a=h.join(p,o),E=h.resolve(a),R=h.resolve(p);if(!E.startsWith(R+h.sep)&&E!==R){i(r,"WARN","File path is not secure, skipping",{filePath:o,resolvedPath:E});continue}switch(c){case"create":{if(e.isDir===!0){if(m.existsSync(a)){if((await m.promises.stat(a)).isFile())throw new y("Cannot create directory, file with the same name already exists",{filePath:o});i(r,"INFO","Directory already exists, skipping creation",{filePath:o});break}await m.promises.mkdir(a,{recursive:!0}),i(r,"INFO","Directory created successfully",{filePath:o});break}if(m.existsSync(a)){if((await m.promises.stat(a)).isDirectory())throw new y("Cannot create file, directory with the same name already exists",{filePath:o});i(r,"INFO","File already exists, skipping creation",{filePath:o});break}await m.promises.mkdir(h.dirname(a),{recursive:!0});const F=e.contents||"";await m.promises.writeFile(a,F,"utf8"),i(r,"INFO","File created successfully",{filePath:o});break}case"delete":{m.existsSync(a)?(await m.promises.stat(a)).isDirectory()?(await m.promises.rm(a,{recursive:!0,force:!0}),i(r,"INFO","Directory deleted successfully",{filePath:o})):(await m.promises.unlink(a),i(r,"INFO","File deleted successfully",{filePath:o})):i(r,"WARN","The file or directory to be deleted does not exist",{filePath:o});break}case"rename":{const F=e.renameFrom;if(!F||typeof F!="string"){i(r,"WARN","Rename operation missing renameFrom",{filePath:o});break}const S=h.normalize(F).replace(/^[\/\\]+/,""),I=h.join(p,S),z=h.resolve(I);if(!z.startsWith(R+h.sep)&&z!==R){i(r,"WARN","Source path is not secure, skipping rename",{sourcePath:S,targetPath:o});break}if(m.existsSync(I)){const A=(await m.promises.stat(I)).isDirectory();await m.promises.mkdir(h.dirname(a),{recursive:!0}),await m.promises.rename(I,a),i(r,"INFO",A?"Directory renamed successfully":"File renamed successfully",{sourcePath:S,targetPath:o})}else i(r,"WARN","The file or directory to be renamed does not exist",{sourcePath:S});break}case"modify":{if(!m.existsSync(a)){i(r,"WARN","The file to be modified does not exist",{filePath:o});break}if((await m.promises.stat(a)).isDirectory()){i(r,"INFO","The target is a directory, skipping modification",{filePath:o});break}const S=typeof e.contents=="string"?e.contents:"";if(await m.promises.readFile(a,"utf8")===S){i(r,"INFO","File content has no changes, skipping write",{filePath:o});break}await m.promises.writeFile(a,S,"utf8"),i(r,"INFO","File modified successfully",{filePath:o});break}default:{i(r,"WARN","Unsupported operation type",{operation:c,filePath:o});break}}}return i(r,"INFO","User files updated successfully",{userId:s,cId:t,filesCount:n.length,elapsedMs:Date.now()-f}),{success:!0,message:"User files updated successfully",userId:s,cId:t,filesCount:n.length}}catch(e){throw i(r,"ERROR","User files updated failed",{userId:s,cId:t,error:e.message,elapsedMs:Date.now()-f}),new D(`User files updated failed: ${e.message}`,{userId:s,cId:t,originalError:e.message})}}async function C(d,u,n,w,f){const r=Date.now(),g=`computer:${d}:${u}`,s=P.COMPUTER_WORKSPACE_DIR;if(!d)throw new y("userId cannot be empty",{field:"userId"});if(!u)throw new y("cId cannot be empty",{field:"cId"});if(!n)throw new y("file cannot be empty",{field:"file"});if(!w||typeof w!="string")throw new y("filePath cannot be empty",{field:"filePath"});const t=String(d),p=String(u),e=f&&f.trim()?f:h.join(s,t,p);m.existsSync(e)||m.mkdirSync(e,{recursive:!0});const c=h.normalize(w).replace(/^[\/\\]+/,""),l=h.join(e,c),o=h.resolve(l),a=h.resolve(e);if(!o.startsWith(a+h.sep)&&o!==a)throw new y("File path is not secure, cannot exceed user directory",{field:"filePath",providedPath:w,resolvedPath:o});try{if(await m.promises.mkdir(h.dirname(l),{recursive:!0}),n.buffer)await m.promises.writeFile(l,n.buffer);else if(typeof n.contents=="string")await m.promises.writeFile(l,n.contents,"utf8");else throw new y("File content format is incorrect",{field:"file",hasBuffer:!!n.buffer,hasContents:typeof n.contents});return i(g,"INFO","File uploaded successfully",{userId:t,cId:p,filePath:c,targetPath:o,fileSize:n.buffer?n.buffer.length:n.contents?n.contents.length:0,elapsedMs:Date.now()-r}),{success:!0,message:"File uploaded successfully",fileSize:n.buffer?n.buffer.length:n.contents?n.contents.length:0}}catch(E){throw i(g,"ERROR","File upload failed",{userId:t,cId:p,filePath:c,error:E.message,elapsedMs:Date.now()-r}),new D(`File upload failed: ${E.message}`,{userId:t,cId:p,filePath:c,originalError:E.message})}}async function W(d,u,n,w,f){const r=Date.now(),g=`computer:${d}:${u}`;if(!d)throw new y("userId cannot be empty",{field:"userId"});if(!u)throw new y("cId cannot be empty",{field:"cId"});if(!Array.isArray(n))throw new y("files must be an array",{field:"files"});if(!Array.isArray(w))throw new y("filePaths must be an array",{field:"filePaths"});if(n.length!==w.length)throw new y(`File count (${n.length}) does not match path count (${w.length})`,{field:"filePaths"});i(g,"DEBUG","Start batch uploading files",{userId:d,cId:u,filesCount:n.length});const s=[];try{for(let e=0;e<n.length;e++){const c=n[e],l=w[e];if(!c){i(g,"WARN","Empty file object encountered in batch upload, skipping",{index:e,filePath:l}),s.push({success:!1,filePath:l,error:"Empty file object"});continue}if(!l||typeof l!="string"){i(g,"WARN","Invalid file path in batch upload, skipping",{index:e,originalname:c.originalname}),s.push({success:!1,filePath:l||"",originalname:c.originalname,error:"Invalid file path"});continue}try{const o=await C(d,u,c,l,f);s.push({success:!0,filePath:l,originalname:c.originalname,...o})}catch(o){i(g,"ERROR","Single file upload failed in batch upload",{filePath:l,originalname:c.originalname,error:o.message}),s.push({success:!1,filePath:l,originalname:c.originalname,error:o.message})}}const t=s.filter(e=>e.success).length,p=s.filter(e=>!e.success).length;return i(g,"INFO","Batch upload files completed",{userId:d,cId:u,totalCount:n.length,successCount:t,failCount:p,elapsedMs:Date.now()-r}),{success:!0,message:"Batch upload completed",totalCount:n.length,successCount:t,failCount:p,results:s}}catch(t){throw i(g,"ERROR","Batch upload files failed",{userId:d,cId:u,error:t.message,elapsedMs:Date.now()-r}),new D(`Batch upload files failed: ${t.message}`,{userId:d,cId:u,originalError:t.message})}}async function U(d,u,n){const w=Date.now(),f=`computer:${d}:${u}`,r=P.COMPUTER_WORKSPACE_DIR;if(!d)throw new y("userId cannot be empty",{field:"userId"});if(!u)throw new y("cId cannot be empty",{field:"cId"});if(!r)throw new D("COMPUTER_WORKSPACE_DIR is not configured, cannot create zip");const g=String(d),s=String(u),t=n&&n.trim()?n:h.join(r,g,s);if(!m.existsSync(t)){const a=`${g}_${s}.zip`;i(f,"WARN","Workspace directory does not exist, returning empty zip",{targetDir:t,userId:g,cId:s,zipFileName:a});const E=N("zip",{zlib:{level:9}});return E.append(null,{name:`${g}_${s}/`,type:"directory"}),E.on("warning",R=>{if(R.code==="ENOENT")i(f,"WARN","Encountered file problem when creating empty zip",{message:R.message,code:R.code});else throw i(f,"ERROR","Encountered warning when creating empty zip",{message:R.message,code:R.code}),R}),E.on("error",R=>{i(f,"ERROR","Failed to create empty zip",{message:R.message})}),{archive:E,zipFileName:a}}const p=`${g}_${s}.zip`;i(f,"DEBUG","Start creating workspace directory zip",{targetDir:t,zipFileName:p});const e=N("zip",{zlib:{level:9}}),c=P.CONTENT_TRAVERSE_EXCLUDE_FILES||[],l=P.TRAVERSE_EXCLUDE_DIRS||[],o=await k(t,c,l,f);if(o>b){const a=b/1024/1024,E=(o/1024/1024).toFixed(2);throw i(f,"WARN","Download rejected due to oversized workspace",{targetDir:t,downloadableSize:o,maxSizeBytes:b}),new y(`Download failed: total file size ${E}MB exceeds limit ${a}MB`,{field:"downloadSize",downloadableSize:o,maxSizeBytes:b})}return e.directory(t,`${g}_${s}`,a=>{const E=a.name||"",R=E.split(/[\/\\]/).filter(Boolean);if(R.some(S=>S.startsWith(".")))return!1;const F=R[R.length-1];if(c.includes(F)||R.some(S=>l.includes(S)))return!1;try{const S=h.join(t,E),I=m.lstatSync(S);if(I.isSymbolicLink()||I.nlink>1)return!1}catch(S){return i(f,"WARN","Error occurred when detecting link file, skipping",{filePath:E,error:S.message}),!1}return a}),e.on("warning",a=>{if(a.code==="ENOENT")i(f,"WARN","Encountered file problem when creating zip",{message:a.message,code:a.code});else throw i(f,"ERROR","Encountered warning when creating zip",{message:a.message,code:a.code}),a}),e.on("error",a=>{i(f,"ERROR","Failed to create zip",{message:a.message,elapsedMs:Date.now()-w})}),e.on("end",()=>{i(f,"INFO","Workspace directory zip created successfully",{targetDir:t,zipFileName:p,elapsedMs:Date.now()-w})}),{archive:e,zipFileName:p}}async function v(d,u,n=200){const w=Date.now(),f=`computer:${d}:${u}`;if(!d)throw new y("userId cannot be empty",{field:"userId"});if(!u)throw new y("cId cannot be empty",{field:"cId"});const r=String(d),g=String(u),s=P.COMPUTER_WORKSPACE_DIR;if(!s)return i(f,"WARN","COMPUTER_WORKSPACE_DIR is not configured"),{fileName:null,fileSize:0,lines:[]};const t=h.join(s,r,g,".logs");if(!m.existsSync(t))return i(f,"DEBUG","Log directory does not exist",{logDir:t}),{fileName:null,fileSize:0,lines:[]};const e=(await m.promises.readdir(t,{withFileTypes:!0})).filter(F=>F.isFile()).map(F=>F.name);if(e.length===0)return i(f,"DEBUG","No log file found",{logDir:t}),{fileName:null,fileSize:0,lines:[]};const c=[];for(const F of e){const S=h.join(t,F),I=await m.promises.stat(S);c.push({name:F,fullPath:S,mtime:I.mtimeMs,size:I.size})}c.sort((F,S)=>S.mtime-F.mtime);const l=c[0],a=(await m.promises.readFile(l.fullPath,"utf8")).split(`
2
- `).filter(F=>F.length>0),E=Math.max(1,n),R=a.slice(-E);return i(f,"DEBUG","Get latest logs",{fileName:l.name,fileSize:l.size,totalLines:a.length,returnedLines:R.length,elapsedMs:Date.now()-w}),{fileName:l.name,fileSize:l.size,lines:R}}export{_ as getFileList,x as updateFiles,C as uploadFile,W as uploadFiles,U as downloadAllFiles,v as getLatestLogs};
1
+ import m from"fs";import h from"path";import C from"archiver";import P from"../../appConfig/index.js";import{log as s}from"../log/logUtils.js";import{ValidationError as y,SystemError as z}from"../error/errorHandler.js";const _=100*1024*1024,O=P.DOWNLOAD_MAX_FILE_SIZE_BYTES||_;async function x(d,u,n,g,f){const r=[],w=await m.promises.readdir(d,{withFileTypes:!0});w.sort((i,t)=>i.isDirectory()&&!t.isDirectory()?-1:!i.isDirectory()&&t.isDirectory()?1:i.name.toLowerCase().localeCompare(t.name.toLowerCase()));for(const i of w){const t=h.join(d,i.name);if(!(i.name.startsWith(".")&&i.name!==".gitignore"||(P.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(i.name))&&!(i.isDirectory()&&P.TRAVERSE_EXCLUDE_DIRS.includes(i.name)))if(i.isDirectory()){const e=await x(t,u,n,g,f);if(e.length===0){const c=u||d,l=h.relative(c,t).replace(/\\/g,"/");r.push({name:l,isDir:!0})}else r.push(...e)}else try{const e=u||d,c=h.relative(e,t).replace(/\\/g,"/"),l=i.isSymbolicLink();let o=null;if(g){const R=c.split("/").map(E=>encodeURIComponent(E)).join("/");o=`${g}/${R}`,f&&(o+=`?customTargetDir=${encodeURIComponent(f)}`)}const a={name:c,isDir:!1,fileProxyUrl:o,isLink:l};r.push(a)}catch(e){s(n,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${t}`,{error:e.message})}}return r}async function A(d,u,n,g,f=""){const r=await m.promises.readdir(h.join(d,f),{withFileTypes:!0});let w=0;for(const i of r){const t=f?h.join(f,i.name):i.name,p=t.split(h.sep).filter(Boolean);if(p.some(o=>o.startsWith(".")))continue;const e=p[p.length-1];if(u.includes(e)||p.some(o=>n.includes(o)))continue;const c=h.join(d,t);let l;try{l=await m.promises.lstat(c)}catch(o){s(g,"WARN","Error occurred when getting file stats, skipping",{filePath:t.replace(/\\/g,"/"),error:o.message});continue}l.isSymbolicLink()||l.nlink>1||(l.isDirectory()?w+=await A(d,u,n,g,t):l.isFile()&&(w+=l.size))}return w}async function L(d,u,n,g){const f=Date.now(),r=`computer:${d}:${u}`,w=P.COMPUTER_WORKSPACE_DIR;if(!d)throw new y("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!u)throw new y("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const i=String(d),t=String(u),p=g&&g.trim()?g:h.join(w,i,t);if(!m.existsSync(p))return s(r,"INFO","Directory does not exist, returning empty list",{targetDir:p,userId:i,cId:t}),{files:[]};s(r,"DEBUG","Start getting user file list",{targetDir:p,userId:i,cId:t});try{const e=await x(p,p,r,n,g);return s(r,"INFO","User file list obtained successfully",{fileCount:e.length,targetDir:p,userId:i,cId:t,elapsedMs:Date.now()-f}),{files:e}}catch(e){throw s(r,"ERROR","Failed to get user file list",{targetDir:p,userId:i,cId:t,error:e.message,elapsedMs:Date.now()-f}),new z(`Failed to get file list: ${e.message}`,{targetDir:p,originalError:e.message})}}async function W(d,u,n,g){const f=Date.now(),r=`computer:${d}:${u}`,w=P.COMPUTER_WORKSPACE_DIR;if(!d)throw new y("userId cannot be empty",{field:"userId"});if(!u)throw new y("cId cannot be empty",{field:"cId"});if(!Array.isArray(n))throw new y("files must be an array",{field:"files"});const i=String(d),t=String(u),p=g&&g.trim()?g:h.join(w,i,t);m.existsSync(p)||m.mkdirSync(p,{recursive:!0});for(let e=0;e<n.length;e++){const c=n[e];if(!c||typeof c.operation!="string")throw new y(`files[${e}].operation cannot be empty`,{field:`files[${e}].operation`});if(!c.name||typeof c.name!="string")throw new y(`files[${e}].name cannot be empty`,{field:`files[${e}].name`});const l=c.operation.toLowerCase();if(!["create","delete","rename","modify"].includes(l))throw new y(`files[${e}].operation must be one of create, delete, rename or modify`,{field:`files[${e}].operation`});if(l==="rename"&&!c.renameFrom)throw new y(`files[${e}].renameFrom cannot be empty (rename operation requires)`,{field:`files[${e}].renameFrom`});if(l==="modify"&&c.isDir!==!0&&typeof c.contents!="string")throw new y(`files[${e}].contents must be a string (modify operation requires)`,{field:`files[${e}].contents`})}s(r,"DEBUG","Start updating user files",{userId:i,cId:t,filesCount:n.length});try{for(const e of n){const c=e.operation.toLowerCase(),l=e.name,o=h.normalize(l).replace(/^[\/\\]+/,""),a=h.join(p,o),R=h.resolve(a),E=h.resolve(p);if(!R.startsWith(E+h.sep)&&R!==E){s(r,"WARN","File path is not secure, skipping",{filePath:o,resolvedPath:R});continue}switch(c){case"create":{if(e.isDir===!0){if(m.existsSync(a)){if((await m.promises.stat(a)).isFile())throw new y("Cannot create directory, file with the same name already exists",{filePath:o});s(r,"INFO","Directory already exists, skipping creation",{filePath:o});break}await m.promises.mkdir(a,{recursive:!0}),s(r,"INFO","Directory created successfully",{filePath:o});break}if(m.existsSync(a)){if((await m.promises.stat(a)).isDirectory())throw new y("Cannot create file, directory with the same name already exists",{filePath:o});s(r,"INFO","File already exists, skipping creation",{filePath:o});break}await m.promises.mkdir(h.dirname(a),{recursive:!0});const S=e.contents||"";await m.promises.writeFile(a,S,"utf8"),s(r,"INFO","File created successfully",{filePath:o});break}case"delete":{m.existsSync(a)?(await m.promises.stat(a)).isDirectory()?(await m.promises.rm(a,{recursive:!0,force:!0}),s(r,"INFO","Directory deleted successfully",{filePath:o})):(await m.promises.unlink(a),s(r,"INFO","File deleted successfully",{filePath:o})):s(r,"WARN","The file or directory to be deleted does not exist",{filePath:o});break}case"rename":{const S=e.renameFrom;if(!S||typeof S!="string"){s(r,"WARN","Rename operation missing renameFrom",{filePath:o});break}const I=h.normalize(S).replace(/^[\/\\]+/,""),D=h.join(p,I),N=h.resolve(D);if(!N.startsWith(E+h.sep)&&N!==E){s(r,"WARN","Source path is not secure, skipping rename",{sourcePath:I,targetPath:o});break}if(m.existsSync(D)){const b=(await m.promises.stat(D)).isDirectory();await m.promises.mkdir(h.dirname(a),{recursive:!0}),await m.promises.rename(D,a),s(r,"INFO",b?"Directory renamed successfully":"File renamed successfully",{sourcePath:I,targetPath:o})}else s(r,"WARN","The file or directory to be renamed does not exist",{sourcePath:I});break}case"modify":{if(!m.existsSync(a)){s(r,"WARN","The file to be modified does not exist",{filePath:o});break}if((await m.promises.stat(a)).isDirectory()){s(r,"INFO","The target is a directory, skipping modification",{filePath:o});break}const I=typeof e.contents=="string"?e.contents:"";if(await m.promises.readFile(a,"utf8")===I){s(r,"INFO","File content has no changes, skipping write",{filePath:o});break}await m.promises.writeFile(a,I,"utf8"),s(r,"INFO","File modified successfully",{filePath:o});break}default:{s(r,"WARN","Unsupported operation type",{operation:c,filePath:o});break}}}return s(r,"INFO","User files updated successfully",{userId:i,cId:t,filesCount:n.length,elapsedMs:Date.now()-f}),{success:!0,message:"User files updated successfully",userId:i,cId:t,filesCount:n.length}}catch(e){throw s(r,"ERROR","User files updated failed",{userId:i,cId:t,error:e.message,elapsedMs:Date.now()-f}),new z(`User files updated failed: ${e.message}`,{userId:i,cId:t,originalError:e.message})}}async function $(d,u,n,g,f){const r=Date.now(),w=`computer:${d}:${u}`,i=P.COMPUTER_WORKSPACE_DIR;if(!d)throw new y("userId cannot be empty",{field:"userId"});if(!u)throw new y("cId cannot be empty",{field:"cId"});if(!n)throw new y("file cannot be empty",{field:"file"});if(!g||typeof g!="string")throw new y("filePath cannot be empty",{field:"filePath"});const t=String(d),p=String(u),e=f&&f.trim()?f:h.join(i,t,p);m.existsSync(e)||m.mkdirSync(e,{recursive:!0});const c=h.normalize(g).replace(/^[\/\\]+/,""),l=h.join(e,c),o=h.resolve(l),a=h.resolve(e);if(!o.startsWith(a+h.sep)&&o!==a)throw new y("File path is not secure, cannot exceed user directory",{field:"filePath",providedPath:g,resolvedPath:o});try{if(await m.promises.mkdir(h.dirname(l),{recursive:!0}),n.buffer)await m.promises.writeFile(l,n.buffer);else if(typeof n.contents=="string")await m.promises.writeFile(l,n.contents,"utf8");else throw new y("File content format is incorrect",{field:"file",hasBuffer:!!n.buffer,hasContents:typeof n.contents});return s(w,"INFO","File uploaded successfully",{userId:t,cId:p,filePath:c,targetPath:o,fileSize:n.buffer?n.buffer.length:n.contents?n.contents.length:0,elapsedMs:Date.now()-r}),{success:!0,message:"File uploaded successfully",fileSize:n.buffer?n.buffer.length:n.contents?n.contents.length:0}}catch(R){throw s(w,"ERROR","File upload failed",{userId:t,cId:p,filePath:c,error:R.message,elapsedMs:Date.now()-r}),new z(`File upload failed: ${R.message}`,{userId:t,cId:p,filePath:c,originalError:R.message})}}async function v(d,u,n,g,f){const r=Date.now(),w=`computer:${d}:${u}`;if(!d)throw new y("userId cannot be empty",{field:"userId"});if(!u)throw new y("cId cannot be empty",{field:"cId"});if(!Array.isArray(n))throw new y("files must be an array",{field:"files"});if(!Array.isArray(g))throw new y("filePaths must be an array",{field:"filePaths"});if(n.length!==g.length)throw new y(`File count (${n.length}) does not match path count (${g.length})`,{field:"filePaths"});s(w,"DEBUG","Start batch uploading files",{userId:d,cId:u,filesCount:n.length});const i=[];try{for(let e=0;e<n.length;e++){const c=n[e],l=g[e];if(!c){s(w,"WARN","Empty file object encountered in batch upload, skipping",{index:e,filePath:l}),i.push({success:!1,filePath:l,error:"Empty file object"});continue}if(!l||typeof l!="string"){s(w,"WARN","Invalid file path in batch upload, skipping",{index:e,originalname:c.originalname}),i.push({success:!1,filePath:l||"",originalname:c.originalname,error:"Invalid file path"});continue}try{const o=await $(d,u,c,l,f);i.push({success:!0,filePath:l,originalname:c.originalname,...o})}catch(o){s(w,"ERROR","Single file upload failed in batch upload",{filePath:l,originalname:c.originalname,error:o.message}),i.push({success:!1,filePath:l,originalname:c.originalname,error:o.message})}}const t=i.filter(e=>e.success).length,p=i.filter(e=>!e.success).length;return s(w,"INFO","Batch upload files completed",{userId:d,cId:u,totalCount:n.length,successCount:t,failCount:p,elapsedMs:Date.now()-r}),{success:!0,message:"Batch upload completed",totalCount:n.length,successCount:t,failCount:p,results:i}}catch(t){throw s(w,"ERROR","Batch upload files failed",{userId:d,cId:u,error:t.message,elapsedMs:Date.now()-r}),new z(`Batch upload files failed: ${t.message}`,{userId:d,cId:u,originalError:t.message})}}async function U(d,u,n){const g=Date.now(),f=`computer:${d}:${u}`,r=P.COMPUTER_WORKSPACE_DIR;if(!d)throw new y("userId cannot be empty",{field:"userId"});if(!u)throw new y("cId cannot be empty",{field:"cId"});if(!r)throw new z("COMPUTER_WORKSPACE_DIR is not configured, cannot create zip");const w=String(d),i=String(u),t=n&&n.trim()?n:h.join(r,w,i);if(!m.existsSync(t)){const a=`${w}_${i}.zip`;s(f,"WARN","Workspace directory does not exist, returning empty zip",{targetDir:t,userId:w,cId:i,zipFileName:a});const R=C("zip",{zlib:{level:9}});return R.append(null,{name:`${w}_${i}/`,type:"directory"}),R.on("warning",E=>{if(E.code==="ENOENT")s(f,"WARN","Encountered file problem when creating empty zip",{message:E.message,code:E.code});else throw s(f,"ERROR","Encountered warning when creating empty zip",{message:E.message,code:E.code}),E}),R.on("error",E=>{s(f,"ERROR","Failed to create empty zip",{message:E.message})}),{archive:R,zipFileName:a}}const p=`${w}_${i}.zip`;s(f,"DEBUG","Start creating workspace directory zip",{targetDir:t,zipFileName:p});const e=C("zip",{zlib:{level:9}}),c=P.CONTENT_TRAVERSE_EXCLUDE_FILES||[],l=P.TRAVERSE_EXCLUDE_DIRS||[],o=await A(t,c,l,f);if(o>O){const a=O/1024/1024,R=(o/1024/1024).toFixed(2);throw s(f,"WARN","Download rejected due to oversized workspace",{targetDir:t,downloadableSize:o,maxSizeBytes:O}),new y(`Download failed: total file size ${R}MB exceeds limit ${a}MB`,{field:"downloadSize",downloadableSize:o,maxSizeBytes:O})}return e.directory(t,`${w}_${i}`,a=>{const R=a.name||"",E=R.split(/[\/\\]/).filter(Boolean);if(E.some(I=>I.startsWith(".")))return!1;const S=E[E.length-1];if(c.includes(S)||E.some(I=>l.includes(I)))return!1;try{const I=h.join(t,R),D=m.lstatSync(I);if(D.isSymbolicLink()||D.nlink>1)return!1}catch(I){return s(f,"WARN","Error occurred when detecting link file, skipping",{filePath:R,error:I.message}),!1}return a}),e.on("warning",a=>{if(a.code==="ENOENT")s(f,"WARN","Encountered file problem when creating zip",{message:a.message,code:a.code});else throw s(f,"ERROR","Encountered warning when creating zip",{message:a.message,code:a.code}),a}),e.on("error",a=>{s(f,"ERROR","Failed to create zip",{message:a.message,elapsedMs:Date.now()-g})}),e.on("end",()=>{s(f,"INFO","Workspace directory zip created successfully",{targetDir:t,zipFileName:p,elapsedMs:Date.now()-g})}),{archive:e,zipFileName:p}}async function M(d,u,n=200){const g=Date.now(),f=`computer:${d}:${u}`;if(!d)throw new y("userId cannot be empty",{field:"userId"});if(!u)throw new y("cId cannot be empty",{field:"cId"});const r=String(d),w=String(u),i=P.COMPUTER_WORKSPACE_DIR;if(!i)return s(f,"WARN","COMPUTER_WORKSPACE_DIR is not configured"),{success:!0,message:"Log workspace is not configured",logs:[],totalLines:0,startIndex:1,logFileName:null};const t=h.join(i,r,w,".logs");if(!m.existsSync(t))return s(f,"DEBUG","Log directory does not exist",{logDir:t}),{success:!0,message:"Log directory does not exist",logs:[],totalLines:0,startIndex:1,logFileName:null};const e=(await m.promises.readdir(t,{withFileTypes:!0})).filter(F=>F.isFile()).map(F=>F.name);if(e.length===0)return s(f,"DEBUG","No log file found",{logDir:t}),{success:!0,message:"No log file found",logs:[],totalLines:0,startIndex:1,logFileName:null};const c=[];for(const F of e){const b=h.join(t,F),k=await m.promises.stat(b);c.push({name:F,fullPath:b,mtime:k.mtimeMs,size:k.size})}c.sort((F,b)=>b.mtime-F.mtime);const l=c[0],a=(await m.promises.readFile(l.fullPath,"utf8")).split(`
2
+ `).filter(F=>F.length>0),R=a.length,E=Math.max(1,n),S=Math.max(0,R-E),I=S+1,N=a.slice(S).map((F,b)=>({line:I+b,content:F}));return s(f,"DEBUG","Get latest logs",{fileName:l.name,fileSize:l.size,totalLines:R,returnedLines:N.length,elapsedMs:Date.now()-g}),{success:!0,message:"Get log successfully",logs:N,totalLines:R,startIndex:I,logFileName:l.name}}export{L as getFileList,W as updateFiles,$ as uploadFile,v as uploadFiles,U as downloadAllFiles,M as getLatestLogs};
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.3.0-beta.18",
4
+ "version": "1.3.0-beta.20",
5
5
  "description": "Cross-platform file service deployment tool with start/stop/restart CLI commands",
6
6
  "type": "module",
7
7
  "main": "index.js",