nuwax-file-server 1.5.1 → 1.5.3
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 +1 -1
- package/dist/routes/computerRoutes.js +1 -1
- package/dist/service/gitService.js +1 -8
- package/dist/utils/computer/computerFileUtils.js +2 -2
- package/dist/utils/computer/fsBrowserUtils.js +1 -1
- package/dist/utils/git/gitUtils.js +4 -4
- package/dist/utils/git/isoGitOps.js +8 -0
- package/dist/utils/git/nativeGitUtils.js +7 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as ee}from"commander";import{createRequire as te}from"module";import f from"path";import U from"os";import c from"fs-extra";import{spawn as E}from"cross-spawn";import V from"tree-kill";import{fileURLToPath as q}from"url";import{execFileSync as R}from"child_process";import M from"http";import{createRequire as G}from"module";var I=f.dirname(q(import.meta.url)),H=G(import.meta.url),n={name:"nuwax-file-server",pidDir:f.join(U.tmpdir(),"nuwax-file-server"),pidFileName:"server.pid",lockFileName:"start.lock",defaultStopTimeout:3e4,defaultStartTimeout:3e4,staleLockTimeout:12e4,checkInterval:500};function $(){return f.join(n.pidDir,n.pidFileName)}function F(){return f.join(n.pidDir,n.lockFileName)}function J(){let e=[f.join(I,"..","server.js"),f.join(I,"server.js")];for(let t of e)if(c.existsSync(t))return t;return e[0]}function W(){let e=["../../package.json","../package.json"];for(let t of e)try{let o=H(t);if(o?.version)return o.version}catch{}return"unknown"}function x(){try{let e=$();if(!c.existsSync(e))return null;let t=c.readFileSync(e,"utf8"),o=JSON.parse(t);return!o||typeof o.pid!="number"?null:o}catch(e){return e.code!=="ENOENT"&&console.error(`Read PID file failed: ${e.message}`),null}}function B(e){try{let t=$();c.ensureDirSync(n.pidDir),c.writeFileSync(t,JSON.stringify(e,null,2)),console.debug(`PID file written: ${t}`)}catch(t){throw console.error(`Write PID file failed: ${t.message}`),t}}function v(){try{let e=$();c.existsSync(e)&&(c.removeSync(e),console.debug(`PID file deleted: ${e}`))}catch(e){console.error(`Delete PID file failed: ${e.message}`)}}function g(e){try{return process.kill(e,0),!0}catch(t){return t.code!=="ESRCH"}}function K(e){try{return k()?R("powershell",["-NoProfile","-Command",`(Get-CimInstance Win32_Process -Filter "ProcessId = ${e}").CommandLine`],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim():R("ps",["-p",String(e),"-o","command="],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return""}}function X(e){if(!g(e))return!1;let t=K(e);if(!t)return!1;let o=t.toLowerCase(),s=f.basename(f.join(I,"..","server.js")).toLowerCase(),i=n.name.toLowerCase();return o.includes(s)||o.includes(i)}function z(){c.ensureDirSync(n.pidDir);let e=F(),t=`${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2,10)}`,o=JSON.stringify({pid:process.pid,token:t,createdAt:new Date().toISOString()},null,2);try{let s=c.openSync(e,"wx");return c.writeSync(s,o),{fd:s,token:t}}catch(s){if(s.code!=="EEXIST")throw s;try{let i=c.readFileSync(e,"utf8"),r=JSON.parse(i),a=Number(r?.pid),u=r?.createdAt?new Date(r.createdAt).getTime():0,l=Date.now()-u,C=Number.isFinite(a)&&a>0&&g(a),p=!u||Number.isNaN(l)||l>n.staleLockTimeout;if(!C||p){c.removeSync(e);let S=c.openSync(e,"wx");return c.writeSync(S,o),console.warn("Detected stale start lock, auto cleaned"),{fd:S,token:t}}}catch{c.removeSync(e);let r=c.openSync(e,"wx");return c.writeSync(r,o),console.warn("Detected invalid start lock, auto cleaned"),{fd:r,token:t}}throw s}}function Q(e){let t=e?.fd,o=e?.token;try{t!=null&&c.closeSync(t)}catch{}try{let s=F();if(c.existsSync(s)){let i=!1;if(!o)i=!0;else try{let r=c.readFileSync(s,"utf8");i=JSON.parse(r)?.token===o}catch{i=!0}i&&c.removeSync(s)}}catch{}}function k(){return process.platform==="win32"}async function Y(e=n.defaultStopTimeout){let t=x();return t?X(t.pid)?(console.log(`Existing service process detected (PID: ${t.pid}), stopping before start...`),await h(t.pid,!1)&&await D(t.pid,e)?(v(),{success:!0,message:`Existing process ${t.pid} stopped gracefully`}):(console.warn(`Graceful stop timeout or failed for PID ${t.pid}, force stop...`),await h(t.pid,!0)?await D(t.pid,e)?(v(),{success:!0,message:`Existing process ${t.pid} stopped forcibly`}):{success:!1,message:`Existing process ${t.pid} did not exit after force stop`}:{success:!1,message:`Failed to stop existing process ${t.pid}`})):(console.log(`Found stale PID file (PID: ${t.pid}), clean it...`),v(),{success:!0,message:"Stale PID file cleaned"}):{success:!0,message:"No existing service process"}}async function h(e,t=!1){return new Promise(o=>{if(!g(e)){console.debug(`Process ${e} does not exist, already stopped`),o(!0);return}let s=t?"SIGKILL":"SIGTERM",i=k()?"taskkill":"tree-kill";if(console.debug(`Use ${i} to stop process ${e} (signal: ${s})`),k()){let r=t?["/F","/PID",String(e)]:["/PID",String(e)],a=E("taskkill",r,{stdio:["ignore","pipe","pipe"],windowsHide:!0}),u="";a.stdout.on("data",l=>{u+=l.toString()}),a.stderr.on("data",l=>{u+=l.toString()}),a.on("error",l=>{console.error(`Stop process failed: ${l.message}`),o(!1)}),a.on("close",l=>{l===0?(console.debug(`Process ${e} stopped`),o(!0)):(console.warn(`taskkill exit code: ${l}, output: ${u}`),t?o(!1):h(e,!0).then(o))})}else V(e,s,r=>{r?r.code==="ESRCH"?(console.debug(`Process ${e} does not exist`),o(!0)):(console.error(`Stop process failed: ${r.message}`),o(!1)):(console.debug(`Process ${e} stopped`),o(!0))})})}async function D(e,t=n.defaultStopTimeout){let o=Date.now();for(;g(e);){if(Date.now()-o>t)return console.warn(`Wait for process ${e} to stop timeout (${t}ms)`),!1;await new Promise(i=>setTimeout(i,n.checkInterval))}let s=Date.now()-o;return console.debug(`Process ${e} stopped after ${s}ms`),!0}async function Z(e,t=n.defaultStartTimeout){let o=Number(e);if(!Number.isFinite(o)||o<=0)return!1;let s=Date.now();for(;Date.now()-s<=t;){if(await new Promise(r=>{let a=M.get({host:"127.0.0.1",port:o,path:"/health",timeout:Math.min(2e3,n.checkInterval*4)},u=>{r(u.statusCode>=200&&u.statusCode<300),u.resume()});a.on("timeout",()=>{a.destroy(),r(!1)}),a.on("error",()=>r(!1))}))return!0;await new Promise(r=>setTimeout(r,n.checkInterval))}return!1}async function N(e={}){let{env:t,port:o,config:s}=e,i=null;console.log(`Start service ${n.name}...`);try{i=z()}catch(r){return r.code==="EEXIST"?{success:!1,pid:null,message:"Another start operation is in progress, please retry later"}:{success:!1,pid:null,message:`Acquire start lock failed: ${r.message}`}}try{let r=Number(e.timeout)||n.defaultStopTimeout,a=await Y(r);if(!a.success)return{success:!1,pid:null,message:`Service start blocked: ${a.message}`};let u={...process.env};t&&(u.NODE_ENV=t,console.log(`Environment: ${t}`)),o&&(u.PORT=o,console.log(`Port: ${o}`)),s&&(u.CONFIG_FILE=s,console.log(`Configuration file: ${s}`));let l=J(),p=E("node",[l,...[]],{env:u,stdio:["pipe","pipe","pipe"],detached:!0,cwd:process.cwd()});if(p.stdout.on("data",m=>{process.stdout.write(m)}),p.stderr.on("data",m=>{process.stderr.write(m)}),p.on("error",m=>{console.error(`Start service failed: ${m.message}`)}),await new Promise(m=>setTimeout(m,2e3)),!g(p.pid))return{success:!1,pid:null,message:"Service start failed"};let S={pid:p.pid,startedAt:new Date().toISOString(),env:t||process.env.NODE_ENV||"production",port:o||process.env.PORT||"60000",version:W(),platform:process.platform};B(S);let P=Number(e.startTimeout)||n.defaultStartTimeout;return await Z(S.port,P)?(p.unref(),console.log(`Service started (PID: ${p.pid})`),console.log(`Service address: http://localhost:${S.port}`),{success:!0,pid:p.pid,message:"Service started successfully"}):(console.error(`Service health check timeout (${P}ms), stop failed instance...`),await h(p.pid,!0),v(),{success:!1,pid:null,message:`Service health check timeout (${P}ms)`})}finally{Q(i)}}async function T(e={}){let{force:t=!1,timeout:o=n.defaultStopTimeout}=e;console.log(`Stop service ${n.name}...`);let s=x();if(!s)return{success:!1,message:"Service not found"};if(!g(s.pid))return console.log("Service process has stopped, clean PID file..."),v(),{success:!0,message:"Service has stopped (process has exited)"};if(!await h(s.pid,t))return{success:!1,message:"Stop service failed"};let r=await D(s.pid,o);return v(),r?{success:!0,message:"Service has stopped"}:{success:!1,message:"Service stop timeout"}}async function O(e={}){console.log(`Restart service ${n.name}...`);let t=await T(e);!t.success&&t.message!=="Service not found"&&console.warn(`Stop service failed: ${t.message}`),await new Promise(s=>setTimeout(s,2e3));let o=await N(e);return o.success?{success:!0,pid:o.pid,message:"Service has restarted"}:{success:!1,pid:null,message:`Restart failed: ${o.message}`}}function _(){let e=x();return e?g(e.pid)?{running:!0,pidInfo:e,message:"Service running"}:{running:!1,pidInfo:e,message:"Service process does not exist"}:{running:!1,pidInfo:null,message:"Service not running"}}function L(e){try{let t=new Date(e),o=new Date;if(isNaN(t.getTime()))return"unknown";let s=Math.floor((o-t)/1e3),i=Math.floor(s/3600),r=Math.floor(s%3600/60),a=s%60;return i>0?`${i} hours ${r} minutes ${a} seconds`:r>0?`${r} minutes ${a} seconds`:`${a} seconds`}catch{return"unknown"}}var Pe=te(import.meta.url),A="1.5.1",d=new ee,w={reset:"\x1B[0m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",cyan:"\x1B[36m"};function j(e){console.error(`${w.red}ERROR: ${e}${w.reset}`)}function b(e){console.log(`${w.green}${e}${w.reset}`)}function oe(e){console.log(`${w.blue}${e}${w.reset}`)}function y(e){return async(...t)=>{try{let o=await e(...t);o&&o.success===!1?(j(o.message||"Command failed"),process.exitCode=1):process.exitCode=0}catch(o){j(o?.message||"Command failed with unexpected error"),process.exitCode=1}}}async function se(e){let t=await N({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(`Service started (PID: ${t.pid})`),t}async function re(e){let t=await T({force:e.force,timeout:Number(e.timeout)});return t.success&&b(t.message||"Service stopped"),t}async function ne(e){let t=await O({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(t.message||"Service restarted"),t}function ie(){let e=_();return oe(`${n.name} service status:`),console.log(""),console.log(` Service name: ${n.name}`),console.log(` Running status: ${e.running?"Running":"Stopped"}`),console.log(` Message: ${e.message}`),console.log(` PID file: ${$()}`),e.pidInfo&&(console.log(` Process ID: ${e.pidInfo.pid}`),console.log(` Environment: ${e.pidInfo.env||"Unknown"}`),console.log(` Port: ${e.pidInfo.port||"Unknown"}`),console.log(` Version: ${e.pidInfo.version||A}`),console.log(` Platform: ${e.pidInfo.platform||process.platform}`),console.log(` Started at: ${e.pidInfo.startedAt||"Unknown"}`),console.log(` Uptime: ${L(e.pidInfo.startedAt)}`)),console.log(""),{success:e.running,message:e.message}}function ce(){d.name("nuwax-file-server").description("Cross-platform file service deployment tool, supporting start/stop/restart/status").version(A,"-v, --version","Display version number").helpOption("-h, --help","Display help information"),d.command("start").allowUnknownOption().description("Start service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(se)),d.command("stop").description("Stop service").option("--force","Force stop").option("--timeout <ms>","\u505C\u6B62\u670D\u52A1\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).action(y(re)),d.command("restart").allowUnknownOption().description("Restart service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(ne)),d.command("status").description("View service status").action(y(ie)),d.command("help").description("Display help information").action(()=>{d.outputHelp()}),d.parse(process.argv),process.argv.slice(2).length||(d.outputHelp(),process.exitCode=0)}ce();
|
|
2
|
+
import{Command as ee}from"commander";import{createRequire as te}from"module";import f from"path";import U from"os";import c from"fs-extra";import{spawn as E}from"cross-spawn";import V from"tree-kill";import{fileURLToPath as q}from"url";import{execFileSync as R}from"child_process";import M from"http";import{createRequire as G}from"module";var I=f.dirname(q(import.meta.url)),H=G(import.meta.url),n={name:"nuwax-file-server",pidDir:f.join(U.tmpdir(),"nuwax-file-server"),pidFileName:"server.pid",lockFileName:"start.lock",defaultStopTimeout:3e4,defaultStartTimeout:3e4,staleLockTimeout:12e4,checkInterval:500};function $(){return f.join(n.pidDir,n.pidFileName)}function F(){return f.join(n.pidDir,n.lockFileName)}function J(){let e=[f.join(I,"..","server.js"),f.join(I,"server.js")];for(let t of e)if(c.existsSync(t))return t;return e[0]}function W(){let e=["../../package.json","../package.json"];for(let t of e)try{let o=H(t);if(o?.version)return o.version}catch{}return"unknown"}function x(){try{let e=$();if(!c.existsSync(e))return null;let t=c.readFileSync(e,"utf8"),o=JSON.parse(t);return!o||typeof o.pid!="number"?null:o}catch(e){return e.code!=="ENOENT"&&console.error(`Read PID file failed: ${e.message}`),null}}function B(e){try{let t=$();c.ensureDirSync(n.pidDir),c.writeFileSync(t,JSON.stringify(e,null,2)),console.debug(`PID file written: ${t}`)}catch(t){throw console.error(`Write PID file failed: ${t.message}`),t}}function v(){try{let e=$();c.existsSync(e)&&(c.removeSync(e),console.debug(`PID file deleted: ${e}`))}catch(e){console.error(`Delete PID file failed: ${e.message}`)}}function g(e){try{return process.kill(e,0),!0}catch(t){return t.code!=="ESRCH"}}function K(e){try{return k()?R("powershell",["-NoProfile","-Command",`(Get-CimInstance Win32_Process -Filter "ProcessId = ${e}").CommandLine`],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim():R("ps",["-p",String(e),"-o","command="],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return""}}function X(e){if(!g(e))return!1;let t=K(e);if(!t)return!1;let o=t.toLowerCase(),s=f.basename(f.join(I,"..","server.js")).toLowerCase(),i=n.name.toLowerCase();return o.includes(s)||o.includes(i)}function z(){c.ensureDirSync(n.pidDir);let e=F(),t=`${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2,10)}`,o=JSON.stringify({pid:process.pid,token:t,createdAt:new Date().toISOString()},null,2);try{let s=c.openSync(e,"wx");return c.writeSync(s,o),{fd:s,token:t}}catch(s){if(s.code!=="EEXIST")throw s;try{let i=c.readFileSync(e,"utf8"),r=JSON.parse(i),a=Number(r?.pid),u=r?.createdAt?new Date(r.createdAt).getTime():0,l=Date.now()-u,C=Number.isFinite(a)&&a>0&&g(a),p=!u||Number.isNaN(l)||l>n.staleLockTimeout;if(!C||p){c.removeSync(e);let S=c.openSync(e,"wx");return c.writeSync(S,o),console.warn("Detected stale start lock, auto cleaned"),{fd:S,token:t}}}catch{c.removeSync(e);let r=c.openSync(e,"wx");return c.writeSync(r,o),console.warn("Detected invalid start lock, auto cleaned"),{fd:r,token:t}}throw s}}function Q(e){let t=e?.fd,o=e?.token;try{t!=null&&c.closeSync(t)}catch{}try{let s=F();if(c.existsSync(s)){let i=!1;if(!o)i=!0;else try{let r=c.readFileSync(s,"utf8");i=JSON.parse(r)?.token===o}catch{i=!0}i&&c.removeSync(s)}}catch{}}function k(){return process.platform==="win32"}async function Y(e=n.defaultStopTimeout){let t=x();return t?X(t.pid)?(console.log(`Existing service process detected (PID: ${t.pid}), stopping before start...`),await h(t.pid,!1)&&await D(t.pid,e)?(v(),{success:!0,message:`Existing process ${t.pid} stopped gracefully`}):(console.warn(`Graceful stop timeout or failed for PID ${t.pid}, force stop...`),await h(t.pid,!0)?await D(t.pid,e)?(v(),{success:!0,message:`Existing process ${t.pid} stopped forcibly`}):{success:!1,message:`Existing process ${t.pid} did not exit after force stop`}:{success:!1,message:`Failed to stop existing process ${t.pid}`})):(console.log(`Found stale PID file (PID: ${t.pid}), clean it...`),v(),{success:!0,message:"Stale PID file cleaned"}):{success:!0,message:"No existing service process"}}async function h(e,t=!1){return new Promise(o=>{if(!g(e)){console.debug(`Process ${e} does not exist, already stopped`),o(!0);return}let s=t?"SIGKILL":"SIGTERM",i=k()?"taskkill":"tree-kill";if(console.debug(`Use ${i} to stop process ${e} (signal: ${s})`),k()){let r=t?["/F","/PID",String(e)]:["/PID",String(e)],a=E("taskkill",r,{stdio:["ignore","pipe","pipe"],windowsHide:!0}),u="";a.stdout.on("data",l=>{u+=l.toString()}),a.stderr.on("data",l=>{u+=l.toString()}),a.on("error",l=>{console.error(`Stop process failed: ${l.message}`),o(!1)}),a.on("close",l=>{l===0?(console.debug(`Process ${e} stopped`),o(!0)):(console.warn(`taskkill exit code: ${l}, output: ${u}`),t?o(!1):h(e,!0).then(o))})}else V(e,s,r=>{r?r.code==="ESRCH"?(console.debug(`Process ${e} does not exist`),o(!0)):(console.error(`Stop process failed: ${r.message}`),o(!1)):(console.debug(`Process ${e} stopped`),o(!0))})})}async function D(e,t=n.defaultStopTimeout){let o=Date.now();for(;g(e);){if(Date.now()-o>t)return console.warn(`Wait for process ${e} to stop timeout (${t}ms)`),!1;await new Promise(i=>setTimeout(i,n.checkInterval))}let s=Date.now()-o;return console.debug(`Process ${e} stopped after ${s}ms`),!0}async function Z(e,t=n.defaultStartTimeout){let o=Number(e);if(!Number.isFinite(o)||o<=0)return!1;let s=Date.now();for(;Date.now()-s<=t;){if(await new Promise(r=>{let a=M.get({host:"127.0.0.1",port:o,path:"/health",timeout:Math.min(2e3,n.checkInterval*4)},u=>{r(u.statusCode>=200&&u.statusCode<300),u.resume()});a.on("timeout",()=>{a.destroy(),r(!1)}),a.on("error",()=>r(!1))}))return!0;await new Promise(r=>setTimeout(r,n.checkInterval))}return!1}async function N(e={}){let{env:t,port:o,config:s}=e,i=null;console.log(`Start service ${n.name}...`);try{i=z()}catch(r){return r.code==="EEXIST"?{success:!1,pid:null,message:"Another start operation is in progress, please retry later"}:{success:!1,pid:null,message:`Acquire start lock failed: ${r.message}`}}try{let r=Number(e.timeout)||n.defaultStopTimeout,a=await Y(r);if(!a.success)return{success:!1,pid:null,message:`Service start blocked: ${a.message}`};let u={...process.env};t&&(u.NODE_ENV=t,console.log(`Environment: ${t}`)),o&&(u.PORT=o,console.log(`Port: ${o}`)),s&&(u.CONFIG_FILE=s,console.log(`Configuration file: ${s}`));let l=J(),p=E("node",[l,...[]],{env:u,stdio:["pipe","pipe","pipe"],detached:!0,cwd:process.cwd()});if(p.stdout.on("data",m=>{process.stdout.write(m)}),p.stderr.on("data",m=>{process.stderr.write(m)}),p.on("error",m=>{console.error(`Start service failed: ${m.message}`)}),await new Promise(m=>setTimeout(m,2e3)),!g(p.pid))return{success:!1,pid:null,message:"Service start failed"};let S={pid:p.pid,startedAt:new Date().toISOString(),env:t||process.env.NODE_ENV||"production",port:o||process.env.PORT||"60000",version:W(),platform:process.platform};B(S);let P=Number(e.startTimeout)||n.defaultStartTimeout;return await Z(S.port,P)?(p.unref(),console.log(`Service started (PID: ${p.pid})`),console.log(`Service address: http://localhost:${S.port}`),{success:!0,pid:p.pid,message:"Service started successfully"}):(console.error(`Service health check timeout (${P}ms), stop failed instance...`),await h(p.pid,!0),v(),{success:!1,pid:null,message:`Service health check timeout (${P}ms)`})}finally{Q(i)}}async function T(e={}){let{force:t=!1,timeout:o=n.defaultStopTimeout}=e;console.log(`Stop service ${n.name}...`);let s=x();if(!s)return{success:!1,message:"Service not found"};if(!g(s.pid))return console.log("Service process has stopped, clean PID file..."),v(),{success:!0,message:"Service has stopped (process has exited)"};if(!await h(s.pid,t))return{success:!1,message:"Stop service failed"};let r=await D(s.pid,o);return v(),r?{success:!0,message:"Service has stopped"}:{success:!1,message:"Service stop timeout"}}async function O(e={}){console.log(`Restart service ${n.name}...`);let t=await T(e);!t.success&&t.message!=="Service not found"&&console.warn(`Stop service failed: ${t.message}`),await new Promise(s=>setTimeout(s,2e3));let o=await N(e);return o.success?{success:!0,pid:o.pid,message:"Service has restarted"}:{success:!1,pid:null,message:`Restart failed: ${o.message}`}}function _(){let e=x();return e?g(e.pid)?{running:!0,pidInfo:e,message:"Service running"}:{running:!1,pidInfo:e,message:"Service process does not exist"}:{running:!1,pidInfo:null,message:"Service not running"}}function L(e){try{let t=new Date(e),o=new Date;if(isNaN(t.getTime()))return"unknown";let s=Math.floor((o-t)/1e3),i=Math.floor(s/3600),r=Math.floor(s%3600/60),a=s%60;return i>0?`${i} hours ${r} minutes ${a} seconds`:r>0?`${r} minutes ${a} seconds`:`${a} seconds`}catch{return"unknown"}}var Pe=te(import.meta.url),A="1.5.3",d=new ee,w={reset:"\x1B[0m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",cyan:"\x1B[36m"};function j(e){console.error(`${w.red}ERROR: ${e}${w.reset}`)}function b(e){console.log(`${w.green}${e}${w.reset}`)}function oe(e){console.log(`${w.blue}${e}${w.reset}`)}function y(e){return async(...t)=>{try{let o=await e(...t);o&&o.success===!1?(j(o.message||"Command failed"),process.exitCode=1):process.exitCode=0}catch(o){j(o?.message||"Command failed with unexpected error"),process.exitCode=1}}}async function se(e){let t=await N({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(`Service started (PID: ${t.pid})`),t}async function re(e){let t=await T({force:e.force,timeout:Number(e.timeout)});return t.success&&b(t.message||"Service stopped"),t}async function ne(e){let t=await O({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(t.message||"Service restarted"),t}function ie(){let e=_();return oe(`${n.name} service status:`),console.log(""),console.log(` Service name: ${n.name}`),console.log(` Running status: ${e.running?"Running":"Stopped"}`),console.log(` Message: ${e.message}`),console.log(` PID file: ${$()}`),e.pidInfo&&(console.log(` Process ID: ${e.pidInfo.pid}`),console.log(` Environment: ${e.pidInfo.env||"Unknown"}`),console.log(` Port: ${e.pidInfo.port||"Unknown"}`),console.log(` Version: ${e.pidInfo.version||A}`),console.log(` Platform: ${e.pidInfo.platform||process.platform}`),console.log(` Started at: ${e.pidInfo.startedAt||"Unknown"}`),console.log(` Uptime: ${L(e.pidInfo.startedAt)}`)),console.log(""),{success:e.running,message:e.message}}function ce(){d.name("nuwax-file-server").description("Cross-platform file service deployment tool, supporting start/stop/restart/status").version(A,"-v, --version","Display version number").helpOption("-h, --help","Display help information"),d.command("start").allowUnknownOption().description("Start service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(se)),d.command("stop").description("Stop service").option("--force","Force stop").option("--timeout <ms>","\u505C\u6B62\u670D\u52A1\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).action(y(re)),d.command("restart").allowUnknownOption().description("Restart service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(ne)),d.command("status").description("View service status").action(y(ie)),d.command("help").description("Display help information").action(()=>{d.outputHelp()}),d.parse(process.argv),process.argv.slice(2).length||(d.outputHelp(),process.exitCode=0)}ce();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import v from"express";import z from"multer";import g from"fs";import j from"path";import{ValidationError as P,asyncHandler as c}from"../utils/error/errorHandler.js";import{log as u}from"../utils/log/logUtils.js";import b from"../appConfig/index.js";import{createWorkspace as D,pushSkillsToWorkspace as T,initProjectTemplate as R,installProjectDependencies as M,executeCommand as _,deleteWorkspace as B,zipWorkspace as H,buildAgentPackage as L,cleanupBuildArtifacts as W}from"../utils/computer/computerUtils.js";import{getFileList as J,getFileMeta as K,resolveExistingFile as V,searchFiles as G,updateFiles as Z,uploadFile as X,uploadFiles as Y,generateFile as Q,downloadAllFiles as q,getLatestLogs as ee,importProject as te}from"../utils/computer/computerFileUtils.js";import{resolveServiceContext as d}from"../utils/computer/workspaceContext.js";import{listFsRoots as se,listFsChildren as ae,createFsDirectory as oe,renameFsDirectory as ne}from"../utils/computer/fsBrowserUtils.js";const A=v.Router(),I=z({storage:z.diskStorage({destination:(e,n,s)=>{try{const t=typeof e.headers["x-workspace-type"]=="string"?e.headers["x-workspace-type"].trim().toLowerCase():"",a=typeof e.headers["x-app-id"]=="string"?e.headers["x-app-id"].trim():"";let o;if(t==="userapp"&&a){const r=b.USERAPP_WORKSPACE_DIR;if(!r)return s(new Error("USERAPP_WORKSPACE_DIR is not configured, cannot determine upload temporary directory"));o=j.join(r,String(a),".tmp")}else{const r=b.COMPUTER_WORKSPACE_DIR;if(!r)return s(new Error("COMPUTER_WORKSPACE_DIR is not configured, cannot determine upload temporary directory"));const i=e.body?.userId||"unknown",l=e.body?.cId||"unknown";o=j.join(r,String(i),String(l),".tmp")}g.existsSync(o)||g.mkdirSync(o,{recursive:!0}),s(null,o)}catch(t){s(t)}},filename:(e,n,s)=>{const t=j.extname(n.originalname)||".zip",a=j.basename(n.originalname,t),o=`${Date.now()}_${Math.round(Math.random()*1e6)}`;s(null,`${a}_${o}${t}`)}}),limits:{fileSize:b.UPLOAD_MAX_FILE_SIZE_BYTES}});function re(e,n,s,t){if(e&&e.name==="MulterError"||e&&(e.name==="ValidationError"||e instanceof P))return t(e);t(e)}const ie=[{path:"/create-workspace",method:"post",middleware:I.single("file"),handler:c(async(e,n)=>{const{userId:s,cId:t}=e.body||{},a=e.file||null,o=d(e),r=`computer:${s}:${t}`;u(r,"INFO","Create workspace request",{userId:s,cId:t,workspaceType:o.workspaceType,appId:o.appId,hasFile:!!a,fileName:a?.originalname,fileSize:a?.size});const i=await D(s,t,a,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,o);n.status(200).json({success:!0,...i})})},{path:"/create-workspace-v2",method:"post",middleware:I.single("file"),handler:c(async(e,n)=>{const{userId:s,cId:t,agentId:a,skillUrls:o,skillUrlMap:r,skillNames:i,updateSkillNames:l,mcpServersConfig:p,hooksConfig:f,permissionsConfig:m,hookScripts:h}=e.body||{},k=e.file||null,C=d(e),O=`computer:${s}:${t}`;let N=o;if(typeof o=="string")try{N=JSON.parse(o)}catch{N=[o]}let w=r;if(typeof r=="string")try{w=JSON.parse(r)}catch{w=null}let F=i;if(typeof i=="string")try{const y=JSON.parse(i);F=Array.isArray(y)?y:[i]}catch{F=i.split(",").map(y=>y.trim()).filter(Boolean)}let S=l;if(typeof l=="string")try{const y=JSON.parse(l);S=Array.isArray(y)?y:[l]}catch{S=l.split(",").map(y=>y.trim()).filter(Boolean)}let $=h;if(typeof h=="string")try{$=JSON.parse(h)}catch{$=null}const x=Array.isArray(N)?N.length:0,E=w&&typeof w=="object"&&!Array.isArray(w)?Object.keys(w).length:0;u(O,"INFO","Create workspace v2 request",{userId:s,cId:t,agentId:a,workspaceType:C.workspaceType,appId:C.appId,hasFile:!!k,fileName:k?.originalname,fileSize:k?.size,skillUrlsCount:x,skillUrlMapCount:E,skillNamesCount:Array.isArray(F)?F.length:0,updateSkillNamesCount:Array.isArray(S)?S.length:0,hasMcpServersConfig:!!p,hasHooksConfig:!!f,hasPermissionsConfig:!!m,hookScriptsCount:Array.isArray($)?$.length:0});const U=await D(s,t,k,N,p,m,f,$,a,F,S,w,C);n.status(200).json({success:!0,...U})})},{path:"/push-skills-to-workspace",method:"post",middleware:I.single("file"),handler:c(async(e,n)=>{const{userId:s,cId:t}=e.body||{},a=e.file||null,o=d(e),r=`computer:${s}:${t}`;u(r,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42",{userId:s,cId:t,workspaceType:o.workspaceType,appId:o.appId,hasFile:!!a,fileName:a?.originalname,fileSize:a?.size});const i=await T(s,t,a,void 0,void 0,o);n.status(200).json({success:!0,...i})})},{path:"/push-skills-to-workspace-v2",method:"post",middleware:I.single("file"),handler:c(async(e,n)=>{const{userId:s,cId:t,agentId:a,skillUrls:o}=e.body||{},r=e.file||null,i=d(e),l=`computer:${s}:${t}`;let p=o;if(typeof o=="string")try{const m=JSON.parse(o);p=Array.isArray(m)?m:[o]}catch{p=[o]}u(l,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42(v2)",{userId:s,cId:t,agentId:a,workspaceType:i.workspaceType,appId:i.appId,hasFile:!!r,fileName:r?.originalname,fileSize:r?.size,skillUrlsCount:Array.isArray(p)?p.length:0});const f=await T(s,t,r,p,a,i);n.status(200).json({success:!0,...f})})},{path:"/fs/roots",method:"get",handler:c(async(e,n)=>{const s=await se();n.status(200).json({success:!0,...s})})},{path:"/fs/children",method:"get",handler:c(async(e,n)=>{const{path:s}=e.query,t=await ae(s);n.status(200).json({success:!0,...t})})},{path:"/fs/mkdir",method:"post",handler:c(async(e,n)=>{const{parentPath:s,dirName:t}=e.body||{},a=await oe(s,t);n.status(200).json({success:!0,...a})})},{path:"/fs/rename",method:"post",handler:c(async(e,n)=>{const{path:s,newName:t}=e.body||{},a=await ne(s,t);n.status(200).json({success:!0,...a})})},{path:"/get-file-list",method:"get",handler:c(async(e,n)=>{const{userId:s,cId:t,proxyPath:a,customTargetDir:o,relativePath:r,recursive:i}=e.query,l=await J(s,t,a,o,r,i,d(e));n.status(200).json({success:!0,...l})})},{path:"/resolve-file",method:"get",handler:c(async(e,n)=>{const{userId:s,cId:t,proxyPath:a,customTargetDir:o,filePath:r}=e.query,i=await V(s,t,r,a,o,d(e));n.status(200).json({success:!0,...i})})},{path:"/get-file-meta",method:"post",handler:c(async(e,n)=>{const{userId:s,cId:t,customTargetDir:a,filePaths:o,fileMetaMaxBatch:r}=e.body||{},i=await K(s,t,o,a,d(e),r);n.status(200).json({success:!0,...i})})},{path:"/search-files",method:"get",handler:c(async(e,n)=>{const{userId:s,cId:t,proxyPath:a,customTargetDir:o,relativePath:r,kw:i,limit:l,maxVisit:p,timeoutMs:f}=e.query,m=await G(s,t,a,i,o,r,l,p,f,d(e));n.status(200).json({success:!0,...m})})},{path:"/files-update",method:"post",handler:c(async(e,n)=>{const{userId:s,cId:t,files:a,customTargetDir:o}=e.body||{},r=`computer:${s}:${t}`;u(r,"INFO","Files update",{userId:s,cId:t,filesCount:a?a.length:0}),a&&Array.isArray(a)&&a.forEach(l=>{if(l&&typeof l.contents=="string"&&l.contents)try{l.contents=decodeURIComponent(l.contents)}catch(p){u(r,"WARN","Decode file content failed",{fileName:l.name,error:p.message})}});const i=await Z(s,t,a,o,d(e));n.status(200).json(i)})},{path:"/upload-file",method:"post",middleware:I.single("file"),handler:c(async(e,n)=>{const{userId:s,cId:t,filePath:a,customTargetDir:o}=e.body||{},r=e.file,i=`computer:${s}:${t}`;u(i,"INFO","Upload single file",{userId:s,cId:t,filePath:a});const p={buffer:await g.promises.readFile(r.path),originalname:r.originalname,mimetype:r.mimetype,size:r.size};try{const f=await X(s,t,p,a,o,d(e));n.status(200).json(f)}finally{g.existsSync(r.path)&&await g.promises.unlink(r.path)}})},{path:"/generate-file",method:"post",handler:c(async(e,n)=>{const{userId:s,cId:t,fileName:a,content:o,customTargetDir:r}=e.body||{},i=`computer:${s}:${t}`;u(i,"INFO","Generate file request",{userId:s,cId:t,fileName:a,contentLength:typeof o=="string"?o.length:0});const l=await Q(s,t,a,o,r,d(e));n.status(200).json(l)})},{path:"/import-project",method:"post",middleware:I.single("file"),handler:c(async(e,n)=>{const{userId:s,cId:t,customTargetDir:a}=e.body||{},o=e.file,r=`computer:${s}:${t}`;if(u(r,"INFO","Import project request",{userId:s,cId:t,fileName:o?.originalname,fileSize:o?.size}),!o)throw new P("file is required",{field:"file"});const i=await te(s,t,o,a,d(e));n.status(200).json(i)})},{path:"/upload-files",method:"post",middleware:I.array("files"),handler:c(async(e,n)=>{const{userId:s,cId:t,filePaths:a,customTargetDir:o}=e.body||{},r=e.files||[],i=`computer:${s}:${t}`,l=Array.isArray(a)?a:typeof a=="string"?[a]:a;u(i,"INFO","Batch upload files request",{userId:s,cId:t,filesCount:r.length,filePathsCount:Array.isArray(l)?l.length:0});const p=[],f=[];try{for(const h of r){f.push(h.path);const k=await g.promises.readFile(h.path);p.push({buffer:k,originalname:h.originalname,mimetype:h.mimetype,size:h.size})}const m=await Y(s,t,p,l,o,d(e));n.status(200).json(m)}finally{for(const m of f)if(g.existsSync(m))try{await g.promises.unlink(m)}catch(h){u(i,"WARN","Clean temporary file failed",{tempPath:m,error:h.message})}}})},{path:"/install-project",method:"post",handler:c(async(e,n)=>{const{userId:s,cId:t,programmingLanguage:a}=e.body||{},o=`computer:${s}:${t}`;u(o,"INFO","Install project request",{userId:s,cId:t,programmingLanguage:a});const r=await M(s,t,a,d(e));n.status(200).json({success:!0,...r})})},{path:"/init-project-template",method:"post",middleware:I.single("file"),handler:c(async(e,n)=>{const{userId:s,cId:t,enableGit:a}=e.body||{},o=e.file||null,r=`computer:${s}:${t}`;u(r,"INFO","Init project template request",{userId:s,cId:t,hasFile:!!o,fileName:o?.originalname,fileSize:o?.size,enableGit:a});const i=await R(s,t,o,a,d(e));n.status(200).json({success:!0,...i})})},{path:"/download-all-files",method:"get",handler:c(async(e,n)=>{const{userId:s,cId:t,customTargetDir:a}=e.query||{},o=`computer:${s}:${t}`;u(o,"INFO","Download all files request",{userId:s,cId:t});const{archive:r,zipFileName:i}=await q(s,t,a,d(e));n.setHeader("Content-Type","application/zip");const l=encodeURIComponent(i);n.setHeader("Content-Disposition",`attachment; filename="${l}"; filename*=UTF-8''${l}`),r.on("error",p=>{n.destroy(p)}),r.pipe(n),r.finalize()})},{path:"/execute-command",method:"post",handler:c(async(e,n)=>{const{userId:s,cId:t,command:a}=e.body||{},o=`computer:${s}:${t}`;u(o,"INFO","Execute command request",{userId:s,cId:t,command:a});const r=await _(s,t,a,d(e));n.status(200).json({success:!0,...r})})},{path:"/delete-workspace",method:"post",handler:c(async(e,n)=>{const{userId:s,cId:t}=e.body||{},a=`computer:${s}:${t}`;u(a,"INFO","Delete workspace request",{userId:s,cId:t});const o=await B(s,t,d(e));n.status(200).json({success:!0,...o})})},{path:"/get-logs",method:"get",handler:c(async(e,n)=>{const{userId:s,cId:t,tailLines:a}=e.query,o=a?parseInt(a,10):200,r=Number.isFinite(o)&&o>0?o:200,i=await ee(s,t,r,d(e));n.status(200).json(i)})},{path:"/zip-workspace",method:"post",handler:c(async(e,n)=>{const{userId:s,cId:t,excludeDirs:a}=e.body||{},o=`computer:${s}:${t}`;u(o,"INFO","Zip workspace request",{userId:s,cId:t,hasExcludeDirs:!!a});const{archive:r,zipFileName:i}=await H(s,t,a||null,d(e));n.setHeader("Content-Type","application/zip");const l=encodeURIComponent(i);n.setHeader("Content-Disposition",`attachment; filename="${l}"; filename*=UTF-8''${l}`),r.on("error",p=>{n.destroy(p)}),r.pipe(n),r.finalize()})},{path:"/build-agent-package",method:"post",handler:c(async(e,n)=>{const{userId:s,cId:t,agentId:a,version:o}=e.body||{},r=`computer:${s}:${t}`;u(r,"INFO","Build agent package request",{userId:s,cId:t,agentId:a,version:o});const i=await L(s,t,a,o,d(e));n.status(200).json({success:!0,...i})})},{path:"/cleanup-build-artifacts",method:"post",handler:c(async(e,n)=>{const{userId:s,cId:t}=e.body||{},a=`computer:${s}:${t}`;u(a,"INFO","Cleanup build artifacts request",{userId:s,cId:t});const o=await W(s,t,d(e));n.status(200).json({success:!0,...o})})}];ie.forEach(e=>{if(e.middleware)A[e.method](e.path,e.middleware,e.handler);else if(e.customHandler){const n=[];e.handler&&n.push(e.handler),e.decodeMiddleware&&n.push(e.decodeMiddleware),n.push(re),n.push(e.customHandler),A[e.method](e.path,...n)}else A[e.method](e.path,e.handler)});export default A;
|
|
1
|
+
import v from"express";import z from"multer";import g from"fs";import j from"path";import{ValidationError as P,asyncHandler as c}from"../utils/error/errorHandler.js";import{log as u}from"../utils/log/logUtils.js";import b from"../appConfig/index.js";import{createWorkspace as D,pushSkillsToWorkspace as T,initProjectTemplate as R,installProjectDependencies as M,executeCommand as _,deleteWorkspace as B,zipWorkspace as H,buildAgentPackage as L,cleanupBuildArtifacts as W}from"../utils/computer/computerUtils.js";import{getFileList as J,getFileMeta as K,resolveExistingFile as V,searchFiles as G,updateFiles as Z,uploadFile as X,uploadFiles as Y,generateFile as Q,downloadAllFiles as q,getLatestLogs as ee,importProject as te}from"../utils/computer/computerFileUtils.js";import{resolveServiceContext as p}from"../utils/computer/workspaceContext.js";import{listFsRoots as se,listFsChildren as ae,createFsDirectory as oe,renameFsDirectory as ne}from"../utils/computer/fsBrowserUtils.js";const A=v.Router(),I=z({storage:z.diskStorage({destination:(e,n,s)=>{try{const t=typeof e.headers["x-workspace-type"]=="string"?e.headers["x-workspace-type"].trim().toLowerCase():"",a=typeof e.headers["x-app-id"]=="string"?e.headers["x-app-id"].trim():"";let o;if(t==="userapp"&&a){const r=b.USERAPP_WORKSPACE_DIR;if(!r)return s(new Error("USERAPP_WORKSPACE_DIR is not configured, cannot determine upload temporary directory"));o=j.join(r,String(a),".tmp")}else{const r=b.COMPUTER_WORKSPACE_DIR;if(!r)return s(new Error("COMPUTER_WORKSPACE_DIR is not configured, cannot determine upload temporary directory"));const i=e.body?.userId||"unknown",l=e.body?.cId||"unknown";o=j.join(r,String(i),String(l),".tmp")}g.existsSync(o)||g.mkdirSync(o,{recursive:!0}),s(null,o)}catch(t){s(t)}},filename:(e,n,s)=>{const t=j.extname(n.originalname)||".zip",a=j.basename(n.originalname,t),o=`${Date.now()}_${Math.round(Math.random()*1e6)}`;s(null,`${a}_${o}${t}`)}}),limits:{fileSize:b.UPLOAD_MAX_FILE_SIZE_BYTES}});function re(e,n,s,t){if(e&&e.name==="MulterError"||e&&(e.name==="ValidationError"||e instanceof P))return t(e);t(e)}const ie=[{path:"/create-workspace",method:"post",middleware:I.single("file"),handler:c(async(e,n)=>{const{userId:s,cId:t}=e.body||{},a=e.file||null,o=p(e),r=`computer:${s}:${t}`;u(r,"INFO","Create workspace request",{userId:s,cId:t,workspaceType:o.workspaceType,appId:o.appId,hasFile:!!a,fileName:a?.originalname,fileSize:a?.size});const i=await D(s,t,a,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,o);n.status(200).json({success:!0,...i})})},{path:"/create-workspace-v2",method:"post",middleware:I.single("file"),handler:c(async(e,n)=>{const{userId:s,cId:t,agentId:a,skillUrls:o,skillUrlMap:r,skillNames:i,updateSkillNames:l,mcpServersConfig:d,hooksConfig:m,permissionsConfig:f,hookScripts:h}=e.body||{},k=e.file||null,C=p(e),O=`computer:${s}:${t}`;let N=o;if(typeof o=="string")try{N=JSON.parse(o)}catch{N=[o]}let w=r;if(typeof r=="string")try{w=JSON.parse(r)}catch{w=null}let F=i;if(typeof i=="string")try{const y=JSON.parse(i);F=Array.isArray(y)?y:[i]}catch{F=i.split(",").map(y=>y.trim()).filter(Boolean)}let S=l;if(typeof l=="string")try{const y=JSON.parse(l);S=Array.isArray(y)?y:[l]}catch{S=l.split(",").map(y=>y.trim()).filter(Boolean)}let $=h;if(typeof h=="string")try{$=JSON.parse(h)}catch{$=null}const x=Array.isArray(N)?N.length:0,E=w&&typeof w=="object"&&!Array.isArray(w)?Object.keys(w).length:0;u(O,"INFO","Create workspace v2 request",{userId:s,cId:t,agentId:a,workspaceType:C.workspaceType,appId:C.appId,hasFile:!!k,fileName:k?.originalname,fileSize:k?.size,skillUrlsCount:x,skillUrlMapCount:E,skillNamesCount:Array.isArray(F)?F.length:0,updateSkillNamesCount:Array.isArray(S)?S.length:0,hasMcpServersConfig:!!d,hasHooksConfig:!!m,hasPermissionsConfig:!!f,hookScriptsCount:Array.isArray($)?$.length:0});const U=await D(s,t,k,N,d,f,m,$,a,F,S,w,C);n.status(200).json({success:!0,...U})})},{path:"/push-skills-to-workspace",method:"post",middleware:I.single("file"),handler:c(async(e,n)=>{const{userId:s,cId:t}=e.body||{},a=e.file||null,o=p(e),r=`computer:${s}:${t}`;u(r,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42",{userId:s,cId:t,workspaceType:o.workspaceType,appId:o.appId,hasFile:!!a,fileName:a?.originalname,fileSize:a?.size});const i=await T(s,t,a,void 0,void 0,o);n.status(200).json({success:!0,...i})})},{path:"/push-skills-to-workspace-v2",method:"post",middleware:I.single("file"),handler:c(async(e,n)=>{const{userId:s,cId:t,agentId:a,skillUrls:o}=e.body||{},r=e.file||null,i=p(e),l=`computer:${s}:${t}`;let d=o;if(typeof o=="string")try{const f=JSON.parse(o);d=Array.isArray(f)?f:[o]}catch{d=[o]}u(l,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42(v2)",{userId:s,cId:t,agentId:a,workspaceType:i.workspaceType,appId:i.appId,hasFile:!!r,fileName:r?.originalname,fileSize:r?.size,skillUrlsCount:Array.isArray(d)?d.length:0});const m=await T(s,t,r,d,a,i);n.status(200).json({success:!0,...m})})},{path:"/fs/roots",method:"get",handler:c(async(e,n)=>{const s=await se();n.status(200).json({success:!0,...s})})},{path:"/fs/children",method:"get",handler:c(async(e,n)=>{const{path:s}=e.query,t=await ae(s);n.status(200).json({success:!0,...t})})},{path:"/fs/mkdir",method:"post",handler:c(async(e,n)=>{const{parentPath:s,dirName:t}=e.body||{},a=await oe(s,t);n.status(200).json({success:!0,...a})})},{path:"/fs/rename",method:"post",handler:c(async(e,n)=>{const{path:s,newName:t}=e.body||{},a=await ne(s,t);n.status(200).json({success:!0,...a})})},{path:"/get-file-list",method:"get",handler:c(async(e,n)=>{const{userId:s,cId:t,proxyPath:a,customTargetDir:o,relativePath:r,recursive:i,type:l,limit:d}=e.query,m=await J(s,t,a,o,r,i,l,d,p(e));n.status(200).json({success:!0,...m})})},{path:"/resolve-file",method:"get",handler:c(async(e,n)=>{const{userId:s,cId:t,proxyPath:a,customTargetDir:o,filePath:r}=e.query,i=await V(s,t,r,a,o,p(e));n.status(200).json({success:!0,...i})})},{path:"/get-file-meta",method:"post",handler:c(async(e,n)=>{const{userId:s,cId:t,customTargetDir:a,filePaths:o,fileMetaMaxBatch:r}=e.body||{},i=await K(s,t,o,a,p(e),r);n.status(200).json({success:!0,...i})})},{path:"/search-files",method:"get",handler:c(async(e,n)=>{const{userId:s,cId:t,proxyPath:a,customTargetDir:o,relativePath:r,kw:i,limit:l,maxVisit:d,timeoutMs:m}=e.query,f=await G(s,t,a,i,o,r,l,d,m,p(e));n.status(200).json({success:!0,...f})})},{path:"/files-update",method:"post",handler:c(async(e,n)=>{const{userId:s,cId:t,files:a,customTargetDir:o}=e.body||{},r=`computer:${s}:${t}`;u(r,"INFO","Files update",{userId:s,cId:t,filesCount:a?a.length:0}),a&&Array.isArray(a)&&a.forEach(l=>{if(l&&typeof l.contents=="string"&&l.contents)try{l.contents=decodeURIComponent(l.contents)}catch(d){u(r,"WARN","Decode file content failed",{fileName:l.name,error:d.message})}});const i=await Z(s,t,a,o,p(e));n.status(200).json(i)})},{path:"/upload-file",method:"post",middleware:I.single("file"),handler:c(async(e,n)=>{const{userId:s,cId:t,filePath:a,customTargetDir:o}=e.body||{},r=e.file,i=`computer:${s}:${t}`;u(i,"INFO","Upload single file",{userId:s,cId:t,filePath:a});const d={buffer:await g.promises.readFile(r.path),originalname:r.originalname,mimetype:r.mimetype,size:r.size};try{const m=await X(s,t,d,a,o,p(e));n.status(200).json(m)}finally{g.existsSync(r.path)&&await g.promises.unlink(r.path)}})},{path:"/generate-file",method:"post",handler:c(async(e,n)=>{const{userId:s,cId:t,fileName:a,content:o,customTargetDir:r}=e.body||{},i=`computer:${s}:${t}`;u(i,"INFO","Generate file request",{userId:s,cId:t,fileName:a,contentLength:typeof o=="string"?o.length:0});const l=await Q(s,t,a,o,r,p(e));n.status(200).json(l)})},{path:"/import-project",method:"post",middleware:I.single("file"),handler:c(async(e,n)=>{const{userId:s,cId:t,customTargetDir:a}=e.body||{},o=e.file,r=`computer:${s}:${t}`;if(u(r,"INFO","Import project request",{userId:s,cId:t,fileName:o?.originalname,fileSize:o?.size}),!o)throw new P("file is required",{field:"file"});const i=await te(s,t,o,a,p(e));n.status(200).json(i)})},{path:"/upload-files",method:"post",middleware:I.array("files"),handler:c(async(e,n)=>{const{userId:s,cId:t,filePaths:a,customTargetDir:o}=e.body||{},r=e.files||[],i=`computer:${s}:${t}`,l=Array.isArray(a)?a:typeof a=="string"?[a]:a;u(i,"INFO","Batch upload files request",{userId:s,cId:t,filesCount:r.length,filePathsCount:Array.isArray(l)?l.length:0});const d=[],m=[];try{for(const h of r){m.push(h.path);const k=await g.promises.readFile(h.path);d.push({buffer:k,originalname:h.originalname,mimetype:h.mimetype,size:h.size})}const f=await Y(s,t,d,l,o,p(e));n.status(200).json(f)}finally{for(const f of m)if(g.existsSync(f))try{await g.promises.unlink(f)}catch(h){u(i,"WARN","Clean temporary file failed",{tempPath:f,error:h.message})}}})},{path:"/install-project",method:"post",handler:c(async(e,n)=>{const{userId:s,cId:t,programmingLanguage:a}=e.body||{},o=`computer:${s}:${t}`;u(o,"INFO","Install project request",{userId:s,cId:t,programmingLanguage:a});const r=await M(s,t,a,p(e));n.status(200).json({success:!0,...r})})},{path:"/init-project-template",method:"post",middleware:I.single("file"),handler:c(async(e,n)=>{const{userId:s,cId:t,enableGit:a}=e.body||{},o=e.file||null,r=`computer:${s}:${t}`;u(r,"INFO","Init project template request",{userId:s,cId:t,hasFile:!!o,fileName:o?.originalname,fileSize:o?.size,enableGit:a});const i=await R(s,t,o,a,p(e));n.status(200).json({success:!0,...i})})},{path:"/download-all-files",method:"get",handler:c(async(e,n)=>{const{userId:s,cId:t,customTargetDir:a}=e.query||{},o=`computer:${s}:${t}`;u(o,"INFO","Download all files request",{userId:s,cId:t});const{archive:r,zipFileName:i}=await q(s,t,a,p(e));n.setHeader("Content-Type","application/zip");const l=encodeURIComponent(i);n.setHeader("Content-Disposition",`attachment; filename="${l}"; filename*=UTF-8''${l}`),r.on("error",d=>{n.destroy(d)}),r.pipe(n),r.finalize()})},{path:"/execute-command",method:"post",handler:c(async(e,n)=>{const{userId:s,cId:t,command:a}=e.body||{},o=`computer:${s}:${t}`;u(o,"INFO","Execute command request",{userId:s,cId:t,command:a});const r=await _(s,t,a,p(e));n.status(200).json({success:!0,...r})})},{path:"/delete-workspace",method:"post",handler:c(async(e,n)=>{const{userId:s,cId:t}=e.body||{},a=`computer:${s}:${t}`;u(a,"INFO","Delete workspace request",{userId:s,cId:t});const o=await B(s,t,p(e));n.status(200).json({success:!0,...o})})},{path:"/get-logs",method:"get",handler:c(async(e,n)=>{const{userId:s,cId:t,tailLines:a}=e.query,o=a?parseInt(a,10):200,r=Number.isFinite(o)&&o>0?o:200,i=await ee(s,t,r,p(e));n.status(200).json(i)})},{path:"/zip-workspace",method:"post",handler:c(async(e,n)=>{const{userId:s,cId:t,excludeDirs:a}=e.body||{},o=`computer:${s}:${t}`;u(o,"INFO","Zip workspace request",{userId:s,cId:t,hasExcludeDirs:!!a});const{archive:r,zipFileName:i}=await H(s,t,a||null,p(e));n.setHeader("Content-Type","application/zip");const l=encodeURIComponent(i);n.setHeader("Content-Disposition",`attachment; filename="${l}"; filename*=UTF-8''${l}`),r.on("error",d=>{n.destroy(d)}),r.pipe(n),r.finalize()})},{path:"/build-agent-package",method:"post",handler:c(async(e,n)=>{const{userId:s,cId:t,agentId:a,version:o}=e.body||{},r=`computer:${s}:${t}`;u(r,"INFO","Build agent package request",{userId:s,cId:t,agentId:a,version:o});const i=await L(s,t,a,o,p(e));n.status(200).json({success:!0,...i})})},{path:"/cleanup-build-artifacts",method:"post",handler:c(async(e,n)=>{const{userId:s,cId:t}=e.body||{},a=`computer:${s}:${t}`;u(a,"INFO","Cleanup build artifacts request",{userId:s,cId:t});const o=await W(s,t,p(e));n.status(200).json({success:!0,...o})})}];ie.forEach(e=>{if(e.middleware)A[e.method](e.path,e.middleware,e.handler);else if(e.customHandler){const n=[];e.handler&&n.push(e.handler),e.decodeMiddleware&&n.push(e.decodeMiddleware),n.push(re),n.push(e.customHandler),A[e.method](e.path,...n)}else A[e.method](e.path,e.handler)});export default A;
|
|
@@ -1,8 +1 @@
|
|
|
1
|
-
import{resolveWorkspaceDir as ue,WORKSPACE_TYPE as _}from"../utils/computer/workspaceContext.js";import N from"path";import n from"fs";import he from"crypto";import f from"isomorphic-git";import{createPatch as me}from"diff";import B from"../appConfig/index.js";import{log as w}from"../utils/log/logUtils.js";import{ValidationError as S,BusinessError as C,SystemError as k,ResourceError as D}from"../utils/error/errorHandler.js";import{resolveProjectPath as ge}from"../utils/common/projectPathUtils.js";import{isGitRepo as we,ensureGitRepo as O,ensureGitignore as M,addAll as pe,commitAllChanges as ye,stageFiles as q,getDefaultAuthor as Fe,hasGitHead as W}from"../utils/git/gitUtils.js";function A(o){const{workspaceType:e,projectId:s,isolationContext:t,userId:a,cId:r,serviceContext:i}=o||{},c=h=>{const g=String(h||"").trim().toLowerCase();return Object.values(_).find(l=>l.toLowerCase()===g)||""},m=i&&i.workspaceType||c(e);if(!m)throw new S("workspaceType is required and must be one of userApp, pageApp, normalProject, taskAgent",{field:"workspaceType"});if(m===_.PAGEAPP){if(!s)throw new S("pageApp mode requires projectId",{field:"projectId"});const h=ge(s,t||{});if(!n.existsSync(h))throw new D("Project does not exist",{projectId:s});return{targetPath:h,logId:s}}if(!a||!r)throw new S("conversation workspace mode requires userId and cId",{field:"userId/cId"});const u=i||{workspaceType:m,isUserApp:m===_.USERAPP,isNormalProject:m===_.NORMAL_PROJECT};if((u.isUserApp||u.isNormalProject)&&!u.workspacePath&&!u.appId)throw new S(`appId(projectId) is required for ${m} workspace when workspacePath is absent`,{field:"appId"});const d=ue(u,a,r);if(!n.existsSync(d))throw new D("Workspace does not exist",{userId:a,cId:r,targetPath:d,workspaceType:m});return{targetPath:d,logId:`computer:${a}:${r}`}}function z(o){for(let e=0;e<Math.min(o.length,8e3);e++)if(o[e]===0)return!0;return!1}function j(o){const e=Buffer.from(`blob ${o.length}\0`);return he.createHash("sha1").update(Buffer.concat([e,o])).digest("hex")}function be(o,e,s,t,a,r,i){const u=me(o,t?e:"",a?s:"",t?`a/${o}`:"/dev/null",a?`b/${o}`:"/dev/null").split(`
|
|
2
|
-
`).slice(4);u.length>0&&u[u.length-1]===""&&u.pop();const d=[];let h=0,g=0;for(const p of u){if(p.startsWith("@@")){const y=p.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);if(y){const P=y[1],F=y[2]!==void 0?parseInt(y[2],10):1,T=y[3],b=y[4]!==void 0?parseInt(y[4],10):1,$=F===1?P:`${P},${F}`,G=b===1?T:`${T},${b}`;d.push(`@@ -${$} +${G} @@`);continue}}p.startsWith("+")&&!p.startsWith("+++")?h++:p.startsWith("-")&&!p.startsWith("---")&&g++,d.push(p)}const l=t&&e!==""&&!e.endsWith(`
|
|
3
|
-
`),I=a&&s!==""&&!s.endsWith(`
|
|
4
|
-
`);if((l||I)&&d.length>0){let p=0;for(let b=0;b<d.length;b++)d[b].startsWith("@@")&&(p=b);let y=-1,P=-1,F=-1;for(let b=d.length-1;b>p;b--){const $=d[b];$.startsWith("-")&&!$.startsWith("---")&&y<0?y=b:$.startsWith("+")&&!$.startsWith("++")&&P<0?P=b:$.startsWith(" ")&&F<0&&(F=b)}const T=Math.max(y,P,F);if(T>=0){const b=d[T];if(b.startsWith(" "))(l||I)&&d.splice(T+1,0,"\");else if(b.startsWith("+")){let $=-1;for(let G=T-1;G>p;G--)if(d[G].startsWith("-")&&!d[G].startsWith("---")){$=G;break}$>=0&&l?(d.splice($+1,0,"\"),I&&d.splice(T+2,0,"\")):I&&d.splice(T+1,0,"\")}else b.startsWith("-")&&l&&d.splice(T+1,0,"\")}}const v=t&&r?j(r).substring(0,7):"0000000",x=a&&i?j(i).substring(0,7):"0000000",R=[];return R.push(`diff --git a/${o} b/${o}`),t?a?R.push(`index ${v}..${x} 100644`):(R.push("deleted file mode 100644"),R.push(`index ${v}..0000000`)):(R.push("new file mode 100644"),R.push(`index 0000000..${x}`)),R.push(`--- ${t?`a/${o}`:"/dev/null"}`),R.push(`+++ ${a?`b/${o}`:"/dev/null"}`),{diff:[...R,...d].join(`
|
|
5
|
-
`)+`
|
|
6
|
-
`,insertions:h,deletions:g}}async function U(o){return f.resolveRef({fs:n,dir:o,ref:"HEAD"})}async function H(o,e,s){try{const{blob:t}=await f.readBlob({fs:n,dir:o,oid:e,filepath:s}),a=Buffer.from(t);return{content:a.toString("utf8"),buf:a}}catch{return null}}async function J(o={}){const{targetPath:e,logId:s}=A(o);if(we(e))return{success:!0,message:"Git repository already initialized",logId:s,alreadyExists:!0};try{return await f.init({fs:n,dir:e,defaultBranch:"main"}),M(e),await f.setConfig({fs:n,dir:e,path:"user.name",value:B.GIT_DEFAULT_AUTHOR_NAME}),await f.setConfig({fs:n,dir:e,path:"user.email",value:B.GIT_DEFAULT_AUTHOR_EMAIL}),w(s,"INFO","Git repository initialized",{logId:s,targetPath:e}),{success:!0,message:"Git repository initialized successfully",logId:s,alreadyExists:!1}}catch(t){throw w(s,"ERROR","Failed to initialize Git repository",{logId:s,error:t.message}),new k("Failed to initialize Git repository",{originalError:t.message})}}async function K(o={}){const{targetPath:e,logId:s}=A(o);await O(e);try{const t=await f.currentBranch({fs:n,dir:e,fullname:!1}),a=await f.statusMatrix({fs:n,dir:e}),r=[],i=[],c=[],m=[],u=[];for(const[d,h,g,l]of a)h!==l&&r.push(d),h===0&&l!==0&&c.push(d),(h===1&&l===0||g===0&&l!==0)&&m.push(d),l!==0&&g!==0&&g!==l&&i.push(d),h===0&&l===0&&g!==0&&u.push(d);return{success:!0,logId:s,current:t,staged:[...new Set(r)],modified:[...new Set(i)],created:[...new Set(c)],deleted:[...new Set(m)],untracked:[...new Set(u)],conflicted:[],ahead:0,behind:0,tracking:null}}catch(t){throw w(s,"ERROR","Failed to get Git status",{logId:s,error:t.message}),new k("Failed to get Git status",{originalError:t.message})}}async function V(o={}){const{message:e,files:s,authorName:t,authorEmail:a}=o;if(!e||typeof e!="string")throw new S("Commit message cannot be empty",{field:"message"});const{targetPath:r,logId:i}=A(o);await O(r);try{const c={name:t||B.GIT_DEFAULT_AUTHOR_NAME,email:a||B.GIT_DEFAULT_AUTHOR_EMAIL};if(Array.isArray(s)&&s.length>0){const u={};if(await q(r,s,{cache:u,force:!0}),!(await f.statusMatrix({fs:n,dir:r,cache:u,filepaths:s})).some(([,,,l])=>l!==1))return{success:!0,message:"Nothing to commit",logId:i,nothingToCommit:!0};const g=await f.commit({fs:n,dir:r,message:e,author:c,cache:u});return w(i,"INFO","Git commit successful",{logId:i,commitHash:g,message:e}),{success:!0,message:"Commit successful",logId:i,commit:g,summary:{changes:1}}}const m=await ye(r,{message:e,authorName:c.name,authorEmail:c.email,cache:{}});return m.nothingToCommit?{success:!0,message:"Nothing to commit",logId:i,nothingToCommit:!0}:(w(i,"INFO","Git commit successful",{logId:i,commitHash:m.commitHash,message:e,viaNative:m.viaNative===!0}),{success:!0,message:"Commit successful",logId:i,commit:m.commitHash,summary:{changes:1}})}catch(c){throw w(i,"ERROR","Failed to commit",{logId:i,error:c.message}),new k("Failed to commit",{originalError:c.message})}}async function Y(o={}){const{files:e}=o,{targetPath:s,logId:t}=A(o);await O(s);try{const a={};return Array.isArray(e)&&e.length>0?await q(s,e,{cache:a,force:!0}):await pe(s,{cache:a}),w(t,"INFO","Git add successful",{logId:t,filesCount:e?e.length:"all"}),{success:!0,message:"Files staged successfully",logId:t}}catch(a){throw w(t,"ERROR","Failed to add files",{logId:t,error:a.message}),new k("Failed to add files",{originalError:a.message})}}async function Q(o={}){const{files:e}=o,{targetPath:s,logId:t}=A(o);await O(s);try{if(Array.isArray(e)&&e.length>0){for(const i of e)await f.resetIndex({fs:n,dir:s,filepath:i});return w(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(([,,,i])=>i!==1).map(([i])=>i);for(const i of r)await f.resetIndex({fs:n,dir:s,filepath:i});return w(t,"INFO","Git unstage all files",{logId:t}),{success:!0,message:"All files unstaged successfully",logId:t,files:"all"}}catch(a){throw w(t,"ERROR","Failed to unstage",{logId:t,error:a.message}),new k("Failed to unstage files",{originalError:a.message})}}async function L(o,e){const s=new Set;for(const a of e){let r=N.dirname(a);for(;r&&r!==".";)s.add(r),r=N.dirname(r)}const t=[...s].sort((a,r)=>{const i=a.split(/[/\\]/).length;return r.split(/[/\\]/).length-i});for(const a of t){const r=N.join(o,a);try{(await n.promises.readdir(r)).length===0&&await n.promises.rmdir(r)}catch{}}}async function X(o={}){const{files:e}=o,{targetPath:s,logId:t}=A(o);await O(s);try{const a=await f.statusMatrix({fs:n,dir:s}),r=Array.isArray(e)&&e.length>0?new Set(e):null,i=[],c=[],m=[];for(const[d,h,g,l]of a)r&&!r.has(d)||(h!==0?(l!==1||g!==1)&&i.push(d):h===0&&l!==0?c.push(d):h===0&&l===0&&g!==0&&m.push(d));if(i.length>0){const d=await U(s);for(const h of i){const g=await H(s,d,h);if(!g)continue;const l=N.join(s,h);n.mkdirSync(N.dirname(l),{recursive:!0}),n.writeFileSync(l,g.buf),await f.add({fs:n,dir:s,filepath:h})}}for(const d of c){try{await f.remove({fs:n,dir:s,filepath:d})}catch{}const h=N.join(s,d);n.existsSync(h)&&await n.promises.unlink(h)}c.length>0&&await L(s,c);for(const d of m){const h=N.join(s,d);n.existsSync(h)&&await n.promises.unlink(h)}m.length>0&&await L(s,m);const u=i.length+c.length+m.length;return w(t,"INFO","Git discard",{logId:t,trackedFiles:i.length,newFiles:c.length,untrackedFiles:m.length}),{success:!0,message:"Files discarded successfully",logId:t,discardedCount:u,trackedFiles:i,newFiles:c,untrackedFiles:m}}catch(a){throw w(t,"ERROR","Failed to discard",{logId:t,error:a.message}),new k("Failed to discard files",{originalError:a.message})}}async function Z(o={}){const{maxCount:e=50,branch:s,skip:t=0,filePath:a}=o,{targetPath:r,logId:i}=A(o);await O(r);try{if(!await W(r))return{success:!0,logId:i,commits:[],total:0};const c=Math.min(Math.max(1,e),500),m=Math.max(0,t),u={fs:n,dir:r,depth:c+m};s&&(u.ref=s),a&&(u.filepath=a);const g=(await f.log(u)).slice(m,m+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:i,commits:g,total:g.length}}catch(c){if(c?.code==="NotFoundError"||/NotFoundError|Could not find|unable to resolve|does not have any commits/i.test(String(c?.message||"")))return{success:!0,logId:i,commits:[],total:0};throw w(i,"ERROR","Failed to get Git log",{logId:i,error:c.message}),new k("Failed to get Git log",{originalError:c.message})}}async function ee(o={}){const{source:e="worktree",from:s,to:t,paths:a}=o,{targetPath:r,logId:i}=A(o);await O(r);try{const c=Array.isArray(a)&&a.length>0?new Set(a):null;let m="";const u=[];let d=0,h=0;async function g(l,I,v){const x=!!I,R=!!v;if(x&&R&&I.content===v.content)return;let E=!1;if(x&&z(I.buf)&&(E=!0),R&&!E&&z(v.buf)&&(E=!0),E){const F=x?j(I.buf).substring(0,7):"0000000",T=R?j(v.buf).substring(0,7):"0000000",b=[`diff --git a/${l} b/${l}`];x?R?b.push(`index ${F}..${T} 100644`):(b.push("deleted file mode 100644"),b.push(`index ${F}..0000000`)):(b.push("new file mode 100644"),b.push(`index 0000000..${T}`)),b.push(`Binary files ${x?`a/${l}`:"/dev/null"} and ${R?`b/${l}`:"/dev/null"} differ`),m+=b.join(`
|
|
7
|
-
`)+`
|
|
8
|
-
`,u.push({file:l,changes:0,insertions:0,deletions:0,binary:!0});return}const{diff:p,insertions:y,deletions:P}=be(l,x?I.content:"",R?v.content:"",x,R,x?I.buf:null,R?v.buf:null);m+=p,u.push({file:l,changes:y+P,insertions:y,deletions:P,binary:!1}),d+=y,h+=P}if(e==="commit"){let l,I;if(s&&t)l=s,I=t;else if(s){const E=await f.readCommit({fs:n,dir:r,oid:s});E.commit.parent&&E.commit.parent.length>0?(l=E.commit.parent[0],I=s):(l=null,I=s)}else throw new S("source=commit requires at least 'from'",{field:"from"});const v=l?new Set(await f.listFiles({fs:n,dir:r,ref:l})):new Set,x=new Set(await f.listFiles({fs:n,dir:r,ref:I})),R=new Set([...v,...x]);for(const E of R){if(c&&!c.has(E))continue;const p=v.has(E)?await H(r,l,E):null,y=x.has(E)?await H(r,I,E):null;await g(E,p,y)}}else{if(!await W(r))return{success:!0,logId:i,source:e,diff:"",summary:{files:[],insertions:0,deletions:0}};const l=await U(r),I=await f.statusMatrix({fs:n,dir:r});for(const[v,x,R,E]of I){if(c&&!c.has(v))continue;if(e==="staged"){if(E===1||x===0&&E===0)continue}else if(x===0&&E===0||x===1&&R===1&&E===1)continue;const p=x!==0?await H(r,l,v):null;let y=null;if(R!==0){const P=N.join(r,v);if(n.existsSync(P))try{const F=n.readFileSync(P);y={content:F.toString("utf8"),buf:F}}catch{}}await g(v,p,y)}}return{success:!0,logId:i,source:e,diff:m,summary:{files:u,insertions:d,deletions:h}}}catch(c){throw c instanceof S?c:(w(i,"ERROR","Failed to get Git diff",{logId:i,error:c.message}),new k("Failed to get Git diff",{originalError:c.message}))}}async function te(o={}){const{ref:e="HEAD",filePath:s}=o;if(!s)throw new S("filePath is required",{field:"filePath"});const{targetPath:t,logId:a}=A(o);await O(t);try{let r;if(e==="worktree"||e==="staged"||e===""){const i=N.join(t,s);r=n.existsSync(i)?n.readFileSync(i,"utf8"):""}else try{const i=await f.resolveRef({fs:n,dir:t,ref:e}),{blob:c}=await f.readBlob({fs:n,dir:t,oid:i,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 D?r:(w(a,"ERROR","Failed to get file content",{logId:a,ref:e,filePath:s,error:r.message}),new k("Failed to get file content",{originalError:r.message}))}}async function se(o={}){const{target:e,mode:s="mixed"}=o;if(!e)throw new S("Reset target cannot be empty",{field:"target"});if(!["soft","mixed","hard"].includes(s))throw new S("Mode must be soft, mixed or hard",{field:"mode"});const{targetPath:t,logId:a}=A(o);await O(t);try{if(!await W(t))throw new C("Cannot reset: repository has no commits yet");const r=await f.log({fs:n,dir:t,depth:1}),i=r.length>0?r[0].oid:null,c=await f.currentBranch({fs:n,dir:t,fullname:!1});if(!c)throw new C("Cannot reset: detached HEAD");const m=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}),d=new Set(u);for(const g of u)await f.resetIndex({fs:n,dir:t,filepath:g,ref:e});const h=await f.statusMatrix({fs:n,dir:t});for(const[g,,,l]of h)if(!d.has(g)&&l!==0)try{await f.remove({fs:n,dir:t,filepath:g})}catch{}}if(s==="hard"){const u=await f.listFiles({fs:n,dir:t,ref:e}),d=new Set(u);for(const h of u){const g=await H(t,e,h);if(!g)continue;const l=N.join(t,h);n.mkdirSync(N.dirname(l),{recursive:!0}),n.writeFileSync(l,g.buf)}if(m){for(const h of m)if(!d.has(h)){const g=N.join(t,h);n.existsSync(g)&&n.unlinkSync(g)}}M(t),await f.add({fs:n,dir:t,filepath:".gitignore"})}return w(a,"INFO","Git reset successful",{logId:a,target:e,mode:s,previousHead:i}),{success:!0,message:`Reset (${s}) to ${e} successful`,logId:a,target:e,mode:s,previousHead:i}}catch(r){throw r instanceof C?r:(w(a,"ERROR","Failed to reset",{logId:a,target:e,mode:s,error:r.message}),new k("Failed to reset",{originalError:r.message}))}}async function re(o={}){const{target:e,message:s,authorName:t,authorEmail:a}=o;if(!e)throw new S("Revert target cannot be empty",{field:"target"});const{targetPath:r,logId:i}=A(o);await O(r);try{if(!await W(r))throw new C("Cannot revert: repository has no commits yet");let c;try{c=(await f.readCommit({fs:n,dir:r,oid:e})).oid}catch{throw new S("Revert target commit does not exist",{field:"target",target:e})}const m=await f.statusMatrix({fs:n,dir:r});if(m.some(([p,y,P,F])=>y===0&&F===0?!1:P!==1||F!==1)){const p=m.filter(([,F])=>F!==0),y=p.filter(([,,F])=>F!==1).map(([F])=>F),P=p.filter(([,,,F])=>F!==1).map(([F])=>F);throw new C("Working directory is not clean, please commit or stash your changes before reverting",{staged:P,modified:y})}const d=new Set(await f.listFiles({fs:n,dir:r,ref:c})),h=await U(r),g=new Set(await f.listFiles({fs:n,dir:r,ref:h})),l=[];for(const p of d){const y=await H(r,c,p);if(!y)continue;const P=N.join(r,p);n.mkdirSync(N.dirname(P),{recursive:!0}),n.writeFileSync(P,y.buf),await f.add({fs:n,dir:r,filepath:p})}for(const p of g)if(!d.has(p)){l.push(p);try{await f.remove({fs:n,dir:r,filepath:p})}catch{}const y=N.join(r,p);n.existsSync(y)&&await n.promises.unlink(y)}if(l.length>0&&await L(r,l),M(r),await f.add({fs:n,dir:r,filepath:".gitignore"}),!(await f.statusMatrix({fs:n,dir:r})).some(([,,,p])=>p!==1))return w(i,"INFO","Nothing to revert, current HEAD already matches target",{logId:i,target:c}),{success:!0,message:"Nothing to revert, already at target state",logId:i,nothingToCommit:!0,target:c};const x={name:t||B.GIT_DEFAULT_AUTHOR_NAME,email:a||B.GIT_DEFAULT_AUTHOR_EMAIL},R=s||`Revert to ${c.substring(0,7)}`,E=await f.commit({fs:n,dir:r,message:R,author:x});return w(i,"INFO","Git revert successful",{logId:i,target:c,commitHash:E,previousHead:h,removedFilesCount:l.length}),{success:!0,message:"Revert successful",logId:i,commit:E,target:c,previousHead:h}}catch(c){throw c instanceof S||c instanceof C?c:(w(i,"ERROR","Failed to revert",{logId:i,target:e,error:c.message}),new k("Failed to revert",{originalError:c.message}))}}async function ae(o={}){const{target:e}=o;if(!e)throw new S("Checkout target cannot be empty",{field:"target"});const{targetPath:s,logId:t}=A(o);await O(s);try{const a=await f.listFiles({fs:n,dir:s,ref:e});for(const r of a){const i=await H(s,e,r);if(!i)continue;const c=N.join(s,r);n.mkdirSync(N.dirname(c),{recursive:!0}),n.writeFileSync(c,i.buf),await f.add({fs:n,dir:s,filepath:r})}return M(s),await f.add({fs:n,dir:s,filepath:".gitignore"}),w(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 w(t,"ERROR","Failed to checkout files",{logId:t,target:e,error:a.message}),new k("Failed to checkout files",{originalError:a.message})}}async function ie(o={}){const{targetPath:e,logId:s}=A(o);await O(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 w(s,"ERROR","Failed to list tags",{logId:s,error:t.message}),new k("Failed to list tags",{originalError:t.message})}}async function ne(o={}){const{tagName:e,message:s}=o;if(!e)throw new S("Tag name cannot be empty",{field:"tagName"});const{targetPath:t,logId:a}=A(o);await O(t);try{return s?await f.annotatedTag({fs:n,dir:t,ref:e,message:s,tagger:Fe()}):await f.tag({fs:n,dir:t,ref:e}),w(a,"INFO","Git tag created",{logId:a,tagName:e,annotated:!!s}),{success:!0,message:"Tag created successfully",logId:a,tagName:e}}catch(r){throw w(a,"ERROR","Failed to create tag",{logId:a,tagName:e,error:r.message}),new k("Failed to create tag",{originalError:r.message})}}async function oe(o={}){const{tagName:e}=o;if(!e)throw new S("Tag name cannot be empty",{field:"tagName"});const{targetPath:s,logId:t}=A(o);await O(s);try{return await f.deleteRef({fs:n,dir:s,ref:`refs/tags/${e}`}),w(t,"INFO","Git tag deleted",{logId:t,tagName:e}),{success:!0,message:"Tag deleted successfully",logId:t,tagName:e}}catch(a){throw w(t,"ERROR","Failed to delete tag",{logId:t,tagName:e,error:a.message}),new k("Failed to delete tag",{originalError:a.message})}}async function ce(o={}){const{targetPath:e,logId:s}=A(o);await O(e);try{const t=await f.listBranches({fs:n,dir:e}),a=await f.currentBranch({fs:n,dir:e,fullname:!1}),r={};for(const i of t)r[i]={name:i,current:i===a};return{success:!0,logId:s,branches:r,current:a}}catch(t){throw w(s,"ERROR","Failed to list branches",{logId:s,error:t.message}),new k("Failed to list branches",{originalError:t.message})}}async function le(o={}){const{branchName:e,startPoint:s}=o;if(!e)throw new S("Branch name cannot be empty",{field:"branchName"});const{targetPath:t,logId:a}=A(o);await O(t);try{const r=s||"HEAD";return await f.branch({fs:n,dir:t,ref:e,object:r,checkout:!0}),w(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 w(a,"ERROR","Failed to create branch",{logId:a,branchName:e,error:r.message}),new k("Failed to create branch",{originalError:r.message})}}async function fe(o={}){const{branchName:e}=o;if(!e)throw new S("Branch name cannot be empty",{field:"branchName"});const{targetPath:s,logId:t}=A(o);await O(s);try{const a=await f.statusMatrix({fs:n,dir:s});if(a.some(([i,c,m,u])=>c===0&&u===0?!1:m!==1||u!==1)){const i=a.filter(([,u])=>u!==0),c=i.filter(([,,u])=>u!==1).map(([u])=>u),m=i.filter(([,,,u])=>u!==1).map(([u])=>u);throw new C("Working directory is not clean, please commit or stash your changes before switching branches",{staged:m,modified:c})}return await f.checkout({fs:n,dir:s,ref:e}),w(t,"INFO","Git branch switched",{logId:t,branchName:e}),{success:!0,message:"Branch switched successfully",logId:t,branchName:e}}catch(a){throw a instanceof C?a:(w(t,"ERROR","Failed to switch branch",{logId:t,branchName:e,error:a.message}),new k("Failed to switch branch",{originalError:a.message}))}}async function de(o={}){const{branchName:e,force:s=!1}=o;if(!e)throw new S("Branch name cannot be empty",{field:"branchName"});const{targetPath:t,logId:a}=A(o);await O(t);try{if(await f.currentBranch({fs:n,dir:t,fullname:!1})===e)throw new C("Cannot delete the current branch, please switch to another branch first");return await f.deleteBranch({fs:n,dir:t,ref:e,force:s}),w(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 C?r:(w(a,"ERROR","Failed to delete branch",{logId:a,branchName:e,error:r.message}),new k("Failed to delete branch",{originalError:r.message}))}}export{J as init,K as status,V as commit,Y as add,Q as unstage,X as discard,Z as logHistory,ee as diff,te as fileContent,se as reset,re as revert,ae as checkout,ie as listTags,ne as createTag,oe as deleteTag,ce as listBranches,le as createBranch,fe as switchBranch,de as deleteBranch};export default{init:J,status:K,commit:V,add:Y,unstage:Q,discard:X,logHistory:Z,diff:ee,fileContent:te,reset:se,revert:re,checkout:ae,listTags:ie,createTag:ne,deleteTag:oe,listBranches:ce,createBranch:le,switchBranch:fe,deleteBranch:de};
|
|
1
|
+
import{resolveWorkspaceDir as _,WORKSPACE_TYPE as w}from"../utils/computer/workspaceContext.js";import I from"fs";import P from"../appConfig/index.js";import{log as h}from"../utils/log/logUtils.js";import{ValidationError as d,BusinessError as N,SystemError as q,ResourceError as p}from"../utils/error/errorHandler.js";import{resolveProjectPath as z}from"../utils/common/projectPathUtils.js";import{isGitRepo as W,ensureGitRepo as g,initRepo as V,getStatus as J,addAll as K,commitAllChanges as Y,commitFiles as Q,stageFiles as X,unstageFiles as Z,discardChanges as ee,getLog as te,getDiff as ae,getFileContentAtRef as se,resetTo as re,revertToTree as ce,checkoutFiles as ie,listTags as ne,createTag as oe,deleteTag as le,listBranches as ue,createBranch as de,switchBranch as ge,deleteBranch as he}from"../utils/git/gitUtils.js";function l(a){const{workspaceType:e,projectId:s,isolationContext:t,userId:r,cId:c,serviceContext:n}=a||{},o=m=>{const D=String(m||"").trim().toLowerCase();return Object.values(w).find(U=>U.toLowerCase()===D)||""},i=n&&n.workspaceType||o(e);if(!i)throw new d("workspaceType is required and must be one of userApp, pageApp, normalProject, taskAgent",{field:"workspaceType"});if(i===w.PAGEAPP){if(!s)throw new d("pageApp mode requires projectId",{field:"projectId"});const m=z(s,t||{});if(!I.existsSync(m))throw new p("Project does not exist",{projectId:s});return{targetPath:m,logId:s}}if(!r||!c)throw new d("conversation workspace mode requires userId and cId",{field:"userId/cId"});const f=n||{workspaceType:i,isUserApp:i===w.USERAPP,isNormalProject:i===w.NORMAL_PROJECT};if((f.isUserApp||f.isNormalProject)&&!f.workspacePath&&!f.appId)throw new d(`appId(projectId) is required for ${i} workspace when workspacePath is absent`,{field:"appId"});const y=_(f,r,c);if(!I.existsSync(y))throw new p("Workspace does not exist",{userId:r,cId:c,targetPath:y,workspaceType:i});return{targetPath:y,logId:`computer:${r}:${c}`}}function u(a,e,s,t={}){if(a instanceof d||a instanceof N||a instanceof p)throw a;const r=a&&typeof a=="object"&&"code"in a?a.code:null;if(r==="VALIDATION"){const c={...t};throw a&&typeof a=="object"&&(a.field!=null&&(c.field=a.field),a.target!=null&&(c.target=a.target)),new d(a.message||"Validation failed",c)}throw r==="BUSINESS"?new N(a.message||"Business error",{...a&&typeof a=="object"&&a.staged!=null?{staged:a.staged}:{},...a&&typeof a=="object"&&a.modified!=null?{modified:a.modified}:{},...t}):(h(e,"ERROR",`Failed to ${s}`,{logId:e,error:a.message,...t}),new q(`Failed to ${s}`,{originalError:a.message}))}async function b(a={}){const{targetPath:e,logId:s}=l(a);if(W(e))return{success:!0,message:"Git repository already initialized",logId:s,alreadyExists:!0};try{return await V(e),h(s,"INFO","Git repository initialized",{logId:s,targetPath:e}),{success:!0,message:"Git repository initialized successfully",logId:s,alreadyExists:!1}}catch(t){u(t,s,"initialize Git repository")}}async function k(a={}){const{targetPath:e,logId:s}=l(a);await g(e);try{const t=await J(e);return{success:!0,logId:s,current:t.current,staged:t.staged,modified:t.modified,created:t.created,deleted:t.deleted,untracked:t.untracked,conflicted:t.conflicted||[],ahead:t.ahead||0,behind:t.behind||0,tracking:t.tracking??null}}catch(t){u(t,s,"get Git status")}}async function F(a={}){const{message:e,files:s,authorName:t,authorEmail:r}=a;if(!e||typeof e!="string")throw new d("Commit message cannot be empty",{field:"message"});const{targetPath:c,logId:n}=l(a);await g(c);try{const o={name:t||P.GIT_DEFAULT_AUTHOR_NAME,email:r||P.GIT_DEFAULT_AUTHOR_EMAIL};let i;return Array.isArray(s)&&s.length>0?i=await Q(c,{message:e,files:s,authorName:o.name,authorEmail:o.email}):i=await Y(c,{message:e,authorName:o.name,authorEmail:o.email,cache:{}}),i.nothingToCommit?{success:!0,message:"Nothing to commit",logId:n,nothingToCommit:!0}:(h(n,"INFO","Git commit successful",{logId:n,commitHash:i.commitHash,message:e,viaNative:i.viaNative===!0}),{success:!0,message:"Commit successful",logId:n,commit:i.commitHash,summary:{changes:1}})}catch(o){u(o,n,"commit")}}async function T(a={}){const{files:e}=a,{targetPath:s,logId:t}=l(a);await g(s);try{return Array.isArray(e)&&e.length>0?await X(s,e,{force:!0}):await K(s,{cache:{}}),h(t,"INFO","Git add successful",{logId:t,filesCount:e?e.length:"all"}),{success:!0,message:"Files staged successfully",logId:t}}catch(r){u(r,t,"add files")}}async function A(a={}){const{files:e}=a,{targetPath:s,logId:t}=l(a);await g(s);try{const r=await Z(s,Array.isArray(e)&&e.length>0?e:null),c=r.files==="all"?"all files":"specified files";return h(t,"INFO",`Git unstage ${c}`,{logId:t,files:r.files}),{success:!0,message:r.files==="all"?"All files unstaged successfully":"Specified files unstaged successfully",logId:t,files:r.files}}catch(r){u(r,t,"unstage")}}async function v(a={}){const{files:e}=a,{targetPath:s,logId:t}=l(a);await g(s);try{const r=await ee(s,Array.isArray(e)&&e.length>0?e:null);return h(t,"INFO","Git discard",{logId:t,trackedFiles:r.trackedFiles.length,newFiles:r.newFiles.length,untrackedFiles:r.untrackedFiles.length}),{success:!0,message:"Files discarded successfully",logId:t,discardedCount:r.discardedCount,trackedFiles:r.trackedFiles,newFiles:r.newFiles,untrackedFiles:r.untrackedFiles}}catch(r){u(r,t,"discard")}}async function C(a={}){const{maxCount:e=50,branch:s,skip:t=0,filePath:r}=a,{targetPath:c,logId:n}=l(a);await g(c);try{const o=Math.min(Math.max(1,e),500),i=Math.max(0,t),f=await te(c,{maxCount:o,skip:i,branch:s,filePath:r});return{success:!0,logId:n,commits:f,total:f.length}}catch(o){u(o,n,"get Git log")}}async function O(a={}){const{source:e="worktree",from:s,to:t,paths:r}=a,{targetPath:c,logId:n}=l(a);await g(c);try{if(e==="commit"&&!s)throw new d("source=commit requires at least 'from'",{field:"from"});const o=await ae(c,{source:e,from:s,to:t,paths:r});return{success:!0,logId:n,source:e,diff:o.diff,summary:o.summary}}catch(o){u(o,n,"get Git diff",{source:e})}}async function E(a={}){const{ref:e="HEAD",filePath:s}=a;if(!s)throw new d("filePath is required",{field:"filePath"});const{targetPath:t,logId:r}=l(a);await g(t);try{const c=await se(t,{ref:e,filePath:s});return{success:!0,logId:r,filePath:s,ref:e,content:c}}catch(c){u(c,r,"get file content",{ref:e,filePath:s})}}async function G(a={}){const{target:e,mode:s="mixed"}=a;if(!e)throw new d("Reset target cannot be empty",{field:"target"});if(!["soft","mixed","hard"].includes(s))throw new d("Mode must be soft, mixed or hard",{field:"mode"});const{targetPath:t,logId:r}=l(a);await g(t);try{const c=await re(t,{target:e,mode:s});return h(r,"INFO","Git reset successful",{logId:r,target:e,mode:s,previousHead:c.previousHead}),{success:!0,message:`Reset (${s}) to ${e} successful`,logId:r,target:e,mode:s,previousHead:c.previousHead}}catch(c){u(c,r,"reset",{target:e,mode:s})}}async function B(a={}){const{target:e,message:s,authorName:t,authorEmail:r}=a;if(!e)throw new d("Revert target cannot be empty",{field:"target"});const{targetPath:c,logId:n}=l(a);await g(c);try{const o=s||`Revert to ${String(e).substring(0,7)}`,i=await ce(c,{target:e,message:o,authorName:t,authorEmail:r});return i.nothingToCommit?(h(n,"INFO","Nothing to revert, current HEAD already matches target",{logId:n,target:i.targetOid}),{success:!0,message:"Nothing to revert, already at target state",logId:n,nothingToCommit:!0,target:i.targetOid}):(h(n,"INFO","Git revert successful",{logId:n,target:i.targetOid,commitHash:i.commitHash,previousHead:i.previousHead}),{success:!0,message:"Revert successful",logId:n,commit:i.commitHash,target:i.targetOid,previousHead:i.previousHead})}catch(o){u(o,n,"revert",{target:e})}}async function H(a={}){const{target:e}=a;if(!e)throw new d("Checkout target cannot be empty",{field:"target"});const{targetPath:s,logId:t}=l(a);await g(s);try{return await ie(s,e),h(t,"INFO","Git checkout files successful",{logId:t,target:e}),{success:!0,message:`Checkout files from ${e} successful`,logId:t,target:e}}catch(r){u(r,t,"checkout files",{target:e})}}async function R(a={}){const{targetPath:e,logId:s}=l(a);await g(e);try{const t=await ne(e);return{success:!0,logId:s,tags:t,latest:t.length>0?t[t.length-1]:null}}catch(t){u(t,s,"list tags")}}async function x(a={}){const{tagName:e,message:s}=a;if(!e)throw new d("Tag name cannot be empty",{field:"tagName"});const{targetPath:t,logId:r}=l(a);await g(t);try{return await oe(t,{tagName:e,message:s}),h(r,"INFO","Git tag created",{logId:r,tagName:e,annotated:!!s}),{success:!0,message:"Tag created successfully",logId:r,tagName:e}}catch(c){u(c,r,"create tag",{tagName:e})}}async function j(a={}){const{tagName:e}=a;if(!e)throw new d("Tag name cannot be empty",{field:"tagName"});const{targetPath:s,logId:t}=l(a);await g(s);try{return await le(s,e),h(t,"INFO","Git tag deleted",{logId:t,tagName:e}),{success:!0,message:"Tag deleted successfully",logId:t,tagName:e}}catch(r){u(r,t,"delete tag",{tagName:e})}}async function S(a={}){const{targetPath:e,logId:s}=l(a);await g(e);try{const t=await ue(e);return{success:!0,logId:s,branches:t.branches,current:t.current}}catch(t){u(t,s,"list branches")}}async function L(a={}){const{branchName:e,startPoint:s}=a;if(!e)throw new d("Branch name cannot be empty",{field:"branchName"});const{targetPath:t,logId:r}=l(a);await g(t);try{return await de(t,{branchName:e,startPoint:s}),h(r,"INFO","Git branch created",{logId:r,branchName:e,startPoint:s}),{success:!0,message:"Branch created and switched to",logId:r,branchName:e}}catch(c){u(c,r,"create branch",{branchName:e})}}async function M(a={}){const{branchName:e}=a;if(!e)throw new d("Branch name cannot be empty",{field:"branchName"});const{targetPath:s,logId:t}=l(a);await g(s);try{return await ge(s,e),h(t,"INFO","Git branch switched",{logId:t,branchName:e}),{success:!0,message:"Branch switched successfully",logId:t,branchName:e}}catch(r){u(r,t,"switch branch",{branchName:e})}}async function $(a={}){const{branchName:e,force:s=!1}=a;if(!e)throw new d("Branch name cannot be empty",{field:"branchName"});const{targetPath:t,logId:r}=l(a);await g(t);try{return await he(t,{branchName:e,force:s}),h(r,"INFO","Git branch deleted",{logId:r,branchName:e,force:s}),{success:!0,message:"Branch deleted successfully",logId:r,branchName:e}}catch(c){u(c,r,"delete branch",{branchName:e})}}export{b as init,k as status,F as commit,T as add,A as unstage,v as discard,C as logHistory,O as diff,E as fileContent,G as reset,B as revert,H as checkout,R as listTags,x as createTag,j as deleteTag,S as listBranches,L as createBranch,M as switchBranch,$ as deleteBranch};export default{init:b,status:k,commit:F,add:T,unstage:A,discard:v,logHistory:C,diff:O,fileContent:E,reset:G,revert:B,checkout:H,listTags:R,createTag:x,deleteTag:j,listBranches:S,createBranch:L,switchBranch:M,deleteBranch:$};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import c from"fs";import p from"path";import ne from"archiver";import{lookup as Ee}from"mime-types";import v from"../../appConfig/index.js";import{log as m}from"../log/logUtils.js";import{ValidationError as E,SystemError as L,FileError as Re}from"../error/errorHandler.js";import{extractZip as Fe}from"../common/zipUtils.js";import{moveDirectory as Se,movePath as ee}from"../common/fileSystemUtils.js";import{resolveLogDir as Ie,resolveWorkspaceDir as A,resolveWorkspaceRoot as se}from"./workspaceContext.js";const j=new Set([".git",".agents",".claude",".codex",".opencode",".tmp",".logs"]),De=100*1024*1024,V=v.DOWNLOAD_MAX_FILE_SIZE_BYTES||De;function G(e){return p.sep==="\\"?e.replace(/\\/g,"/"):e}function oe(e,r){return G(p.relative(e,r))}function ae(e,r){return e?`${e}/${r}`:r}async function xe(e,r,t,i,a){const n=[],l=await c.promises.readdir(e,{withFileTypes:!0});l.sort((s,h)=>s.isDirectory()&&!h.isDirectory()?-1:!s.isDirectory()&&h.isDirectory()?1:s.name.toLowerCase().localeCompare(h.name.toLowerCase()));const u=oe(r,e);for(const s of l){if(s.name.startsWith(".")&&s.name!==".gitignore"||(v.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(s.name)||s.isDirectory()&&v.TRAVERSE_EXCLUDE_DIRS.includes(s.name))continue;const f=ae(u,s.name);if(s.isDirectory()){n.push({name:f,isDir:!0});continue}try{n.push({name:f,isDir:!1,fileProxyUrl:H(i,f,a),isLink:s.isSymbolicLink()})}catch(o){m(t,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${e}/${s.name}`,{error:o.message})}}return n}async function le(e,r,t,i,a=""){const n=[],l=await c.promises.readdir(e,{withFileTypes:!0});l.sort((u,s)=>u.isDirectory()&&!s.isDirectory()?-1:!u.isDirectory()&&s.isDirectory()?1:u.name.toLowerCase().localeCompare(s.name.toLowerCase()));for(const u of l){const s=p.join(e,u.name);if(u.name.startsWith(".")&&u.name!==".gitignore"||(v.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(u.name)||u.isDirectory()&&v.TRAVERSE_EXCLUDE_DIRS.includes(u.name))continue;const f=ae(a,u.name);if(u.isDirectory()){const o=await le(s,r,t,i,f);o.length===0?n.push({name:f,isDir:!0}):n.push(...o)}else try{n.push({name:f,isDir:!1,fileProxyUrl:H(t,f,i),isLink:u.isSymbolicLink()})}catch(o){m(r,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${s}`,{error:o.message})}}return n}function ce(e,r){const t=(r==null?"":String(r)).trim();if(!t||t==="."||t==="/")return p.resolve(e);const i=p.normalize(t).replace(/^[\/\\]+/,"");if(!i||i===".")return p.resolve(e);if(p.isAbsolute(t)||i.split(p.sep).includes(".."))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:r});const a=p.resolve(e),n=p.resolve(e,i);if(n!==a&&!n.startsWith(a+p.sep))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:r});return n}function q(e,r){const t=(r==null?"":String(r)).trim();if(!t)return null;const i=p.resolve(e);let a;if(p.isAbsolute(t))a=p.resolve(t);else{const l=p.normalize(t).replace(/^[\/\\]+/,"");if(!l||l==="."||l.split(p.sep).includes(".."))return null;a=p.resolve(e,l)}if(a!==i&&!a.startsWith(i+p.sep))return null;const n=G(p.relative(i,a));return!n||n.startsWith("..")?null:{absPath:a,name:n}}function H(e,r,t){if(!e||!r)return null;const i=r.split("/").map(n=>encodeURIComponent(n)).join("/");let a=`${e}/${i}`;return t&&(a+=`?customTargetDir=${encodeURIComponent(t)}`),a}async function ke(e,r,t,i,a,n=null){if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!r)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});if(!t||!String(t).trim())throw new E("filePath \u4E0D\u80FD\u4E3A\u7A7A",{field:"filePath"});const l=String(e),u=String(r),s=a&&a.trim()?a.trim():null,h=s||A(n,l,u);if(!c.existsSync(h))return{exists:!1};let f=q(h,t);if(!f){const o=String(t).trim().replace(/^[\/\\]+/,"");o&&o!==String(t).trim()&&(f=q(h,o))}if(!f)return{exists:!1};try{if(!(await c.promises.stat(f.absPath)).isFile())return{exists:!1}}catch{return{exists:!1}}return{exists:!0,name:f.name,fileProxyUrl:H(i,f.name,s)}}const ve=100,be=1e3,Ne=8,ue=1e3;function Ce(e){return p.extname(e).replace(/^\./,"").toLowerCase()||""}async function Pe(e,r,t){const i=new Array(e.length),a=Math.max(1,Math.min(r,e.length));let n=0;return await Promise.all(Array.from({length:a},async()=>{for(;;){const l=n++;if(l>=e.length)return;i[l]=await t(e[l],l)}})),i}function _e(e){return!(e.startsWith(".")&&e!==".gitignore"||(v.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(e)||v.TRAVERSE_EXCLUDE_DIRS.includes(e))}async function ze(e){const r=await c.promises.opendir(e);let t=0;for await(const i of r)if(_e(i.name)&&(t+=1,t>=ue))return ue;return t}function me(e,r){return{path:e,isDir:null,isLink:null,size:null,mtimeMs:null,extension:null,mimeType:null,linkTarget:null,childCount:null,...r?{error:r}:{}}}async function Le(e,r,t){const i=(r==null?"":String(r)).trim();let a=i?q(e,i):null;if(!a&&i.startsWith("/")){const n=i.replace(/^[\/\\]+/,"");n&&n!==i&&(a=q(e,n))}if(!a)return me(i,"illegal path");try{const n=await c.promises.lstat(a.absPath),l=n.isDirectory(),u=n.isSymbolicLink(),s={path:i,isDir:l,isLink:u,size:l?null:n.size,mtimeMs:n.mtimeMs,extension:l?null:Ce(i),mimeType:l?null:Ee(i)||"application/octet-stream",linkTarget:null,childCount:null};if(u)try{s.linkTarget=await c.promises.readlink(a.absPath)}catch(h){m(t,"WARN","Readlink failed, linkTarget set to null",{filePath:i,error:h.message})}if(l)try{s.childCount=await ze(a.absPath)}catch(h){m(t,"WARN","Count directory entries failed, childCount set to null",{filePath:i,error:h.message})}return s}catch(n){return me(i,n.code||n.message)}}async function Ae(e,r,t,i,a=null,n=null){const l=Date.now(),u=`computer:${e}:${r}`;if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!r)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});if(!Array.isArray(t)||t.length===0)throw new E("filePaths \u5FC5\u987B\u4E3A\u975E\u7A7A\u6570\u7EC4",{field:"filePaths"});const s=Number.parseInt(n,10),h=Number.isFinite(s)&&s>0?Math.min(s,be):ve;if(t.length>h)throw new E(`filePaths \u5355\u6B21\u6700\u591A ${h} \u6761`,{field:"filePaths",count:t.length});const f=String(e),o=String(r),w=(i==null?"":String(i).trim())||null,d=w||A(a,f,o);if(!c.existsSync(d))return m(u,"INFO","Target directory does not exist, returning empty metas",{targetDir:d,userId:f,cId:o}),{metas:[]};const y=await Pe(t,Ne,R=>Le(d,R,u));return m(u,"INFO","File metadata queried",{targetDir:d,count:y.length,failed:y.filter(R=>R.error).length,customTargetDir:w,userId:f,cId:o,elapsedMs:Date.now()-l}),{metas:y}}async function fe(e,r,t,i,a=""){const n=await c.promises.readdir(p.join(e,a),{withFileTypes:!0});let l=0;for(const u of n){const s=a?p.join(a,u.name):u.name,h=s.split(p.sep).filter(Boolean);if(h.some(w=>w.startsWith(".")))continue;const f=h[h.length-1];if(r.includes(f)||h.some(w=>t.includes(w)))continue;const o=p.join(e,s);let g;try{g=await c.promises.lstat(o)}catch(w){m(i,"WARN","Error occurred when getting file stats, skipping",{filePath:s.replace(/\\/g,"/"),error:w.message});continue}g.isSymbolicLink()||g.nlink>1||(g.isDirectory()?l+=await fe(e,r,t,i,s):g.isFile()&&(l+=g.size))}return l}async function Te(e,r,t,i,a,n,l=null){const u=Date.now(),s=`computer:${e}:${r}`,h=!(n===!1||n==="false");if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!r)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const f=String(e),o=String(r),g=i&&i.trim()?i.trim():null,w=g||A(l,f,o);if(!c.existsSync(w))return m(s,"INFO","Directory does not exist, returning empty list",{targetDir:w,userId:f,cId:o}),{files:[],recursive:h};const d=ce(w,a);if(!c.existsSync(d))return m(s,"INFO","List path does not exist, returning empty list",{targetDir:w,listDir:d,relativePath:a||"",userId:f,cId:o}),{files:[],recursive:h};if(!(await c.promises.stat(d)).isDirectory())throw new E("relativePath \u5FC5\u987B\u662F\u76EE\u5F55",{field:"relativePath",relativePath:a});m(s,"DEBUG","Start getting user file list",{targetDir:w,listDir:d,relativePath:a||"",recursive:h,userId:f,cId:o});try{const R=h?await le(d,s,t,g,oe(w,d)):await xe(d,w,s,t,g);return m(s,"INFO","User file list obtained successfully",{fileCount:R.length,targetDir:w,listDir:d,relativePath:a||"",recursive:h,userId:f,cId:o,elapsedMs:Date.now()-u}),{files:R,recursive:h}}catch(R){throw R instanceof E?R:(m(s,"ERROR","Failed to get user file list",{targetDir:w,listDir:d,relativePath:a||"",recursive:h,userId:f,cId:o,error:R.message,elapsedMs:Date.now()-u}),new L(`Failed to get file list: ${R.message}`,{targetDir:w,originalError:R.message}))}}const Oe=5e3;function te(e,r){const t=Number.parseInt(e,10);if(!Number.isFinite(t)||t<=0)throw new E(`${r} \u5FC5\u987B\u4E3A\u6B63\u6574\u6570`,{field:r,value:e});return t}function de(e,r,t){return t?r.toLowerCase().includes(t)?!0:e.toLowerCase().includes(t):!1}function pe(e,r,t){return!!(e.name.startsWith(".")&&e.name!==".gitignore"||r.includes(e.name)||e.isDirectory()&&t.includes(e.name))}function he(e,r,t,i){for(let a=r;a<e.length;a++)if(!pe(e[a],t,i))return!0;return!1}function we(e,r){return Date.now()-e>=r}async function $e(e,r,t,i,a,n,l,u,s,h=null){const f=Date.now(),o=`computer:${e}:${r}`;if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!r)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const g=(i==null?"":String(i)).trim();if(!g)throw new E("kw \u4E0D\u80FD\u4E3A\u7A7A",{field:"kw"});const w=te(l,"limit"),d=te(u,"maxVisit"),y=te(s,"timeoutMs"),R=g.toLowerCase(),D=String(e),F=String(r),x=a&&a.trim()?a.trim():null,S=x||A(h,D,F);if(!c.existsSync(S))return{files:[],truncated:!1,visited:0};const I=ce(S,n);if(!c.existsSync(I))return{files:[],truncated:!1,visited:0};if(!(await c.promises.stat(I)).isDirectory())throw new E("relativePath \u5FC5\u987B\u662F\u76EE\u5F55",{field:"relativePath",relativePath:n});const T=G(p.relative(S,I)),Z=v.CONTENT_TRAVERSE_EXCLUDE_FILES||[],K=v.TRAVERSE_EXCLUDE_DIRS||[],b=[],Y=new Set;let O=0,J=!1,_=!1;const z=[T],W=()=>O>=d||we(f,y);for(;z.length>0;){if(W()){_=!0;break}if(b.length>=w){z.length>0&&(_=!0);break}const C=z.shift();if(Y.has(C))continue;Y.add(C);const U=C?p.join(S,C):S;let $;try{$=await c.promises.readdir(U,{withFileTypes:!0})}catch(k){m(o,"WARN","search readdir failed",{dirRel:C,error:k.message});continue}$.sort((k,N)=>k.isDirectory()&&!N.isDirectory()?-1:!k.isDirectory()&&N.isDirectory()?1:k.name.toLowerCase().localeCompare(N.name.toLowerCase()));const Q=[];let B=!1,X=!1;for(let k=0;k<$.length;k++){const N=$[k];if(pe(N,Z,K))continue;if(W()){_=!0,B=!0,X=!0;break}O+=1;const ie=p.join(U,N.name),M=G(p.relative(S,ie));if(N.isDirectory()){if(Q.push(M),b.length<w&&de(M,N.name,R)&&(b.push({name:M,isDir:!0}),b.length>=w)){B=!0,X=he($,k+1,Z,K);break}continue}try{if(b.length<w&&de(M,N.name,R)&&(b.push({name:M,isDir:!1,fileProxyUrl:H(t,M,x),isLink:N.isSymbolicLink()}),b.length>=w)){B=!0,X=he($,k+1,Z,K);break}}catch(ye){m(o,"WARN",`search \u5904\u7406\u6587\u4EF6\u5931\u8D25: ${ie}`,{error:ye.message})}}if(B||W()){(W()||X||Q.length>0||z.length>0)&&(_=!0);continue}for(const k of Q){if(W()){_=!0;break}if(z.length>=Oe){_=!0;break}if(O+z.length>=d){_=!0;break}Y.has(k)||z.push(k)}}return b.sort((C,U)=>C.isDir!==U.isDir?C.isDir?-1:1:String(C.name).localeCompare(String(U.name))),J=_||z.length>0||O>=d||we(f,y),m(o,"INFO","File search completed",{kw:g,matchCount:b.length,visited:O,truncated:J,relativePath:n||"",elapsedMs:Date.now()-f}),{files:b,truncated:J,visited:O}}async function Me(e,r,t,i,a=null){const n=Date.now(),l=`computer:${e}:${r}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!Array.isArray(t))throw new E("files must be an array",{field:"files"});const u=String(e),s=String(r),h=i&&i.trim()?i.trim():null,f=h||A(a,u,s);c.existsSync(f)||c.mkdirSync(f,{recursive:!0});for(let o=0;o<t.length;o++){const g=t[o];if(!g||typeof g.operation!="string")throw new E(`files[${o}].operation cannot be empty`,{field:`files[${o}].operation`});if(!g.name||typeof g.name!="string")throw new E(`files[${o}].name cannot be empty`,{field:`files[${o}].name`});const w=g.operation.toLowerCase();if(!["create","delete","rename","modify"].includes(w))throw new E(`files[${o}].operation must be one of create, delete, rename or modify`,{field:`files[${o}].operation`});if(w==="rename"&&!g.renameFrom)throw new E(`files[${o}].renameFrom cannot be empty (rename operation requires)`,{field:`files[${o}].renameFrom`});if(w==="modify"&&g.isDir!==!0&&typeof g.contents!="string")throw new E(`files[${o}].contents must be a string (modify operation requires)`,{field:`files[${o}].contents`})}m(l,"DEBUG","Start updating user files",{userId:u,cId:s,filesCount:t.length});try{for(const o of t){const g=o.operation.toLowerCase(),w=o.name,d=p.normalize(w).replace(/^[\/\\]+/,""),y=p.join(f,d),R=p.resolve(y),D=p.resolve(f);if(!R.startsWith(D+p.sep)&&R!==D){m(l,"WARN","File path is not secure, skipping",{filePath:d,resolvedPath:R});continue}switch(g){case"create":{if(o.isDir===!0){if(c.existsSync(y)){if((await c.promises.stat(y)).isFile())throw new E("Cannot create directory, file with the same name already exists",{filePath:d});m(l,"INFO","Directory already exists, skipping creation",{filePath:d});break}await c.promises.mkdir(y,{recursive:!0}),m(l,"INFO","Directory created successfully",{filePath:d});break}if(c.existsSync(y)){if((await c.promises.stat(y)).isDirectory())throw new E("Cannot create file, directory with the same name already exists",{filePath:d});m(l,"INFO","File already exists, skipping creation",{filePath:d});break}await c.promises.mkdir(p.dirname(y),{recursive:!0});const F=o.contents||"";await c.promises.writeFile(y,F,"utf8"),m(l,"INFO","File created successfully",{filePath:d});break}case"delete":{c.existsSync(y)?(await c.promises.stat(y)).isDirectory()?(await c.promises.rm(y,{recursive:!0,force:!0}),m(l,"INFO","Directory deleted successfully",{filePath:d})):(await c.promises.unlink(y),m(l,"INFO","File deleted successfully",{filePath:d})):m(l,"WARN","The file or directory to be deleted does not exist",{filePath:d});break}case"rename":{const F=o.renameFrom;if(!F||typeof F!="string"){m(l,"WARN","Rename operation missing renameFrom",{filePath:d});break}const x=p.normalize(F).replace(/^[\/\\]+/,""),S=p.join(f,x),I=p.resolve(S);if(!I.startsWith(D+p.sep)&&I!==D){m(l,"WARN","Source path is not secure, skipping rename",{sourcePath:x,targetPath:d});break}if(c.existsSync(S)){const T=(await c.promises.stat(S)).isDirectory();await c.promises.mkdir(p.dirname(y),{recursive:!0}),await c.promises.rename(S,y),m(l,"INFO",T?"Directory renamed successfully":"File renamed successfully",{sourcePath:x,targetPath:d})}else m(l,"WARN","The file or directory to be renamed does not exist",{sourcePath:x});break}case"modify":{if(!c.existsSync(y)){m(l,"WARN","The file to be modified does not exist",{filePath:d});break}if((await c.promises.stat(y)).isDirectory()){m(l,"INFO","The target is a directory, skipping modification",{filePath:d});break}const x=typeof o.contents=="string"?o.contents:"";if(await c.promises.readFile(y,"utf8")===x){m(l,"INFO","File content has no changes, skipping write",{filePath:d});break}await c.promises.writeFile(y,x,"utf8"),m(l,"INFO","File modified successfully",{filePath:d});break}default:{m(l,"WARN","Unsupported operation type",{operation:g,filePath:d});break}}}return m(l,"INFO","User files updated successfully",{userId:u,cId:s,filesCount:t.length,elapsedMs:Date.now()-n}),{success:!0,message:"User files updated successfully",userId:u,cId:s,filesCount:t.length}}catch(o){throw m(l,"ERROR","User files updated failed",{userId:u,cId:s,error:o.message,elapsedMs:Date.now()-n}),new L(`User files updated failed: ${o.message}`,{userId:u,cId:s,originalError:o.message})}}async function re(e,r,t,i,a,n=null){const l=Date.now(),u=`computer:${e}:${r}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!t)throw new E("file cannot be empty",{field:"file"});if(!i||typeof i!="string")throw new E("filePath cannot be empty",{field:"filePath"});const s=String(e),h=String(r),f=a&&a.trim()?a.trim():null,o=f||A(n,s,h);c.existsSync(o)||c.mkdirSync(o,{recursive:!0});const g=p.normalize(i).replace(/^[\/\\]+/,""),w=p.join(o,g),d=p.resolve(w),y=p.resolve(o);if(!d.startsWith(y+p.sep)&&d!==y)throw new E("File path is not secure, cannot exceed user directory",{field:"filePath",providedPath:i,resolvedPath:d});try{if(await c.promises.mkdir(p.dirname(w),{recursive:!0}),t.buffer)await c.promises.writeFile(w,t.buffer);else if(typeof t.contents=="string")await c.promises.writeFile(w,t.contents,"utf8");else throw new E("File content format is incorrect",{field:"file",hasBuffer:!!t.buffer,hasContents:typeof t.contents});return m(u,"INFO","File uploaded successfully",{userId:s,cId:h,filePath:g,targetPath:d,fileSize:t.buffer?t.buffer.length:t.contents?t.contents.length:0,elapsedMs:Date.now()-l}),{success:!0,message:"File uploaded successfully",fileSize:t.buffer?t.buffer.length:t.contents?t.contents.length:0}}catch(R){throw m(u,"ERROR","File upload failed",{userId:s,cId:h,filePath:g,error:R.message,elapsedMs:Date.now()-l}),new L(`File upload failed: ${R.message}`,{userId:s,cId:h,filePath:g,originalError:R.message})}}async function We(e,r,t,i,a,n=null){const l=Date.now(),u=`computer:${e}:${r}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!Array.isArray(t))throw new E("files must be an array",{field:"files"});if(!Array.isArray(i))throw new E("filePaths must be an array",{field:"filePaths"});if(t.length!==i.length)throw new E(`File count (${t.length}) does not match path count (${i.length})`,{field:"filePaths"});m(u,"DEBUG","Start batch uploading files",{userId:e,cId:r,filesCount:t.length});const s=[];try{for(let o=0;o<t.length;o++){const g=t[o],w=i[o];if(!g){m(u,"WARN","Empty file object encountered in batch upload, skipping",{index:o,filePath:w}),s.push({success:!1,filePath:w,error:"Empty file object"});continue}if(!w||typeof w!="string"){m(u,"WARN","Invalid file path in batch upload, skipping",{index:o,originalname:g.originalname}),s.push({success:!1,filePath:w||"",originalname:g.originalname,error:"Invalid file path"});continue}try{const d=await re(e,r,g,w,a,n);s.push({success:!0,filePath:w,originalname:g.originalname,...d})}catch(d){m(u,"ERROR","Single file upload failed in batch upload",{filePath:w,originalname:g.originalname,error:d.message}),s.push({success:!1,filePath:w,originalname:g.originalname,error:d.message})}}const h=s.filter(o=>o.success).length,f=s.filter(o=>!o.success).length;return m(u,"INFO","Batch upload files completed",{userId:e,cId:r,totalCount:t.length,successCount:h,failCount:f,elapsedMs:Date.now()-l}),{success:!0,message:"Batch upload completed",totalCount:t.length,successCount:h,failCount:f,results:s}}catch(h){throw m(u,"ERROR","Batch upload files failed",{userId:e,cId:r,error:h.message,elapsedMs:Date.now()-l}),new L(`Batch upload files failed: ${h.message}`,{userId:e,cId:r,originalError:h.message})}}async function Ue(e,r,t,i=null){const a=Date.now(),n=`computer:${e}:${r}`,l=se(i);if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!l&&!i?.workspacePath)throw new L("COMPUTER_WORKSPACE_DIR is not configured, cannot create zip");const u=String(e),s=String(r),h=t&&t.trim()?t.trim():null,f=h||A(i,u,s);if(!c.existsSync(f)){const R=`${u}_${s}.zip`;m(n,"WARN","Workspace directory does not exist, returning empty zip",{targetDir:f,userId:u,cId:s,zipFileName:R});const D=ne("zip",{zlib:{level:9}});return D.append(null,{name:`${u}_${s}/`,type:"directory"}),D.on("warning",F=>{if(F.code==="ENOENT")m(n,"WARN","Encountered file problem when creating empty zip",{message:F.message,code:F.code});else throw m(n,"ERROR","Encountered warning when creating empty zip",{message:F.message,code:F.code}),F}),D.on("error",F=>{m(n,"ERROR","Failed to create empty zip",{message:F.message})}),{archive:D,zipFileName:R}}const o=`${u}_${s}.zip`;m(n,"DEBUG","Start creating workspace directory zip",{targetDir:f,zipFileName:o});const g=ne("zip",{zlib:{level:9}}),w=v.CONTENT_TRAVERSE_EXCLUDE_FILES||[],d=v.TRAVERSE_EXCLUDE_DIRS||[],y=await fe(f,w,d,n);if(y>V){const R=V/1024/1024,D=(y/1024/1024).toFixed(2);throw m(n,"WARN","Download rejected due to oversized workspace",{targetDir:f,downloadableSize:y,maxSizeBytes:V}),new E(`Download failed: total file size ${D}MB exceeds limit ${R}MB`,{field:"downloadSize",downloadableSize:y,maxSizeBytes:V})}return g.directory(f,`${u}_${s}`,R=>{const D=R.name||"",F=D.split(/[\/\\]/).filter(Boolean);if(F.some(S=>S.startsWith(".")))return!1;const x=F[F.length-1];if(w.includes(x)||F.some(S=>d.includes(S)))return!1;try{const S=p.join(f,D),I=c.lstatSync(S);if(I.isSymbolicLink()||I.nlink>1)return!1}catch(S){return m(n,"WARN","Error occurred when detecting link file, skipping",{filePath:D,error:S.message}),!1}return R}),g.on("warning",R=>{if(R.code==="ENOENT")m(n,"WARN","Encountered file problem when creating zip",{message:R.message,code:R.code});else throw m(n,"ERROR","Encountered warning when creating zip",{message:R.message,code:R.code}),R}),g.on("error",R=>{m(n,"ERROR","Failed to create zip",{message:R.message,elapsedMs:Date.now()-a})}),g.on("end",()=>{m(n,"INFO","Workspace directory zip created successfully",{targetDir:f,zipFileName:o,elapsedMs:Date.now()-a})}),{archive:g,zipFileName:o}}async function je(e,r,t=200,i=null){const a=Date.now(),n=`computer:${e}:${r}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});const l=String(e),u=String(r),s=Ie(i,l,u);if(!s)return m(n,"WARN","COMPUTER_WORKSPACE_DIR is not configured"),{success:!0,message:"Log workspace is not configured",logs:[],totalLines:0,startIndex:1,logFileName:null};if(!c.existsSync(s))return m(n,"DEBUG","Log directory does not exist",{logDir:s}),{success:!0,message:"Log directory does not exist",logs:[],totalLines:0,startIndex:1,logFileName:null};const f=(await c.promises.readdir(s,{withFileTypes:!0})).filter(I=>I.isFile()).map(I=>I.name);if(f.length===0)return m(n,"DEBUG","No log file found",{logDir:s}),{success:!0,message:"No log file found",logs:[],totalLines:0,startIndex:1,logFileName:null};const o=[];for(const I of f){const P=p.join(s,I),T=await c.promises.stat(P);o.push({name:I,fullPath:P,mtime:T.mtimeMs,size:T.size})}o.sort((I,P)=>P.mtime-I.mtime);const g=o[0],d=(await c.promises.readFile(g.fullPath,"utf8")).split(`
|
|
2
|
-
`).filter(I=>I.length>0),
|
|
1
|
+
import l from"fs";import w from"path";import oe from"archiver";import{lookup as Fe}from"mime-types";import v from"../../appConfig/index.js";import{log as u}from"../log/logUtils.js";import{ValidationError as E,SystemError as z,FileError as Ie}from"../error/errorHandler.js";import{extractZip as Se}from"../common/zipUtils.js";import{moveDirectory as De,movePath as te}from"../common/fileSystemUtils.js";import{resolveLogDir as ke,resolveWorkspaceDir as A,resolveWorkspaceRoot as ae}from"./workspaceContext.js";const j=new Set([".git",".agents",".claude",".codex",".opencode",".tmp",".logs"]),xe=100*1024*1024,V=v.DOWNLOAD_MAX_FILE_SIZE_BYTES||xe;function G(e){return w.sep==="\\"?e.replace(/\\/g,"/"):e}function le(e,i){return G(w.relative(e,i))}function ce(e,i){return e?`${e}/${i}`:i}function re(e){return e!=null&&e.limit!=null&&e.pushed>=e.limit}function q(e){e!=null&&(e.pushed+=1)}async function be(e,i,t,n,a,s={}){const{typeFilter:o=null,limitState:m=null}=s,c=[],y=await l.promises.readdir(e,{withFileTypes:!0});y.sort((r,f)=>r.isDirectory()&&!f.isDirectory()?-1:!r.isDirectory()&&f.isDirectory()?1:r.name.toLowerCase().localeCompare(f.name.toLowerCase()));const h=le(i,e);for(const r of y){if(re(m))break;if(r.name.startsWith(".")&&r.name!==".gitignore"||(v.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(r.name)||r.isDirectory()&&v.TRAVERSE_EXCLUDE_DIRS.includes(r.name))continue;const g=ce(h,r.name);if(r.isDirectory()){if(o==="file")continue;c.push({name:g,isDir:!0}),q(m);continue}if(o!=="dir")try{c.push({name:g,isDir:!1,fileProxyUrl:Z(n,g,a),isLink:r.isSymbolicLink()}),q(m)}catch(d){u(t,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${e}/${r.name}`,{error:d.message})}}return c}async function ue(e,i,t,n,a="",s={}){const{typeFilter:o=null,limitState:m=null}=s,c=[];let y=0;const h=await l.promises.readdir(e,{withFileTypes:!0});h.sort((r,f)=>r.isDirectory()&&!f.isDirectory()?-1:!r.isDirectory()&&f.isDirectory()?1:r.name.toLowerCase().localeCompare(f.name.toLowerCase()));for(const r of h){if(re(m))break;const f=w.join(e,r.name);if(r.name.startsWith(".")&&r.name!==".gitignore"||(v.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(r.name)||r.isDirectory()&&v.TRAVERSE_EXCLUDE_DIRS.includes(r.name))continue;const d=ce(a,r.name);if(y+=1,r.isDirectory()){const p=await ue(f,i,t,n,d,s);p.naturalCount===0?o!=="file"&&!re(m)&&(c.push({name:d,isDir:!0}),q(m)):c.push(...p.entries)}else{if(o==="dir")continue;try{c.push({name:d,isDir:!1,fileProxyUrl:Z(t,d,n),isLink:r.isSymbolicLink()}),q(m)}catch(p){u(i,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${f}`,{error:p.message})}}}return{entries:c,naturalCount:y}}function me(e,i){const t=(i==null?"":String(i)).trim();if(!t||t==="."||t==="/")return w.resolve(e);const n=w.normalize(t).replace(/^[\/\\]+/,"");if(!n||n===".")return w.resolve(e);if(w.isAbsolute(t)||n.split(w.sep).includes(".."))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:i});const a=w.resolve(e),s=w.resolve(e,n);if(s!==a&&!s.startsWith(a+w.sep))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:i});return s}function H(e,i){const t=(i==null?"":String(i)).trim();if(!t)return null;const n=w.resolve(e);let a;if(w.isAbsolute(t))a=w.resolve(t);else{const o=w.normalize(t).replace(/^[\/\\]+/,"");if(!o||o==="."||o.split(w.sep).includes(".."))return null;a=w.resolve(e,o)}if(a!==n&&!a.startsWith(n+w.sep))return null;const s=G(w.relative(n,a));return!s||s.startsWith("..")?null:{absPath:a,name:s}}function Z(e,i,t){if(!e||!i)return null;const n=i.split("/").map(s=>encodeURIComponent(s)).join("/");let a=`${e}/${n}`;return t&&(a+=`?customTargetDir=${encodeURIComponent(t)}`),a}async function ve(e,i,t,n,a,s=null){if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!i)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});if(!t||!String(t).trim())throw new E("filePath \u4E0D\u80FD\u4E3A\u7A7A",{field:"filePath"});const o=String(e),m=String(i),c=a&&a.trim()?a.trim():null,y=c||A(s,o,m);if(!l.existsSync(y))return{exists:!1};let h=H(y,t);if(!h){const r=String(t).trim().replace(/^[\/\\]+/,"");r&&r!==String(t).trim()&&(h=H(y,r))}if(!h)return{exists:!1};try{if(!(await l.promises.stat(h.absPath)).isFile())return{exists:!1}}catch{return{exists:!1}}return{exists:!0,name:h.name,fileProxyUrl:Z(n,h.name,c)}}const Ne=100,Ce=1e3,Pe=8,fe=1e3;function Le(e){return w.extname(e).replace(/^\./,"").toLowerCase()||""}async function _e(e,i,t){const n=new Array(e.length),a=Math.max(1,Math.min(i,e.length));let s=0;return await Promise.all(Array.from({length:a},async()=>{for(;;){const o=s++;if(o>=e.length)return;n[o]=await t(e[o],o)}})),n}function ze(e){return!(e.startsWith(".")&&e!==".gitignore"||(v.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(e)||v.TRAVERSE_EXCLUDE_DIRS.includes(e))}async function Ae(e){const i=await l.promises.opendir(e);let t=0;for await(const n of i)if(ze(n.name)&&(t+=1,t>=fe))return fe;return t}function de(e,i){return{path:e,isDir:null,isLink:null,size:null,mtimeMs:null,extension:null,mimeType:null,linkTarget:null,childCount:null,...i?{error:i}:{}}}async function Te(e,i,t){const n=(i==null?"":String(i)).trim();let a=n?H(e,n):null;if(!a&&n.startsWith("/")){const s=n.replace(/^[\/\\]+/,"");s&&s!==n&&(a=H(e,s))}if(!a)return de(n,"illegal path");try{const s=await l.promises.lstat(a.absPath),o=s.isDirectory(),m=s.isSymbolicLink(),c={path:n,isDir:o,isLink:m,size:o?null:s.size,mtimeMs:s.mtimeMs,extension:o?null:Le(n),mimeType:o?null:Fe(n)||"application/octet-stream",linkTarget:null,childCount:null};if(m)try{c.linkTarget=await l.promises.readlink(a.absPath)}catch(y){u(t,"WARN","Readlink failed, linkTarget set to null",{filePath:n,error:y.message})}if(o)try{c.childCount=await Ae(a.absPath)}catch(y){u(t,"WARN","Count directory entries failed, childCount set to null",{filePath:n,error:y.message})}return c}catch(s){return de(n,s.code||s.message)}}async function Oe(e,i,t,n,a=null,s=null){const o=Date.now(),m=`computer:${e}:${i}`;if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!i)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});if(!Array.isArray(t)||t.length===0)throw new E("filePaths \u5FC5\u987B\u4E3A\u975E\u7A7A\u6570\u7EC4",{field:"filePaths"});const c=Number.parseInt(s,10),y=Number.isFinite(c)&&c>0?Math.min(c,Ce):Ne;if(t.length>y)throw new E(`filePaths \u5355\u6B21\u6700\u591A ${y} \u6761`,{field:"filePaths",count:t.length});const h=String(e),r=String(i),g=(n==null?"":String(n).trim())||null,d=g||A(a,h,r);if(!l.existsSync(d))return u(m,"INFO","Target directory does not exist, returning empty metas",{targetDir:d,userId:h,cId:r}),{metas:[]};const p=await _e(t,Pe,R=>Te(d,R,m));return u(m,"INFO","File metadata queried",{targetDir:d,count:p.length,failed:p.filter(R=>R.error).length,customTargetDir:g,userId:h,cId:r,elapsedMs:Date.now()-o}),{metas:p}}async function pe(e,i,t,n,a=""){const s=await l.promises.readdir(w.join(e,a),{withFileTypes:!0});let o=0;for(const m of s){const c=a?w.join(a,m.name):m.name,y=c.split(w.sep).filter(Boolean);if(y.some(g=>g.startsWith(".")))continue;const h=y[y.length-1];if(i.includes(h)||y.some(g=>t.includes(g)))continue;const r=w.join(e,c);let f;try{f=await l.promises.lstat(r)}catch(g){u(n,"WARN","Error occurred when getting file stats, skipping",{filePath:c.replace(/\\/g,"/"),error:g.message});continue}f.isSymbolicLink()||f.nlink>1||(f.isDirectory()?o+=await pe(e,i,t,n,c):f.isFile()&&(o+=f.size))}return o}async function $e(e,i,t,n,a,s,o,m,c=null){const y=Date.now(),h=`computer:${e}:${i}`,r=!(s===!1||s==="false");if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!i)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const f=o==null?"":String(o).trim().toLowerCase();let g=null;if(f&&f!=="all")if(f==="file")g="file";else if(f==="dir"||f==="directory")g="dir";else throw new E("type \u4EC5\u652F\u6301 file/dir/all",{field:"type",value:o});let d=null;if(m!=null&&m!==""){const I=Number(m);if(!Number.isInteger(I)||I<0)throw new E("limit \u5FC5\u987B\u4E3A\u975E\u8D1F\u6574\u6570",{field:"limit",value:m});d=I}const p=String(e),R=String(i),D=n&&n.trim()?n.trim():null,F=D||A(c,p,R);if(!l.existsSync(F))return u(h,"INFO","Directory does not exist, returning empty list",{targetDir:F,userId:p,cId:R}),{files:[],recursive:r,type:g||"all",limit:d};const S=me(F,a);if(!l.existsSync(S))return u(h,"INFO","List path does not exist, returning empty list",{targetDir:F,listDir:S,relativePath:a||"",userId:p,cId:R}),{files:[],recursive:r,type:g||"all",limit:d};if(!(await l.promises.stat(S)).isDirectory())throw new E("relativePath \u5FC5\u987B\u662F\u76EE\u5F55",{field:"relativePath",relativePath:a});u(h,"DEBUG","Start getting user file list",{targetDir:F,listDir:S,relativePath:a||"",recursive:r,userId:p,cId:R});try{const I={typeFilter:g,limitState:{limit:d,pushed:0}},b=r?(await ue(S,h,t,D,le(F,S),I)).entries:await be(S,F,h,t,D,I);return u(h,"INFO","User file list obtained successfully",{fileCount:b.length,targetDir:F,listDir:S,relativePath:a||"",recursive:r,type:g||"all",limit:d,userId:p,cId:R,elapsedMs:Date.now()-y}),{files:b,recursive:r,type:g||"all",limit:d}}catch(I){throw I instanceof E?I:(u(h,"ERROR","Failed to get user file list",{targetDir:F,listDir:S,relativePath:a||"",recursive:r,userId:p,cId:R,error:I.message,elapsedMs:Date.now()-y}),new z(`Failed to get file list: ${I.message}`,{targetDir:F,originalError:I.message}))}}const Me=5e3;function ie(e,i){const t=Number.parseInt(e,10);if(!Number.isFinite(t)||t<=0)throw new E(`${i} \u5FC5\u987B\u4E3A\u6B63\u6574\u6570`,{field:i,value:e});return t}function he(e,i,t){return t?i.toLowerCase().includes(t)?!0:e.toLowerCase().includes(t):!1}function we(e,i,t){return!!(e.name.startsWith(".")&&e.name!==".gitignore"||i.includes(e.name)||e.isDirectory()&&t.includes(e.name))}function ge(e,i,t,n){for(let a=i;a<e.length;a++)if(!we(e[a],t,n))return!0;return!1}function ye(e,i){return Date.now()-e>=i}async function We(e,i,t,n,a,s,o,m,c,y=null){const h=Date.now(),r=`computer:${e}:${i}`;if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!i)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const f=(n==null?"":String(n)).trim();if(!f)throw new E("kw \u4E0D\u80FD\u4E3A\u7A7A",{field:"kw"});const g=ie(o,"limit"),d=ie(m,"maxVisit"),p=ie(c,"timeoutMs"),R=f.toLowerCase(),D=String(e),F=String(i),S=a&&a.trim()?a.trim():null,k=S||A(y,D,F);if(!l.existsSync(k))return{files:[],truncated:!1,visited:0};const I=me(k,s);if(!l.existsSync(I))return{files:[],truncated:!1,visited:0};if(!(await l.promises.stat(I)).isDirectory())throw new E("relativePath \u5FC5\u987B\u662F\u76EE\u5F55",{field:"relativePath",relativePath:s});const T=G(w.relative(k,I)),K=v.CONTENT_TRAVERSE_EXCLUDE_FILES||[],Y=v.TRAVERSE_EXCLUDE_DIRS||[],N=[],J=new Set;let O=0,Q=!1,L=!1;const _=[T],W=()=>O>=d||ye(h,p);for(;_.length>0;){if(W()){L=!0;break}if(N.length>=g){_.length>0&&(L=!0);break}const P=_.shift();if(J.has(P))continue;J.add(P);const U=P?w.join(k,P):k;let $;try{$=await l.promises.readdir(U,{withFileTypes:!0})}catch(x){u(r,"WARN","search readdir failed",{dirRel:P,error:x.message});continue}$.sort((x,C)=>x.isDirectory()&&!C.isDirectory()?-1:!x.isDirectory()&&C.isDirectory()?1:x.name.toLowerCase().localeCompare(C.name.toLowerCase()));const ee=[];let B=!1,X=!1;for(let x=0;x<$.length;x++){const C=$[x];if(we(C,K,Y))continue;if(W()){L=!0,B=!0,X=!0;break}O+=1;const se=w.join(U,C.name),M=G(w.relative(k,se));if(C.isDirectory()){if(ee.push(M),N.length<g&&he(M,C.name,R)&&(N.push({name:M,isDir:!0}),N.length>=g)){B=!0,X=ge($,x+1,K,Y);break}continue}try{if(N.length<g&&he(M,C.name,R)&&(N.push({name:M,isDir:!1,fileProxyUrl:Z(t,M,S),isLink:C.isSymbolicLink()}),N.length>=g)){B=!0,X=ge($,x+1,K,Y);break}}catch(Re){u(r,"WARN",`search \u5904\u7406\u6587\u4EF6\u5931\u8D25: ${se}`,{error:Re.message})}}if(B||W()){(W()||X||ee.length>0||_.length>0)&&(L=!0);continue}for(const x of ee){if(W()){L=!0;break}if(_.length>=Me){L=!0;break}if(O+_.length>=d){L=!0;break}J.has(x)||_.push(x)}}return N.sort((P,U)=>P.isDir!==U.isDir?P.isDir?-1:1:String(P.name).localeCompare(String(U.name))),Q=L||_.length>0||O>=d||ye(h,p),u(r,"INFO","File search completed",{kw:f,matchCount:N.length,visited:O,truncated:Q,relativePath:s||"",elapsedMs:Date.now()-h}),{files:N,truncated:Q,visited:O}}async function Ue(e,i,t,n,a=null){const s=Date.now(),o=`computer:${e}:${i}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!i)throw new E("cId cannot be empty",{field:"cId"});if(!Array.isArray(t))throw new E("files must be an array",{field:"files"});const m=String(e),c=String(i),y=n&&n.trim()?n.trim():null,h=y||A(a,m,c);l.existsSync(h)||l.mkdirSync(h,{recursive:!0});for(let r=0;r<t.length;r++){const f=t[r];if(!f||typeof f.operation!="string")throw new E(`files[${r}].operation cannot be empty`,{field:`files[${r}].operation`});if(!f.name||typeof f.name!="string")throw new E(`files[${r}].name cannot be empty`,{field:`files[${r}].name`});const g=f.operation.toLowerCase();if(!["create","delete","rename","modify"].includes(g))throw new E(`files[${r}].operation must be one of create, delete, rename or modify`,{field:`files[${r}].operation`});if(g==="rename"&&!f.renameFrom)throw new E(`files[${r}].renameFrom cannot be empty (rename operation requires)`,{field:`files[${r}].renameFrom`});if(g==="modify"&&f.isDir!==!0&&typeof f.contents!="string")throw new E(`files[${r}].contents must be a string (modify operation requires)`,{field:`files[${r}].contents`})}u(o,"DEBUG","Start updating user files",{userId:m,cId:c,filesCount:t.length});try{for(const r of t){const f=r.operation.toLowerCase(),g=r.name,d=w.normalize(g).replace(/^[\/\\]+/,""),p=w.join(h,d),R=w.resolve(p),D=w.resolve(h);if(!R.startsWith(D+w.sep)&&R!==D){u(o,"WARN","File path is not secure, skipping",{filePath:d,resolvedPath:R});continue}switch(f){case"create":{if(r.isDir===!0){if(l.existsSync(p)){if((await l.promises.stat(p)).isFile())throw new E("Cannot create directory, file with the same name already exists",{filePath:d});u(o,"INFO","Directory already exists, skipping creation",{filePath:d});break}await l.promises.mkdir(p,{recursive:!0}),u(o,"INFO","Directory created successfully",{filePath:d});break}if(l.existsSync(p)){if((await l.promises.stat(p)).isDirectory())throw new E("Cannot create file, directory with the same name already exists",{filePath:d});u(o,"INFO","File already exists, skipping creation",{filePath:d});break}await l.promises.mkdir(w.dirname(p),{recursive:!0});const F=r.contents||"";await l.promises.writeFile(p,F,"utf8"),u(o,"INFO","File created successfully",{filePath:d});break}case"delete":{l.existsSync(p)?(await l.promises.stat(p)).isDirectory()?(await l.promises.rm(p,{recursive:!0,force:!0}),u(o,"INFO","Directory deleted successfully",{filePath:d})):(await l.promises.unlink(p),u(o,"INFO","File deleted successfully",{filePath:d})):u(o,"WARN","The file or directory to be deleted does not exist",{filePath:d});break}case"rename":{const F=r.renameFrom;if(!F||typeof F!="string"){u(o,"WARN","Rename operation missing renameFrom",{filePath:d});break}const S=w.normalize(F).replace(/^[\/\\]+/,""),k=w.join(h,S),I=w.resolve(k);if(!I.startsWith(D+w.sep)&&I!==D){u(o,"WARN","Source path is not secure, skipping rename",{sourcePath:S,targetPath:d});break}if(l.existsSync(k)){const T=(await l.promises.stat(k)).isDirectory();await l.promises.mkdir(w.dirname(p),{recursive:!0}),await l.promises.rename(k,p),u(o,"INFO",T?"Directory renamed successfully":"File renamed successfully",{sourcePath:S,targetPath:d})}else u(o,"WARN","The file or directory to be renamed does not exist",{sourcePath:S});break}case"modify":{if(!l.existsSync(p)){u(o,"WARN","The file to be modified does not exist",{filePath:d});break}if((await l.promises.stat(p)).isDirectory()){u(o,"INFO","The target is a directory, skipping modification",{filePath:d});break}const S=typeof r.contents=="string"?r.contents:"";if(await l.promises.readFile(p,"utf8")===S){u(o,"INFO","File content has no changes, skipping write",{filePath:d});break}await l.promises.writeFile(p,S,"utf8"),u(o,"INFO","File modified successfully",{filePath:d});break}default:{u(o,"WARN","Unsupported operation type",{operation:f,filePath:d});break}}}return u(o,"INFO","User files updated successfully",{userId:m,cId:c,filesCount:t.length,elapsedMs:Date.now()-s}),{success:!0,message:"User files updated successfully",userId:m,cId:c,filesCount:t.length}}catch(r){throw u(o,"ERROR","User files updated failed",{userId:m,cId:c,error:r.message,elapsedMs:Date.now()-s}),new z(`User files updated failed: ${r.message}`,{userId:m,cId:c,originalError:r.message})}}async function ne(e,i,t,n,a,s=null){const o=Date.now(),m=`computer:${e}:${i}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!i)throw new E("cId cannot be empty",{field:"cId"});if(!t)throw new E("file cannot be empty",{field:"file"});if(!n||typeof n!="string")throw new E("filePath cannot be empty",{field:"filePath"});const c=String(e),y=String(i),h=a&&a.trim()?a.trim():null,r=h||A(s,c,y);l.existsSync(r)||l.mkdirSync(r,{recursive:!0});const f=w.normalize(n).replace(/^[\/\\]+/,""),g=w.join(r,f),d=w.resolve(g),p=w.resolve(r);if(!d.startsWith(p+w.sep)&&d!==p)throw new E("File path is not secure, cannot exceed user directory",{field:"filePath",providedPath:n,resolvedPath:d});try{if(await l.promises.mkdir(w.dirname(g),{recursive:!0}),t.buffer)await l.promises.writeFile(g,t.buffer);else if(typeof t.contents=="string")await l.promises.writeFile(g,t.contents,"utf8");else throw new E("File content format is incorrect",{field:"file",hasBuffer:!!t.buffer,hasContents:typeof t.contents});return u(m,"INFO","File uploaded successfully",{userId:c,cId:y,filePath:f,targetPath:d,fileSize:t.buffer?t.buffer.length:t.contents?t.contents.length:0,elapsedMs:Date.now()-o}),{success:!0,message:"File uploaded successfully",fileSize:t.buffer?t.buffer.length:t.contents?t.contents.length:0}}catch(R){throw u(m,"ERROR","File upload failed",{userId:c,cId:y,filePath:f,error:R.message,elapsedMs:Date.now()-o}),new z(`File upload failed: ${R.message}`,{userId:c,cId:y,filePath:f,originalError:R.message})}}async function je(e,i,t,n,a,s=null){const o=Date.now(),m=`computer:${e}:${i}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!i)throw new E("cId cannot be empty",{field:"cId"});if(!Array.isArray(t))throw new E("files must be an array",{field:"files"});if(!Array.isArray(n))throw new E("filePaths must be an array",{field:"filePaths"});if(t.length!==n.length)throw new E(`File count (${t.length}) does not match path count (${n.length})`,{field:"filePaths"});u(m,"DEBUG","Start batch uploading files",{userId:e,cId:i,filesCount:t.length});const c=[];try{for(let r=0;r<t.length;r++){const f=t[r],g=n[r];if(!f){u(m,"WARN","Empty file object encountered in batch upload, skipping",{index:r,filePath:g}),c.push({success:!1,filePath:g,error:"Empty file object"});continue}if(!g||typeof g!="string"){u(m,"WARN","Invalid file path in batch upload, skipping",{index:r,originalname:f.originalname}),c.push({success:!1,filePath:g||"",originalname:f.originalname,error:"Invalid file path"});continue}try{const d=await ne(e,i,f,g,a,s);c.push({success:!0,filePath:g,originalname:f.originalname,...d})}catch(d){u(m,"ERROR","Single file upload failed in batch upload",{filePath:g,originalname:f.originalname,error:d.message}),c.push({success:!1,filePath:g,originalname:f.originalname,error:d.message})}}const y=c.filter(r=>r.success).length,h=c.filter(r=>!r.success).length;return u(m,"INFO","Batch upload files completed",{userId:e,cId:i,totalCount:t.length,successCount:y,failCount:h,elapsedMs:Date.now()-o}),{success:!0,message:"Batch upload completed",totalCount:t.length,successCount:y,failCount:h,results:c}}catch(y){throw u(m,"ERROR","Batch upload files failed",{userId:e,cId:i,error:y.message,elapsedMs:Date.now()-o}),new z(`Batch upload files failed: ${y.message}`,{userId:e,cId:i,originalError:y.message})}}async function Be(e,i,t,n=null){const a=Date.now(),s=`computer:${e}:${i}`,o=ae(n);if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!i)throw new E("cId cannot be empty",{field:"cId"});if(!o&&!n?.workspacePath)throw new z("COMPUTER_WORKSPACE_DIR is not configured, cannot create zip");const m=String(e),c=String(i),y=t&&t.trim()?t.trim():null,h=y||A(n,m,c);if(!l.existsSync(h)){const R=`${m}_${c}.zip`;u(s,"WARN","Workspace directory does not exist, returning empty zip",{targetDir:h,userId:m,cId:c,zipFileName:R});const D=oe("zip",{zlib:{level:9}});return D.append(null,{name:`${m}_${c}/`,type:"directory"}),D.on("warning",F=>{if(F.code==="ENOENT")u(s,"WARN","Encountered file problem when creating empty zip",{message:F.message,code:F.code});else throw u(s,"ERROR","Encountered warning when creating empty zip",{message:F.message,code:F.code}),F}),D.on("error",F=>{u(s,"ERROR","Failed to create empty zip",{message:F.message})}),{archive:D,zipFileName:R}}const r=`${m}_${c}.zip`;u(s,"DEBUG","Start creating workspace directory zip",{targetDir:h,zipFileName:r});const f=oe("zip",{zlib:{level:9}}),g=v.CONTENT_TRAVERSE_EXCLUDE_FILES||[],d=v.TRAVERSE_EXCLUDE_DIRS||[],p=await pe(h,g,d,s);if(p>V){const R=V/1024/1024,D=(p/1024/1024).toFixed(2);throw u(s,"WARN","Download rejected due to oversized workspace",{targetDir:h,downloadableSize:p,maxSizeBytes:V}),new E(`Download failed: total file size ${D}MB exceeds limit ${R}MB`,{field:"downloadSize",downloadableSize:p,maxSizeBytes:V})}return f.directory(h,`${m}_${c}`,R=>{const D=R.name||"",F=D.split(/[\/\\]/).filter(Boolean);if(F.some(k=>k.startsWith(".")))return!1;const S=F[F.length-1];if(g.includes(S)||F.some(k=>d.includes(k)))return!1;try{const k=w.join(h,D),I=l.lstatSync(k);if(I.isSymbolicLink()||I.nlink>1)return!1}catch(k){return u(s,"WARN","Error occurred when detecting link file, skipping",{filePath:D,error:k.message}),!1}return R}),f.on("warning",R=>{if(R.code==="ENOENT")u(s,"WARN","Encountered file problem when creating zip",{message:R.message,code:R.code});else throw u(s,"ERROR","Encountered warning when creating zip",{message:R.message,code:R.code}),R}),f.on("error",R=>{u(s,"ERROR","Failed to create zip",{message:R.message,elapsedMs:Date.now()-a})}),f.on("end",()=>{u(s,"INFO","Workspace directory zip created successfully",{targetDir:h,zipFileName:r,elapsedMs:Date.now()-a})}),{archive:f,zipFileName:r}}async function Xe(e,i,t=200,n=null){const a=Date.now(),s=`computer:${e}:${i}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!i)throw new E("cId cannot be empty",{field:"cId"});const o=String(e),m=String(i),c=ke(n,o,m);if(!c)return u(s,"WARN","COMPUTER_WORKSPACE_DIR is not configured"),{success:!0,message:"Log workspace is not configured",logs:[],totalLines:0,startIndex:1,logFileName:null};if(!l.existsSync(c))return u(s,"DEBUG","Log directory does not exist",{logDir:c}),{success:!0,message:"Log directory does not exist",logs:[],totalLines:0,startIndex:1,logFileName:null};const h=(await l.promises.readdir(c,{withFileTypes:!0})).filter(I=>I.isFile()).map(I=>I.name);if(h.length===0)return u(s,"DEBUG","No log file found",{logDir:c}),{success:!0,message:"No log file found",logs:[],totalLines:0,startIndex:1,logFileName:null};const r=[];for(const I of h){const b=w.join(c,I),T=await l.promises.stat(b);r.push({name:I,fullPath:b,mtime:T.mtimeMs,size:T.size})}r.sort((I,b)=>b.mtime-I.mtime);const f=r[0],d=(await l.promises.readFile(f.fullPath,"utf8")).split(`
|
|
2
|
+
`).filter(I=>I.length>0),p=d.length,R=Math.max(1,t),D=Math.max(0,p-R),F=D+1,k=d.slice(D).map((I,b)=>({line:F+b,content:I}));return u(s,"DEBUG","Get latest logs",{fileName:f.name,fileSize:f.size,totalLines:p,returnedLines:k.length,elapsedMs:Date.now()-a}),{success:!0,message:"Get log successfully",logs:k,totalLines:p,startIndex:F,logFileName:f.name}}async function Ee(e,i){try{const n=(await l.promises.readdir(e,{withFileTypes:!0})).filter(m=>!m.name.startsWith(".")&&m.name!=="node_modules");if(n.length!==1||!n[0].isDirectory())return;const a=w.join(e,n[0].name),s=w.join(e,`..tmp_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{await l.promises.rename(a,s)}catch(m){if(m.code==="EXDEV")await l.promises.cp(a,s,{recursive:!0}),await l.promises.rm(a,{recursive:!0,force:!0});else throw m}const o=await l.promises.readdir(s);for(const m of o){const c=w.join(s,m),y=w.join(e,m);try{await l.promises.rename(c,y)}catch(h){if(h.code==="EXDEV")(await l.promises.lstat(c)).isDirectory()?await De(c,y):(await l.promises.copyFile(c,y),await l.promises.unlink(c));else throw h}}await l.promises.rm(s,{recursive:!0,force:!0}),u(i,"INFO","Removed single top-level directory from imported zip",{removedDir:n[0].name})}catch(t){u(i,"WARN","Failed to remove top-level directory from imported zip, continuing",{error:t.message})}}async function Ve(e,i){if(!l.existsSync(e)){await l.promises.mkdir(e,{recursive:!0});return}const t=await l.promises.readdir(e,{withFileTypes:!0});for(const n of t){if(j.has(n.name))continue;const a=w.join(e,n.name);await l.promises.rm(a,{recursive:!0,force:!0}),u(i,"DEBUG","Removed workspace entry during import",{name:n.name})}}async function Ge(e,i,t){if(await l.promises.mkdir(i,{recursive:!0}),!l.existsSync(e)){await l.promises.mkdir(e,{recursive:!0});return}const n=await l.promises.readdir(e,{withFileTypes:!0});for(const a of n){if(j.has(a.name))continue;const s=w.join(e,a.name),o=w.join(i,a.name);await te(s,o),u(t,"DEBUG","Backed up workspace entry before import",{name:a.name})}}async function qe(e,i,t){if(!l.existsSync(e))return;const n=await l.promises.readdir(e,{withFileTypes:!0});for(const a of n){if(j.has(a.name))continue;const s=w.join(e,a.name),o=w.join(i,a.name);await te(s,o)}u(t,"INFO","Workspace restored from backup after import failure")}async function He(e,i,t){const n=await l.promises.readdir(e,{withFileTypes:!0}),a=[];try{for(const s of n){if(j.has(s.name)){u(t,"INFO","Skipping preserved entry from imported zip",{name:s.name});continue}const o=w.join(e,s.name),m=w.join(i,s.name);await te(o,m),a.push(s.name)}}catch(s){throw s.mergedEntries=a,s}}async function Ze(e,i,t,n,a=null){const s=Date.now(),o=`computer:${e}:${i}`,m=ae(a);if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!i)throw new E("cId cannot be empty",{field:"cId"});if(!t||!t.path)throw new E("file is required",{field:"file"});if(!m&&!a?.workspacePath)throw new z("COMPUTER_WORKSPACE_DIR is not configured");if(w.extname(t.originalname||t.filename||"").toLowerCase()!==".zip")throw new E("Only zip files are supported",{field:"file",originalName:t.originalname});const y=String(e),h=String(i),r=n&&n.trim()?n.trim():A(a,y,h),f=w.join(r,".tmp"),g=w.join(f,`import_project_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);let d=null;u(o,"INFO","Start importing project from zip",{userId:y,cId:h,targetDir:r,fileName:t.originalname});try{await l.promises.mkdir(r,{recursive:!0}),await l.promises.mkdir(f,{recursive:!0}),await l.promises.mkdir(g,{recursive:!0}),await Se(t.path,g),await Ee(g,o),d=w.join(f,`import_backup_${Date.now()}_${Math.round(Math.random()*1e6)}`),await Ge(r,d,o);try{await He(g,r,o)}catch(p){throw u(o,"ERROR","Merge imported project failed, restoring workspace",{error:p.message,mergedEntries:p.mergedEntries||[]}),await Ve(r,o),await qe(d,r,o),p}return l.existsSync(d)&&(await l.promises.rm(d,{recursive:!0,force:!0}),d=null),u(o,"INFO","Project imported successfully",{userId:y,cId:h,targetDir:r,elapsedMs:Date.now()-s}),{success:!0,message:"Project imported successfully",userId:y,cId:h,targetDir:r}}catch(p){throw u(o,"ERROR","Failed to import project",{userId:y,cId:h,targetDir:r,error:p.message,elapsedMs:Date.now()-s}),p instanceof E||p instanceof z||p instanceof Ie?p:new z(`Failed to import project: ${p.message}`,{userId:y,cId:h,originalError:p.message})}finally{if(l.existsSync(g))try{await l.promises.rm(g,{recursive:!0,force:!0})}catch(p){u(o,"WARN","Failed to clean import extract directory",{extractRoot:g,error:p.message})}if(d&&l.existsSync(d))try{await l.promises.rm(d,{recursive:!0,force:!0})}catch(p){u(o,"WARN","Failed to clean import backup directory",{backupDir:d,error:p.message})}if(t?.path&&l.existsSync(t.path))try{await l.promises.unlink(t.path)}catch(p){u(o,"WARN","Failed to clean uploaded zip file",{tempZipPath:t.path,error:p.message})}}}async function Ke(e,i,t,n,a,s=null){const o=`computer:${e}:${i}`;if(!t||typeof t!="string"||!t.trim())throw new E("fileName cannot be empty",{field:"fileName"});if(n!=null&&typeof n!="string")throw new E("content must be a string",{field:"content"});const m=t.trim(),c=n??"";return u(o,"INFO","Generate file from text content",{userId:e,cId:i,fileName:m,contentLength:c.length}),{...await ne(e,i,{contents:c,originalname:w.basename(m),size:Buffer.byteLength(c,"utf8")},m,a,s),message:"File generated successfully",fileName:m}}export{$e as getFileList,Oe as getFileMeta,ve as resolveExistingFile,We as searchFiles,Ue as updateFiles,ne as uploadFile,je as uploadFiles,Ke as generateFile,Be as downloadAllFiles,Xe as getLatestLogs,Ze as importProject,j as IMPORT_PROJECT_PRESERVED_ENTRIES,Ee as removeTopLevelDir};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import m from"fs";import w from"os";import
|
|
1
|
+
import m from"fs";import w from"os";import u from"path";import{ValidationError as o}from"../error/errorHandler.js";function a(t){const r=t.startsWith("\\\\")||t.startsWith("//");let e=String(t).replace(/\\/g,"/").replace(/\/{2,}/g,"/");return r&&(e="/"+e),e}export async function listFsRoots(){const t=a(w.homedir()),r=[];if(process.platform==="win32")for(let e=65;e<=90;e++){const n=String.fromCharCode(e)+":/";try{await m.promises.stat(n),r.push({name:n,path:n,isDir:!0})}catch{}}else r.push({name:"/",path:"/",isDir:!0});return{roots:r,home:t}}function h(t,r="path"){if(!t||typeof t!="string"||t.includes("\0"))throw new o(`${r} is required`,{field:r});if(!u.isAbsolute(t))throw new o(`${r} must be absolute`,{field:r})}function p(t,r){const e=String(t??"");if(!e.trim()||e.includes("\0"))throw new o(`${r} is required`,{field:r});if(e.includes("/")||e.includes("\\"))throw new o(`${r} must not contain path separators`,{field:r});if(e==="."||e==="..")throw new o(`${r} must not be a relative segment`,{field:r});if(e.length>255)throw new o(`${r} exceeds 255 characters`,{field:r});return e}export async function listFsChildren(t){h(t);let r;try{r=await m.promises.readdir(t,{withFileTypes:!0})}catch(n){throw new o(`cannot read directory (${n.code||n.message})`,{field:"path"})}const e=[];for(const n of r){let i=n.isDirectory();const c=n.isSymbolicLink();if(c)try{i=(await m.promises.stat(u.join(t,n.name))).isDirectory()}catch{}e.push({name:n.name,path:a(u.join(t,n.name)),isDir:i,isSymlink:c})}return e.sort((n,i)=>Number(i.isDir)-Number(n.isDir)||n.name.localeCompare(i.name,void 0,{sensitivity:"base",numeric:!0})),{path:a(t),entries:e}}export async function createFsDirectory(t,r){h(t,"parentPath");const e=p(r,"dirName"),n=u.join(t,e);try{await m.promises.mkdir(n)}catch(i){throw i.code==="EEXIST"?new o(`directory already exists: ${e}`,{field:"dirName"}):i.code==="ENOENT"?new o("parent directory does not exist",{field:"parentPath"}):new o(`cannot create directory (${i.code||i.message})`,{field:"dirName"})}return{path:a(n),name:e,parentPath:a(t),isDir:!0,isSymlink:!1}}export async function renameFsDirectory(t,r){h(t);const e=p(r,"newName"),n=u.dirname(t);if(n===t)throw new o("cannot rename the root directory",{field:"path"});const i=u.join(n,e);let c;try{await m.promises.stat(i),c=!0}catch(s){if(s.code!=="ENOENT")throw new o(`cannot rename (${s.code||s.message})`,{field:"path"});c=!1}if(c)throw new o(`name already exists: ${e}`,{field:"newName"});try{await m.promises.rename(t,i)}catch(s){throw s.code==="ENOENT"?new o("directory does not exist",{field:"path"}):new o(`cannot rename directory (${s.code||s.message})`,{field:"newName"})}return{path:a(i),name:e,parentPath:a(n),isDir:!0,isSymlink:!1}}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
`+
|
|
1
|
+
import g from"path";import u from"fs";import c from"../../appConfig/index.js";import{log as I}from"../log/logUtils.js";import{shouldUseNativeGit as T,isNativeGitUnavailableError as A,markNativeGitMissing as w,nativeHasHead as G,nativeInit as U,nativeStatus as L,nativeAddAll as N,nativeStageFiles as R,nativeAddAllAndCommit as S,nativeCommitFiles as D,nativeUnstage as C,nativeDiscard as B,nativeLog as H,nativeDiff as O,nativeFileContent as k,nativeReset as M,nativeRevertToTree as b,nativeCheckoutFiles as x,nativeListTags as W,nativeCreateTag as p,nativeDeleteTag as $,nativeListBranches as j,nativeCreateBranch as q,nativeSwitchBranch as z,nativeDeleteBranch as J}from"./nativeGitUtils.js";import{isoHasHead as K,isoInit as Q,isoStatus as V,isoAddAll as y,isoStageFiles as X,isoCommitAll as Y,isoCommitFiles as Z,isoUnstage as P,isoDiscard as d,isoLog as tt,isoDiff as et,isoFileContent as at,isoReset as nt,isoRevertToTree as it,isoCheckoutFiles as st,isoListTags as ct,isoCreateTag as rt,isoDeleteTag as ot,isoListBranches as ut,isoCreateBranch as lt,isoSwitchBranch as gt,isoDeleteBranch as ht,listWorkdirFilesSkippingIgnored as ft}from"./isoGitOps.js";function m(t){return u.existsSync(g.join(t,".git"))}function v(){return{name:c.GIT_DEFAULT_AUTHOR_NAME,email:c.GIT_DEFAULT_AUTHOR_EMAIL}}function _(t,e,a){w();const n=e&&typeof e=="object"&&"message"in e?String(e.message):String(e);I(g.basename(t)||"git","WARN",`Native git unavailable during ${a}, fallback to isomorphic-git`,{error:n})}async function i(t,e,a,n){if(await T())try{return await a()}catch(s){if(!A(s))throw s;_(t,s,e)}return n()}async function mt(t){return i(t,"hasGitHead",()=>G(t),()=>K(t))}function l(t){if(!c.GIT_AUTO_GITIGNORE)return;const e=g.join(t,".gitignore"),a=c.GIT_GITIGNORE_ENTRIES||[];let n=[];u.existsSync(e)&&(n=u.readFileSync(e,"utf8").split(/\r?\n/));const s=new Set(n.map(o=>o.trim()).filter(Boolean)),r=a.filter(o=>!s.has(o));if(r.length>0){const o=n.length>0&&n[n.length-1]!==""?`
|
|
2
|
+
`+r.join(`
|
|
3
3
|
`)+`
|
|
4
|
-
`:
|
|
4
|
+
`:r.join(`
|
|
5
5
|
`)+`
|
|
6
|
-
`;
|
|
6
|
+
`;u.appendFileSync(e,o,"utf8")}}async function E(t,e={}){const a=e.authorName||c.GIT_DEFAULT_AUTHOR_NAME,n=e.authorEmail||c.GIT_DEFAULT_AUTHOR_EMAIL;await i(t,"init",()=>U(t,{authorName:a,authorEmail:n}),()=>Q(t,{authorName:a,authorEmail:n})),l(t)}async function Tt(t){if(m(t))l(t);else{await E(t);const e=g.join(t,".gitignore"),a=g.join(t,".gitkeep");!u.existsSync(e)&&!u.existsSync(a)&&u.writeFileSync(a,"")}}async function At(t){return i(t,"status",()=>L(t),()=>V(t))}async function F(t,e={}){return i(t,"addAll",()=>N(t),()=>y(t,e))}async function h(t,e,a={}){return i(t,"stageFiles",()=>R(t,e,a),()=>X(t,e,a))}async function yt(t,e={}){const{message:a,authorName:n=c.GIT_DEFAULT_AUTHOR_NAME,authorEmail:s=c.GIT_DEFAULT_AUTHOR_EMAIL,cache:r}=e;if(await T())try{return{...await S(t,{message:a,authorName:n,authorEmail:s}),viaNative:!0}}catch(f){if(!A(f))throw f;_(t,f,"commitAllChanges")}return{...await Y(t,{message:a,authorName:n,authorEmail:s,cache:r}),viaNative:!1}}async function vt(t,e){const{message:a,files:n,authorName:s=c.GIT_DEFAULT_AUTHOR_NAME,authorEmail:r=c.GIT_DEFAULT_AUTHOR_EMAIL,cache:o={}}=e;return i(t,"commitFiles",()=>D(t,{message:a,files:n,authorName:s,authorEmail:r,force:!0}),()=>Z(t,{message:a,files:n,authorName:s,authorEmail:r,cache:o}))}async function _t(t,e=null){return i(t,"unstage",async()=>(await C(t,e),{files:Array.isArray(e)&&e.length>0?e:"all"}),()=>P(t,e))}async function Et(t,e=null){return i(t,"discard",()=>B(t,e),()=>d(t,e))}async function Ft(t,e={}){return i(t,"log",()=>H(t,e),()=>tt(t,e))}async function It(t,e={}){return i(t,"diff",()=>O(t,e),()=>et(t,e))}async function wt(t,e){return i(t,"fileContent",()=>k(t,e),()=>at(t,e))}async function Gt(t,e){const a=await i(t,"reset",()=>M(t,e),()=>nt(t,e));if(e.mode==="hard"){l(t);try{await h(t,[".gitignore"],{force:!0})}catch{}}return a}async function Ut(t,e){const a=e.authorName||c.GIT_DEFAULT_AUTHOR_NAME,n=e.authorEmail||c.GIT_DEFAULT_AUTHOR_EMAIL,s=e.message||`Revert to ${String(e.target).substring(0,7)}`,r=async()=>{l(t);try{await h(t,[".gitignore"],{force:!0})}catch{}};return i(t,"revert",()=>b(t,{target:e.target,message:s,authorName:a,authorEmail:n,beforeCommit:r}),()=>it(t,{target:e.target,message:s,authorName:a,authorEmail:n,beforeCommit:r}))}async function Lt(t,e){await i(t,"checkoutFiles",()=>x(t,e),()=>st(t,e)),l(t);try{await h(t,[".gitignore"],{force:!0})}catch{}}async function Nt(t){return i(t,"listTags",()=>W(t),()=>ct(t))}async function Rt(t,e){const a=v();return i(t,"createTag",()=>p(t,{...e,authorName:a.name,authorEmail:a.email}),()=>rt(t,{...e,authorName:a.name,authorEmail:a.email}))}async function St(t,e){return i(t,"deleteTag",()=>$(t,e),()=>ot(t,e))}async function Dt(t){return i(t,"listBranches",()=>j(t),()=>ut(t))}async function Ct(t,e){return i(t,"createBranch",()=>q(t,e),()=>lt(t,e))}async function Bt(t,e){return i(t,"switchBranch",()=>z(t,e),()=>gt(t,e))}async function Ht(t,e){return i(t,"deleteBranch",()=>J(t,e),()=>ht(t,e))}async function Ot(t,e){if(m(t))try{Array.isArray(e)&&e.length>0?await h(t,e):await F(t)}catch{}}export{m as isGitRepo,Tt as ensureGitRepo,l as ensureGitignore,Ot as autoGitAdd,F as addAll,y as addAllWithIsomorphic,yt as commitAllChanges,vt as commitFiles,h as stageFiles,v as getDefaultAuthor,mt as hasGitHead,ft as listWorkdirFilesSkippingIgnored,i as withNativeFallback,E as initRepo,At as getStatus,_t as unstageFiles,Et as discardChanges,Ft as getLog,It as getDiff,wt as getFileContentAtRef,Gt as resetTo,Ut as revertToTree,Lt as checkoutFiles,Nt as listTags,Rt as createTag,St as deleteTag,Dt as listBranches,Ct as createBranch,Bt as switchBranch,Ht as deleteBranch};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import C from"path";import e from"fs";import L from"crypto";import i from"isomorphic-git";import{createPatch as O}from"diff";import P from"../../appConfig/index.js";import{cleanEmptyParentDirs as N}from"./nativeGitUtils.js";function T(t,s){return{name:t||P.GIT_DEFAULT_AUTHOR_NAME,email:s||P.GIT_DEFAULT_AUTHOR_EMAIL}}function M(t){for(let s=0;s<Math.min(t.length,8e3);s++)if(t[s]===0)return!0;return!1}function W(t){const s=Buffer.from(`blob ${t.length}\0`);return L.createHash("sha1").update(Buffer.concat([s,t])).digest("hex")}async function D(t){return i.resolveRef({fs:e,dir:t,ref:"HEAD"})}async function I(t){try{return await i.resolveRef({fs:e,dir:t,ref:"HEAD"}),!0}catch{return!1}}async function B(t,s,o){try{const{blob:a}=await i.readBlob({fs:e,dir:t,oid:s,filepath:o}),n=Buffer.from(a);return{content:n.toString("utf8"),buf:n}}catch{return null}}function G(t,s,o,a,n,r,f){const w=O(t,a?s:"",n?o:"",a?`a/${t}`:"/dev/null",n?`b/${t}`:"/dev/null").split(`
|
|
2
|
+
`).slice(4);w.length>0&&w[w.length-1]===""&&w.pop();const u=[];let d=0,g=0;for(const m of w){if(m.startsWith("@@")){const x=m.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);if(x){const H=x[1],E=x[2]!==void 0?parseInt(x[2],10):1,F=x[3],v=x[4]!==void 0?parseInt(x[4],10):1,A=E===1?H:`${H},${E}`,$=v===1?F:`${F},${v}`;u.push(`@@ -${A} +${$} @@`);continue}}m.startsWith("+")&&!m.startsWith("+++")?d++:m.startsWith("-")&&!m.startsWith("---")&&g++,u.push(m)}const p=a&&s!==""&&!s.endsWith(`
|
|
3
|
+
`),S=n&&o!==""&&!o.endsWith(`
|
|
4
|
+
`);if((p||S)&&u.length>0){let m=0;for(let v=0;v<u.length;v++)u[v].startsWith("@@")&&(m=v);let x=-1,H=-1,E=-1;for(let v=u.length-1;v>m;v--){const A=u[v];A.startsWith("-")&&!A.startsWith("---")&&x<0?x=v:A.startsWith("+")&&!A.startsWith("++")&&H<0?H=v:A.startsWith(" ")&&E<0&&(E=v)}const F=Math.max(x,H,E);if(F>=0){const v=u[F];if(v.startsWith(" "))(p||S)&&u.splice(F+1,0,"\");else if(v.startsWith("+")){let A=-1;for(let $=F-1;$>m;$--)if(u[$].startsWith("-")&&!u[$].startsWith("---")){A=$;break}A>=0&&p?(u.splice(A+1,0,"\"),S&&u.splice(F+2,0,"\")):S&&u.splice(F+1,0,"\")}else v.startsWith("-")&&p&&u.splice(F+1,0,"\")}}const b=a&&r?W(r).substring(0,7):"0000000",k=n&&f?W(f).substring(0,7):"0000000",c=[];return c.push(`diff --git a/${t} b/${t}`),a?n?c.push(`index ${b}..${k} 100644`):(c.push("deleted file mode 100644"),c.push(`index ${b}..0000000`)):(c.push("new file mode 100644"),c.push(`index 0000000..${k}`)),c.push(`--- ${a?`a/${t}`:"/dev/null"}`),c.push(`+++ ${n?`b/${t}`:"/dev/null"}`),{diff:[...c,...u].join(`
|
|
5
|
+
`)+`
|
|
6
|
+
`,insertions:d,deletions:g}}async function V(t,s){const{authorName:o,authorEmail:a,defaultBranch:n="main"}=s;await i.init({fs:e,dir:t,defaultBranch:n}),await i.setConfig({fs:e,dir:t,path:"user.name",value:o}),await i.setConfig({fs:e,dir:t,path:"user.email",value:a})}async function q(t){const s=await i.currentBranch({fs:e,dir:t,fullname:!1}),o=await i.statusMatrix({fs:e,dir:t}),a=[],n=[],r=[],f=[],l=[];for(const[h,w,u,d]of o)w!==d&&a.push(h),w===0&&d!==0&&r.push(h),(w===1&&d===0||u===0&&d!==0)&&f.push(h),d!==0&&u!==0&&u!==d&&n.push(h),w===0&&d===0&&u!==0&&l.push(h);return{current:s,staged:[...new Set(a)],modified:[...new Set(n)],created:[...new Set(r)],deleted:[...new Set(f)],untracked:[...new Set(l)],conflicted:[],ahead:0,behind:0,tracking:null}}async function _(t,s={}){const{cache:o}=s,a=[];return await i.walk({fs:e,dir:t,cache:o,trees:[i.WORKDIR()],map:async(n,[r])=>{if(!r)return null;if(n===".")return;const f=await r.type();if(f==="tree")return await i.isIgnored({fs:e,dir:t,filepath:n})?null:void 0;if(f!=="blob"||await i.isIgnored({fs:e,dir:t,filepath:n}))return null;a.push(n)}}),a}async function j(t,s={}){const{cache:o}=s,a=await _(t,{cache:o});return a.length>0&&await i.add({fs:e,dir:t,filepath:a,parallel:!0,cache:o,force:!0}),{hasChanges:a.length>0,toAdd:a,toRemove:[]}}async function R(t,s={}){const{cache:o}=s;if(!await I(t))return j(t,{cache:o});const a=await i.statusMatrix({fs:e,dir:t,cache:o}),n=[],r=[];for(const[l,h,w,u]of a)w===2&&u!==2&&n.push(l),h===1&&w===0&&u!==0&&r.push(l);n.length>0&&await i.add({fs:e,dir:t,filepath:n,parallel:!0,cache:o,force:!0});for(const l of r)await i.remove({fs:e,dir:t,filepath:l,cache:o});return{hasChanges:n.length>0||r.length>0||a.some(([,,,l])=>l!==1),toAdd:n,toRemove:r}}async function U(t,s,o={}){const{cache:a,force:n=!1}=o,r=[],f=[];for(const l of s){const h=C.join(t,l);e.existsSync(h)?r.push(l):f.push(l)}r.length>0&&await i.add({fs:e,dir:t,filepath:r,parallel:!0,cache:a,force:n});for(const l of f)await i.remove({fs:e,dir:t,filepath:l,cache:a})}async function K(t,s){const{message:o,authorName:a,authorEmail:n,cache:r}=s,{hasChanges:f}=await R(t,{cache:r});return f?{nothingToCommit:!1,commitHash:await i.commit({fs:e,dir:t,message:o,author:T(a,n),cache:r})}:{nothingToCommit:!0}}async function z(t,s){const{message:o,authorName:a,authorEmail:n,files:r,cache:f={}}=s;return await U(t,r,{cache:f,force:!0}),(await i.statusMatrix({fs:e,dir:t,cache:f,filepaths:r})).some(([,,,u])=>u!==1)?{nothingToCommit:!1,commitHash:await i.commit({fs:e,dir:t,message:o,author:T(a,n),cache:f})}:{nothingToCommit:!0}}async function J(t,s){if(Array.isArray(s)&&s.length>0){for(const n of s)await i.resetIndex({fs:e,dir:t,filepath:n});return{files:s}}const a=(await i.statusMatrix({fs:e,dir:t})).filter(([,,,n])=>n!==1).map(([n])=>n);for(const n of a)await i.resetIndex({fs:e,dir:t,filepath:n});return{files:"all"}}async function Q(t,s){const o=await i.statusMatrix({fs:e,dir:t}),a=Array.isArray(s)&&s.length>0?new Set(s):null,n=[],r=[],f=[];for(const[l,h,w,u]of o)a&&!a.has(l)||(h!==0?(u!==1||w!==1)&&n.push(l):h===0&&u!==0?r.push(l):h===0&&u===0&&w!==0&&f.push(l));if(n.length>0&&await I(t)){const l=await D(t);for(const h of n){const w=await B(t,l,h);if(!w)continue;const u=C.join(t,h);e.mkdirSync(C.dirname(u),{recursive:!0}),e.writeFileSync(u,w.buf),await i.add({fs:e,dir:t,filepath:h})}}for(const l of r){try{await i.remove({fs:e,dir:t,filepath:l})}catch{}const h=C.join(t,l);e.existsSync(h)&&await e.promises.unlink(h)}r.length>0&&await N(t,r);for(const l of f){const h=C.join(t,l);e.existsSync(h)&&await e.promises.unlink(h)}return f.length>0&&await N(t,f),{discardedCount:n.length+r.length+f.length,trackedFiles:n,newFiles:r,untrackedFiles:f}}async function X(t,s={}){const{maxCount:o=50,skip:a=0,branch:n,filePath:r}=s;if(!await I(t))return[];const f={fs:e,dir:t,depth:o+a};n&&(f.ref=n),r&&(f.filepath=r);try{return(await i.log(f)).slice(a,a+o).map(w=>({hash:w.oid,date:new Date(w.commit.author.timestamp*1e3).toISOString(),message:w.commit.message,author_name:w.commit.author.name,author_email:w.commit.author.email}))}catch(l){if(l?.code==="NotFoundError"||/NotFoundError|Could not find|unable to resolve|does not have any commits/i.test(String(l?.message||"")))return[];throw l}}async function Y(t,s={}){const{source:o="worktree",from:a,to:n,paths:r}=s,f=Array.isArray(r)&&r.length>0?new Set(r):null;let l="";const h=[];let w=0,u=0;async function d(g,p,S){const b=!!p,k=!!S;if(b&&k&&p.content===S.content)return;let c=!1;if(b&&M(p.buf)&&(c=!0),k&&!c&&M(S.buf)&&(c=!0),c){const H=b?W(p.buf).substring(0,7):"0000000",E=k?W(S.buf).substring(0,7):"0000000",F=[`diff --git a/${g} b/${g}`];b?k?F.push(`index ${H}..${E} 100644`):(F.push("deleted file mode 100644"),F.push(`index ${H}..0000000`)):(F.push("new file mode 100644"),F.push(`index 0000000..${E}`)),F.push(`Binary files ${b?`a/${g}`:"/dev/null"} and ${k?`b/${g}`:"/dev/null"} differ`),l+=F.join(`
|
|
7
|
+
`)+`
|
|
8
|
+
`,h.push({file:g,changes:0,insertions:0,deletions:0,binary:!0});return}const{diff:y,insertions:m,deletions:x}=G(g,b?p.content:"",k?S.content:"",b,k,b?p.buf:null,k?S.buf:null);l+=y,h.push({file:g,changes:m+x,insertions:m,deletions:x,binary:!1}),w+=m,u+=x}if(o==="commit"){let g,p;if(a&&n)g=a,p=n;else if(a){const c=await i.readCommit({fs:e,dir:t,oid:a});c.commit.parent&&c.commit.parent.length>0?(g=c.commit.parent[0],p=a):(g=null,p=a)}else{const c=new Error("source=commit requires at least 'from'");throw c.code="VALIDATION",c}const S=g?new Set(await i.listFiles({fs:e,dir:t,ref:g})):new Set,b=new Set(await i.listFiles({fs:e,dir:t,ref:p})),k=new Set([...S,...b]);for(const c of k){if(f&&!f.has(c))continue;const y=S.has(c)?await B(t,g,c):null,m=b.has(c)?await B(t,p,c):null;await d(c,y,m)}}else{if(!await I(t))return{diff:"",summary:{files:[],insertions:0,deletions:0}};const g=await D(t),p=await i.statusMatrix({fs:e,dir:t});for(const[S,b,k,c]of p){if(f&&!f.has(S))continue;if(o==="staged"){if(c===1||b===0&&c===0)continue}else if(b===0&&c===0||b===1&&k===1&&c===1)continue;const y=b!==0?await B(t,g,S):null;let m=null;if(k!==0){const x=C.join(t,S);if(e.existsSync(x))try{const H=e.readFileSync(x);m={content:H.toString("utf8"),buf:H}}catch{}}await d(S,y,m)}}return{diff:l,summary:{files:h,insertions:w,deletions:u}}}async function Z(t,s){const{ref:o,filePath:a}=s;if(o==="worktree"||o==="staged"||o===""){const n=C.join(t,a);return e.existsSync(n)?e.readFileSync(n,"utf8"):""}try{const n=await i.resolveRef({fs:e,dir:t,ref:o}),{blob:r}=await i.readBlob({fs:e,dir:t,oid:n,filepath:a});return Buffer.from(r).toString("utf8")}catch{return""}}async function tt(t,s){const{target:o,mode:a}=s;if(!await I(t)){const h=new Error("Cannot reset: repository has no commits yet");throw h.code="BUSINESS",h}const n=await i.log({fs:e,dir:t,depth:1}),r=n.length>0?n[0].oid:null,f=await i.currentBranch({fs:e,dir:t,fullname:!1});if(!f){const h=new Error("Cannot reset: detached HEAD");throw h.code="BUSINESS",h}const l=a==="hard"?new Set(await i.listFiles({fs:e,dir:t,ref:"HEAD"})):null;if(await i.writeRef({fs:e,dir:t,ref:`refs/heads/${f}`,value:o,force:!0}),a==="mixed"||a==="hard"){const h=await i.listFiles({fs:e,dir:t,ref:o}),w=new Set(h);for(const d of h)await i.resetIndex({fs:e,dir:t,filepath:d,ref:o});const u=await i.statusMatrix({fs:e,dir:t});for(const[d,,,g]of u)if(!w.has(d)&&g!==0)try{await i.remove({fs:e,dir:t,filepath:d})}catch{}}if(a==="hard"){const h=await i.listFiles({fs:e,dir:t,ref:o}),w=new Set(h);for(const u of h){const d=await B(t,o,u);if(!d)continue;const g=C.join(t,u);e.mkdirSync(C.dirname(g),{recursive:!0}),e.writeFileSync(g,d.buf)}if(l){for(const u of l)if(!w.has(u)){const d=C.join(t,u);e.existsSync(d)&&e.unlinkSync(d)}}}return{previousHead:r}}async function et(t,s){const{target:o,message:a,authorName:n,authorEmail:r,beforeCommit:f}=s;if(!await I(t)){const c=new Error("Cannot revert: repository has no commits yet");throw c.code="BUSINESS",c}let l;try{l=(await i.readCommit({fs:e,dir:t,oid:o})).oid}catch{const y=new Error("Revert target commit does not exist");throw y.code="VALIDATION",y.field="target",y.target=o,y}const h=await i.statusMatrix({fs:e,dir:t});if(h.some(([,c,y,m])=>c===0&&m===0?!1:y!==1||m!==1)){const c=h.filter(([,m])=>m!==0),y=new Error("Working directory is not clean, please commit or stash your changes before reverting");throw y.code="BUSINESS",y.staged=c.filter(([,,,m])=>m!==1).map(([m])=>m),y.modified=c.filter(([,,m])=>m!==1).map(([m])=>m),y}const u=new Set(await i.listFiles({fs:e,dir:t,ref:l})),d=await D(t),g=new Set(await i.listFiles({fs:e,dir:t,ref:d})),p=[];for(const c of u){const y=await B(t,l,c);if(!y)continue;const m=C.join(t,c);e.mkdirSync(C.dirname(m),{recursive:!0}),e.writeFileSync(m,y.buf),await i.add({fs:e,dir:t,filepath:c})}for(const c of g)if(!u.has(c)){p.push(c);try{await i.remove({fs:e,dir:t,filepath:c})}catch{}const y=C.join(t,c);e.existsSync(y)&&await e.promises.unlink(y)}return p.length>0&&await N(t,p),typeof f=="function"&&await f(),(await i.statusMatrix({fs:e,dir:t})).some(([,,,c])=>c!==1)?{nothingToCommit:!1,commitHash:await i.commit({fs:e,dir:t,message:a,author:T(n,r)}),targetOid:l,previousHead:d}:{nothingToCommit:!0,targetOid:l,previousHead:d}}async function nt(t,s){const o=await i.listFiles({fs:e,dir:t,ref:s});for(const a of o){const n=await B(t,s,a);if(!n)continue;const r=C.join(t,a);e.mkdirSync(C.dirname(r),{recursive:!0}),e.writeFileSync(r,n.buf),await i.add({fs:e,dir:t,filepath:a})}}async function at(t){return i.listTags({fs:e,dir:t})}async function st(t,s){const{tagName:o,message:a,authorName:n,authorEmail:r}=s;a?await i.annotatedTag({fs:e,dir:t,ref:o,message:a,tagger:T(n,r)}):await i.tag({fs:e,dir:t,ref:o})}async function ot(t,s){await i.deleteRef({fs:e,dir:t,ref:`refs/tags/${s}`})}async function it(t){const s=await i.listBranches({fs:e,dir:t}),o=await i.currentBranch({fs:e,dir:t,fullname:!1}),a={};for(const n of s)a[n]={name:n,current:n===o};return{branches:a,current:o}}async function rt(t,s){const{branchName:o,startPoint:a}=s;await i.branch({fs:e,dir:t,ref:o,object:a||"HEAD",checkout:!0})}async function ct(t,s){const o=await i.statusMatrix({fs:e,dir:t});if(o.some(([,n,r,f])=>n===0&&f===0?!1:r!==1||f!==1)){const n=o.filter(([,f])=>f!==0),r=new Error("Working directory is not clean, please commit or stash your changes before switching branches");throw r.code="BUSINESS",r.staged=n.filter(([,,,f])=>f!==1).map(([f])=>f),r.modified=n.filter(([,,f])=>f!==1).map(([f])=>f),r}await i.checkout({fs:e,dir:t,ref:s})}async function ft(t,s){const{branchName:o,force:a=!1}=s;if(await i.currentBranch({fs:e,dir:t,fullname:!1})===o){const r=new Error("Cannot delete the current branch, please switch to another branch first");throw r.code="BUSINESS",r}await i.deleteBranch({fs:e,dir:t,ref:o,force:a})}export{I as isoHasHead,V as isoInit,q as isoStatus,R as isoAddAll,U as isoStageFiles,K as isoCommitAll,z as isoCommitFiles,J as isoUnstage,Q as isoDiscard,X as isoLog,Y as isoDiff,Z as isoFileContent,tt as isoReset,et as isoRevertToTree,nt as isoCheckoutFiles,at as isoListTags,st as isoCreateTag,ot as isoDeleteTag,it as isoListBranches,rt as isoCreateBranch,ct as isoSwitchBranch,ft as isoDeleteBranch,_ as listWorkdirFilesSkippingIgnored,j as addAllFirstCommitWithIsomorphic};
|
|
@@ -1 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import tt from"cross-spawn";import v from"path";import y from"fs";import L from"../../appConfig/index.js";let U=null,G=0;const et=30*1e3,nt=10*60*1e3;function rt(){U=null,G=0}function W(){return L.GIT_USE_NATIVE!==!1}function ot(t){if(!t||typeof t!="object")return!1;const e=t;if(e.code==="ENOENT"||e.code==="NATIVE_GIT_UNAVAILABLE")return!0;const n=`${e.message||""} ${e.stderr||""}`;return/spawn\s+git(\.exe)?\s+ENOENT|git(\.exe)?:?\s*command not found|git(\.exe)?\s+not found|is not recognized as an internal or external command/i.test(n)}function j(){U=null,G=Date.now()+et}function u(t,e,n={}){const{allowFailure:r=!1,env:o={},timeoutMs:i=nt}=n;return new Promise((a,s)=>{const c=tt("git",e,{cwd:t,env:{...process.env,GIT_TERMINAL_PROMPT:"0",...o},windowsHide:!0});let f="",l="",w=!1,C=!1,h=null;const p=i>0?setTimeout(()=>{C=!0;try{c.kill("SIGTERM")}catch{}h=setTimeout(()=>{try{c.kill("SIGKILL")}catch{}},2e3)},i):null,m=d=>{w||(w=!0,p&&clearTimeout(p),h&&clearTimeout(h),d())};c.stdout?.on("data",d=>{f+=d.toString()}),c.stderr?.on("data",d=>{l+=d.toString()}),c.on("error",d=>{m(()=>{if(r){a({stdout:f,stderr:l||d.message,exitCode:1});return}s(d)})}),c.on("close",d=>{m(()=>{if(C){const x=new Error(`git ${e.join(" ")} timed out after ${i}ms`);if(x.code="ETIMEDOUT",x.exitCode=-1,x.stdout=f,x.stderr=l,r){a({stdout:f,stderr:l||x.message,exitCode:-1});return}s(x);return}const g=d??1;if(g!==0&&!r){const x=(l||f||"").trim()||`git ${e.join(" ")} failed`,T=new Error(x);T.exitCode=g,T.stdout=f,T.stderr=l,s(T);return}a({stdout:f,stderr:l,exitCode:g})})})})}function F(t,e){return{GIT_AUTHOR_NAME:t,GIT_AUTHOR_EMAIL:e,GIT_COMMITTER_NAME:t,GIT_COMMITTER_EMAIL:e}}async function q(){if(!W())return!1;if(U===!0)return!0;if(Date.now()<G)return!1;try{if((await u(process.cwd(),["--version"],{allowFailure:!0,timeoutMs:5e3})).exitCode===0)return U=!0,G=0,!0}catch{}return j(),!1}async function it(){return q()}async function A(t){return(await u(t,["rev-parse","--verify","HEAD"],{allowFailure:!0})).exitCode===0}async function I(t,e){const{stdout:n}=await u(t,["rev-parse",e]);return n.trim()}async function N(t,e){const{stdout:n}=await u(t,["ls-tree","-r","--name-only",e]);return n.split(`
|
|
2
|
+
`).map(r=>r.trim()).filter(Boolean)}async function at(t,e){const{authorName:n,authorEmail:r,defaultBranch:o="main"}=e;(await u(t,["init","-b",o],{allowFailure:!0})).exitCode!==0&&(await u(t,["init"]),await u(t,["symbolic-ref","HEAD",`refs/heads/${o}`],{allowFailure:!0})),await u(t,["config","user.name",n]),await u(t,["config","user.email",r])}async function D(t){const{stdout:e}=await u(t,["status","--porcelain=v1","-b"]),n=e.split(`
|
|
3
|
+
`).filter(p=>p.length>0);let r=null,o=null,i=0,a=0;const s=[],c=[],f=[],l=[],w=[],C=[];for(const p of n){if(p.startsWith("## ")){const R=p.slice(3).trim(),$=R.match(/\s*\[([^\]]+)\]\s*$/),P=$?$[1]:null,E=$?R.slice(0,$.index).trim():R;if(E.startsWith("HEAD (")||E==="HEAD"||/^HEAD\b/i.test(E)&&E.includes("Detached"))r=null;else if(E.startsWith("No commits yet on "))r=E.slice(18).trim()||null;else{const k=E.indexOf("...");k>=0?(r=E.slice(0,k)||null,o=E.slice(k+3)||null):r=E||null}if(P){const k=P.match(/ahead\s+(\d+)/),O=P.match(/behind\s+(\d+)/);k&&(i=parseInt(k[1],10)),O&&(a=parseInt(O[1],10))}continue}const m=p.slice(0,2);let d=p.slice(3);d.includes(" -> ")&&(d=d.split(" -> ").pop()),d=J(d);const g=m[0],x=m[1];if(m==="??"){w.push(d);continue}if(m==="!!")continue;(g==="U"||x==="U"||m==="AA"||m==="DD")&&C.push(d),g!==" "&&g!=="?"&&(s.push(d),(g==="A"||g==="C")&&f.push(d),g==="D"&&l.push(d)),(x==="M"||x==="T")&&c.push(d),x==="D"&&l.push(d)}const h=await u(t,["branch","--show-current"],{allowFailure:!0});return h.exitCode===0&&(r=h.stdout.trim()||null),{current:r,staged:[...new Set(s)],modified:[...new Set(c)],created:[...new Set(f)],deleted:[...new Set(l)],untracked:[...new Set(w)],conflicted:[...new Set(C)],ahead:i,behind:a,tracking:o}}async function V(t){await u(t,["add","-A"]);const e=await u(t,["diff","--cached","--quiet"],{allowFailure:!0});if(e.exitCode===0)return{hasChanges:!1,toAdd:[],toRemove:[]};if(e.exitCode===1)return{hasChanges:!0,toAdd:[],toRemove:[]};const n=(e.stderr||e.stdout||"").trim()||`git diff --cached --quiet failed (exit ${e.exitCode})`,r=new Error(n);throw r.exitCode=e.exitCode,r.stderr=e.stderr,r.stdout=e.stdout,r}async function z(t,e,n={}){const{force:r=!1}=n;if(!Array.isArray(e)||e.length===0)return;const o=[],i=[];for(const s of e)y.existsSync(v.join(t,s))?o.push(s):i.push(s);await st(t,r?["add","-A","-f"]:["add","-A"],o);for(const s of i){const c=await u(t,["rm","--cached","-f","--ignore-unmatch","--",_(s)],{allowFailure:!0});if(c.exitCode!==0&&!K(c)){const f=(c.stderr||c.stdout||"").trim()||`git rm --cached failed for ${s}`,l=new Error(f);throw l.exitCode=c.exitCode,l}}}function K(t){const e=`${t.stderr||""} ${t.stdout||""}`;return/did not match any file|pathspec.*did not match|No such file|exists on disk, but not in the index/i.test(e)}async function st(t,e,n){if(!n||n.length===0)return;const r=200;for(let o=0;o<n.length;o+=r){const i=n.slice(o,o+r);try{await u(t,[...e,"--",...b(i)])}catch{for(const s of i){const c=await u(t,[...e,"--",_(s)],{allowFailure:!0});if(c.exitCode===0||K(c))continue;const f=(c.stderr||c.stdout||"").trim()||`git ${e.join(" ")} failed for ${s}`,l=new Error(f);throw l.exitCode=c.exitCode,l}}}}async function ct(t,e){const{message:n,authorName:r,authorEmail:o}=e,i=F(r,o),{hasChanges:a}=await V(t);if(!a)return{nothingToCommit:!0};await u(t,["commit","-m",n],{env:i});const{stdout:s}=await u(t,["rev-parse","HEAD"]),c=s.trim();if(!c)throw new Error("Native git commit succeeded but HEAD is empty");return{nothingToCommit:!1,commitHash:c}}async function X(t,e){const{message:n,authorName:r,authorEmail:o}=e,i=await u(t,["diff","--cached","--quiet"],{allowFailure:!0});if(i.exitCode===0)return{nothingToCommit:!0};if(i.exitCode!==1){const s=(i.stderr||i.stdout||"").trim()||`git diff --cached --quiet failed (exit ${i.exitCode})`,c=new Error(s);throw c.exitCode=i.exitCode,c}await u(t,["commit","-m",n],{env:F(r,o)});const{stdout:a}=await u(t,["rev-parse","HEAD"]);return{nothingToCommit:!1,commitHash:a.trim()}}async function Y(t,e){if(!Array.isArray(e)||e.length===0){const n=await u(t,["diff","--cached","--quiet"],{allowFailure:!0});if(n.exitCode===0)return!1;if(n.exitCode===1)return!0;const r=(n.stderr||n.stdout||"").trim()||`git diff --cached --quiet failed (exit ${n.exitCode})`,o=new Error(r);throw o.exitCode=n.exitCode,o}for(let n=0;n<e.length;n+=200){const r=b(e.slice(n,n+200)),o=await u(t,["diff","--cached","--quiet","--",...r],{allowFailure:!0});if(o.exitCode===1)return!0;if(o.exitCode!==0){const i=(o.stderr||o.stdout||"").trim()||`git diff --cached --quiet failed (exit ${o.exitCode})`,a=new Error(i);throw a.exitCode=o.exitCode,a}}return!1}async function lt(t,e){const{message:n,files:r,authorName:o,authorEmail:i,force:a=!0}=e;return await z(t,r,{force:a}),await Y(t,r)?X(t,{message:n,authorName:o,authorEmail:i}):{nothingToCommit:!0}}function S(t){const e=`${t.stderr||""} ${t.stdout||""}`;return/did not match any file|pathspec.*did not match|unstaged changes after reset|No changes|ambiguous argument 'HEAD'/i.test(e)}async function M(t,e,n,r={},o=200){if(!(!n||n.length===0))for(let i=0;i<n.length;i+=o){const a=b(n.slice(i,i+o));await u(t,[...e,"--",...a],r)}}function _(t){let e=String(t||"").replace(/\\/g,"/");return e.startsWith(":(")||e.startsWith(":!")||e.startsWith(":^")?e:`:(literal)${e}`}function b(t){return(t||[]).map(_)}function J(t){const e=String(t||"");if(e.length<2||e[0]!=='"'||e[e.length-1]!=='"')return e;const n=e.slice(1,-1);let r="";for(let o=0;o<n.length;o++){const i=n[o];if(i!=="\\"){r+=i;continue}if(o+=1,o>=n.length)break;const a=n[o];switch(a){case"\\":r+="\\";break;case'"':r+='"';break;case"a":r+="\x07";break;case"b":r+="\b";break;case"t":r+=" ";break;case"n":r+=`
|
|
4
|
+
`;break;case"v":r+="\v";break;case"f":r+="\f";break;case"r":r+="\r";break;default:if(a>="0"&&a<="7"){let s=a;o+1<n.length&&n[o+1]>="0"&&n[o+1]<="7"&&(s+=n[++o],o+1<n.length&&n[o+1]>="0"&&n[o+1]<="7"&&(s+=n[++o])),r+=String.fromCharCode(parseInt(s,8))}else r+=a;break}}return r}function Q(t){let e=String(t||"").replace(/\\/g,"/");return e.length>1&&e.endsWith("/")&&(e=e.replace(/\/+$/,"")),e}function Z(t,e){const n=[...e].map(Q).filter(Boolean),r=(t||[]).map(Q).filter(Boolean),o=new Set(r),i=new Set;for(const a of n){o.has(a)&&i.add(a);let s=v.posix.dirname(a);for(;s&&s!==".";){o.has(s)&&i.add(s);const c=v.posix.dirname(s);if(c===s)break;s=c}}for(const a of r)for(const s of n)if(s===a||s.startsWith(`${a}/`)){i.add(a);break}return[...i]}async function H(t,e,n){let r=n;if(r||(r=(await D(t)).untracked),!r||r.length===0)return;const o=Z(r,e);for(const i of o){const a=v.join(t,i);y.existsSync(a)&&await y.promises.rm(a,{recursive:!0,force:!0})}o.length>0&&await B(t,o)}async function ut(t,e){const n=await A(t);if(Array.isArray(e)&&e.length>0){if(n){let f=null;try{for(let h=0;h<e.length;h+=200){const p=b(e.slice(h,h+200)),m=await u(t,["restore","--staged","--",...p],{allowFailure:!0});if(m.exitCode!==0&&!S(m)){f=m;break}}}catch(h){f={stderr:h.message,stdout:"",exitCode:1}}if(!f)return;let l=null;for(let h=0;h<e.length;h+=200){const p=b(e.slice(h,h+200)),m=await u(t,["reset","HEAD","--",...p],{allowFailure:!0});if(m.exitCode!==0&&!S(m)){l=m;break}}if(!l)return;const w=(l.stderr||l.stdout||f.stderr||f.stdout||"").trim()||"git unstage failed",C=new Error(w);throw C.exitCode=l.exitCode||f.exitCode,C}let a=null;for(let f=0;f<e.length;f+=200){const l=b(e.slice(f,f+200)),w=await u(t,["rm","--cached","-f","--",...l],{allowFailure:!0});if(w.exitCode!==0&&!S(w)){a=w;break}}if(!a)return;const s=(a.stderr||a.stdout||"").trim()||"git unstage (rm --cached) failed",c=new Error(s);throw c.exitCode=a.exitCode,c}if(n){const a=await u(t,["restore","--staged","."],{allowFailure:!0});if(a.exitCode===0||S(a))return;const s=await u(t,["reset","HEAD"],{allowFailure:!0});if(s.exitCode===0||S(s))return;const c=(s.stderr||s.stdout||a.stderr||a.stdout||"").trim()||"git unstage all failed",f=new Error(c);throw f.exitCode=s.exitCode||a.exitCode,f}const r=await u(t,["reset"],{allowFailure:!0});if(r.exitCode===0||S(r))return;const o=(r.stderr||r.stdout||"").trim()||"git unstage all failed",i=new Error(o);throw i.exitCode=r.exitCode,i}async function B(t,e){const n=new Set;for(const o of e){let i=v.dirname(o);for(;i&&i!==".";)n.add(i),i=v.dirname(i)}const r=[...n].sort((o,i)=>{const a=o.split(/[/\\]/).length;return i.split(/[/\\]/).length-a});for(const o of r){const i=v.join(t,o);try{(await y.promises.readdir(i)).length===0&&await y.promises.rmdir(i)}catch{}}}async function ft(t,e){const n=await D(t),r=Array.isArray(e)&&e.length>0?new Set(e):null,o=[],i=[],a=[],s=new Set([...n.staged,...n.modified,...n.deleted]);for(const l of n.created)s.delete(l);for(const l of s)r&&!r.has(l)||o.push(l);const c=new Set(o);for(const l of n.created)r&&!r.has(l)||c.has(l)||i.push(l);for(const l of n.untracked)r&&!r.has(l)||c.has(l)||a.push(l);const f=await A(t);if(o.length>0&&f&&(await H(t,o,n.untracked),await M(t,["restore","--source=HEAD","--staged","--worktree"],o)),i.length>0){await M(t,["rm","-f","--cached"],i,{allowFailure:!0});for(const l of i){const w=v.join(t,l);y.existsSync(w)&&await y.promises.unlink(w)}await B(t,i)}for(const l of a){const w=v.join(t,l);y.existsSync(w)&&(y.statSync(w).isDirectory()?await y.promises.rm(w,{recursive:!0,force:!0}):await y.promises.unlink(w))}return a.length>0&&await B(t,a),{discardedCount:o.length+i.length+a.length,trackedFiles:o,newFiles:i,untrackedFiles:a}}function dt(t){return/unknown revision|bad revision|invalid object name|ambiguous argument|does not have any commits|Needed a single revision|not a valid object name|unknown revision or path not in the working tree/i.test(t)}async function ht(t,e={}){const{maxCount:n=50,skip:r=0,branch:o,filePath:i}=e;if(!await A(t))return[];const a=["log",`--skip=${r}`,"-n",String(n),"--format=%H%x00%aI%x00%B%x00%an%x00%ae%x1e"];o&&a.push(o),i&&a.push("--",_(i));const s=await u(t,a,{allowFailure:!0});if(s.exitCode!==0){const f=(s.stderr||s.stdout||"").trim();if(dt(f))return[];const l=new Error(f||`git log failed (exit ${s.exitCode})`);throw l.exitCode=s.exitCode,l.stderr=s.stderr,l.stdout=s.stdout,l}return s.stdout.split("").filter(f=>f.trim()).map(f=>{const l=f.replace(/^\n/,"").split("\0"),[w,C,h,p,m]=l;let d="";if(C){const g=new Date(C);d=Number.isNaN(g.getTime())?C:g.toISOString()}return{hash:(w||"").trim(),date:d,message:(h||"").replace(/\n$/,""),author_name:p||"",author_email:m||""}}).filter(f=>f.hash)}function mt(t){const e=[];for(const n of t.split(`
|
|
5
|
+
`)){if(!n.trim())continue;const r=n.split(" ");if(r.length<3)continue;const[o,i,a]=r,s=o==="-"||i==="-",c=s?0:parseInt(o,10)||0,f=s?0:parseInt(i,10)||0;e.push({file:a,changes:c+f,insertions:c,deletions:f,binary:s})}return e}async function wt(t,e={}){const{source:n="worktree",from:r,to:o,paths:i}=e,a=Array.isArray(i)&&i.length>0?["--",...b(i)]:[];if(n!=="commit"&&!await A(t))return{diff:"",summary:{files:[],insertions:0,deletions:0}};let s,c;if(n==="commit")if(r&&o)s=["diff",r,o,...a],c=["diff","--numstat",r,o,...a];else if(r){const d=((await u(t,["rev-list","--parents","-n","1",r],{allowFailure:!0})).stdout||"").trim().split(/\s+/).filter(Boolean);if(d.length<=1){const g=await u(t,["hash-object","-t","tree","/dev/null"],{allowFailure:!0});s=["show","--format=","--patch",r,...a],c=["show","--format=","--numstat",r,...a]}else{const g=d[1];s=["diff",g,r,...a],c=["diff","--numstat",g,r,...a]}}else{const m=new Error("source=commit requires at least 'from'");throw m.code="VALIDATION",m}else n==="staged"?(s=["diff","--cached",...a],c=["diff","--cached","--numstat",...a]):(s=["diff","HEAD",...a],c=["diff","HEAD","--numstat",...a]);const[f,l]=await Promise.all([u(t,s,{allowFailure:!0}),u(t,c,{allowFailure:!0})]),w=f.exitCode===0||f.exitCode===1?f.stdout:"";if(f.exitCode>1&&!w)throw new Error((f.stderr||f.stdout||"git diff failed").trim());const C=l.exitCode===0||l.exitCode===1?mt(l.stdout):[];let h=0,p=0;for(const m of C)h+=m.insertions,p+=m.deletions;return{diff:w,summary:{files:C,insertions:h,deletions:p}}}async function gt(t,e){const{ref:n,filePath:r}=e;if(n==="worktree"||n==="staged"||n===""){const i=v.join(t,r);return y.existsSync(i)?y.readFileSync(i,"utf8"):""}const o=await u(t,["show",`${n}:${r}`],{allowFailure:!0});return o.exitCode!==0?"":o.stdout}async function Ct(t,e){const{target:n,mode:r}=e;if(!await A(t)){const s=new Error("Cannot reset: repository has no commits yet");throw s.code="BUSINESS",s}const o=await u(t,["branch","--show-current"],{allowFailure:!0});if(!(o.exitCode===0?o.stdout.trim():"")){const s=new Error("Cannot reset: detached HEAD");throw s.code="BUSINESS",s}const a=await I(t,"HEAD");if(r==="hard"){let s=[];try{s=await N(t,n)}catch{s=[]}s.length>0&&await H(t,s)}return await u(t,["reset",`--${r}`,n]),{previousHead:a}}async function pt(t,e){const{target:n,message:r,authorName:o,authorEmail:i,beforeCommit:a}=e;if(!await A(t)){const d=new Error("Cannot revert: repository has no commits yet");throw d.code="BUSINESS",d}let s;try{s=await I(t,n)}catch{const g=new Error("Revert target commit does not exist");throw g.code="VALIDATION",g.field="target",g.target=n,g}const c=await D(t);if(c.staged.length>0||c.modified.length>0||c.deleted.length>0||c.created.length>0||c.conflicted.length>0){const d=new Error("Working directory is not clean, please commit or stash your changes before reverting");throw d.code="BUSINESS",d.staged=c.staged,d.modified=c.modified,d}const l=await I(t,"HEAD"),w=new Set(await N(t,l)),C=new Set(await N(t,s));C.size>0&&(await H(t,C,c.untracked),await u(t,["restore",`--source=${s}`,"--worktree","--staged","."]));const h=[...w].filter(d=>!C.has(d));return h.length>0&&(await M(t,["rm","-f"],h),await B(t,h)),typeof a=="function"&&await a(),(await u(t,["diff","--cached","--quiet"],{allowFailure:!0})).exitCode===0?{nothingToCommit:!0,targetOid:s,previousHead:l}:(await u(t,["commit","-m",r],{env:F(o,i)}),{nothingToCommit:!1,commitHash:await I(t,"HEAD"),targetOid:s,previousHead:l})}async function xt(t,e){const n=await N(t,e);n.length!==0&&(await H(t,n),await M(t,["restore",`--source=${e}`,"--worktree","--staged"],n))}async function yt(t){const{stdout:e}=await u(t,["tag","--list"]);return e.split(`
|
|
6
|
+
`).map(n=>n.trim()).filter(Boolean)}async function Et(t,e){const{tagName:n,message:r,authorName:o,authorEmail:i}=e;r?await u(t,["tag","-a",n,"-m",r],{env:F(o||L.GIT_DEFAULT_AUTHOR_NAME,i||L.GIT_DEFAULT_AUTHOR_EMAIL)}):await u(t,["tag",n])}async function vt(t,e){await u(t,["tag","-d",e])}async function bt(t){const{stdout:e}=await u(t,["branch","--format=%(refname:short)"]),n=e.split(`
|
|
7
|
+
`).map(a=>a.trim()).filter(Boolean),r=await u(t,["branch","--show-current"],{allowFailure:!0}),o=r.exitCode===0&&r.stdout.trim()?r.stdout.trim():null,i={};for(const a of n)i[a]={name:a,current:a===o};return{branches:i,current:o}}async function At(t,e){const{branchName:n,startPoint:r}=e;r?await u(t,["checkout","-b",n,r]):await u(t,["checkout","-b",n])}async function kt(t,e){const n=await D(t);if(n.staged.length>0||n.modified.length>0||n.deleted.length>0||n.created.length>0||n.conflicted.length>0){const o=new Error("Working directory is not clean, please commit or stash your changes before switching branches");throw o.code="BUSINESS",o.staged=n.staged,o.modified=n.modified,o}await u(t,["checkout",e])}async function St(t,e){const{branchName:n,force:r=!1}=e;if((await u(t,["branch","--show-current"],{allowFailure:!0})).stdout.trim()===n){const i=new Error("Cannot delete the current branch, please switch to another branch first");throw i.code="BUSINESS",i}await u(t,["branch",r?"-D":"-d",n])}export{W as isNativeGitEnabled,q as isNativeGitAvailable,it as shouldUseNativeGit,ot as isNativeGitUnavailableError,j as markNativeGitMissing,u as runGit,F as buildAuthorEnv,A as nativeHasHead,I as nativeResolveOid,N as nativeListFiles,at as nativeInit,D as nativeStatus,V as nativeAddAll,z as nativeStageFiles,ct as nativeAddAllAndCommit,X as nativeCommitStaged,lt as nativeCommitFiles,Y as nativeHasStagedChangesForPaths,_ as toLiteralPathspec,b as toLiteralPathspecs,ut as nativeUnstage,ft as nativeDiscard,ht as nativeLog,wt as nativeDiff,gt as nativeFileContent,Ct as nativeReset,pt as nativeRevertToTree,xt as nativeCheckoutFiles,yt as nativeListTags,Et as nativeCreateTag,vt as nativeDeleteTag,bt as nativeListBranches,At as nativeCreateBranch,kt as nativeSwitchBranch,St as nativeDeleteBranch,rt as resetNativeGitAvailabilityCache,B as cleanEmptyParentDirs,Z as collectUntrackedRestoreBlockers,H as removeUntrackedBlockingRestore,J as decodeGitPorcelainPath};
|
package/package.json
CHANGED