nuwax-file-server 1.5.7 → 1.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{Command as ee}from"commander";import{createRequire as te}from"module";import f from"path";import U from"os";import c from"fs-extra";import{spawn as E}from"cross-spawn";import V from"tree-kill";import{fileURLToPath as q}from"url";import{execFileSync as R}from"child_process";import M from"http";import{createRequire as G}from"module";var I=f.dirname(q(import.meta.url)),H=G(import.meta.url),n={name:"nuwax-file-server",pidDir:f.join(U.tmpdir(),"nuwax-file-server"),pidFileName:"server.pid",lockFileName:"start.lock",defaultStopTimeout:3e4,defaultStartTimeout:3e4,staleLockTimeout:12e4,checkInterval:500};function $(){return f.join(n.pidDir,n.pidFileName)}function F(){return f.join(n.pidDir,n.lockFileName)}function J(){let e=[f.join(I,"..","server.js"),f.join(I,"server.js")];for(let t of e)if(c.existsSync(t))return t;return e[0]}function W(){let e=["../../package.json","../package.json"];for(let t of e)try{let o=H(t);if(o?.version)return o.version}catch{}return"unknown"}function x(){try{let e=$();if(!c.existsSync(e))return null;let t=c.readFileSync(e,"utf8"),o=JSON.parse(t);return!o||typeof o.pid!="number"?null:o}catch(e){return e.code!=="ENOENT"&&console.error(`Read PID file failed: ${e.message}`),null}}function B(e){try{let t=$();c.ensureDirSync(n.pidDir),c.writeFileSync(t,JSON.stringify(e,null,2)),console.debug(`PID file written: ${t}`)}catch(t){throw console.error(`Write PID file failed: ${t.message}`),t}}function v(){try{let e=$();c.existsSync(e)&&(c.removeSync(e),console.debug(`PID file deleted: ${e}`))}catch(e){console.error(`Delete PID file failed: ${e.message}`)}}function g(e){try{return process.kill(e,0),!0}catch(t){return t.code!=="ESRCH"}}function K(e){try{return k()?R("powershell",["-NoProfile","-Command",`(Get-CimInstance Win32_Process -Filter "ProcessId = ${e}").CommandLine`],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim():R("ps",["-p",String(e),"-o","command="],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return""}}function X(e){if(!g(e))return!1;let t=K(e);if(!t)return!1;let o=t.toLowerCase(),s=f.basename(f.join(I,"..","server.js")).toLowerCase(),i=n.name.toLowerCase();return o.includes(s)||o.includes(i)}function z(){c.ensureDirSync(n.pidDir);let e=F(),t=`${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2,10)}`,o=JSON.stringify({pid:process.pid,token:t,createdAt:new Date().toISOString()},null,2);try{let s=c.openSync(e,"wx");return c.writeSync(s,o),{fd:s,token:t}}catch(s){if(s.code!=="EEXIST")throw s;try{let i=c.readFileSync(e,"utf8"),r=JSON.parse(i),a=Number(r?.pid),u=r?.createdAt?new Date(r.createdAt).getTime():0,l=Date.now()-u,C=Number.isFinite(a)&&a>0&&g(a),p=!u||Number.isNaN(l)||l>n.staleLockTimeout;if(!C||p){c.removeSync(e);let S=c.openSync(e,"wx");return c.writeSync(S,o),console.warn("Detected stale start lock, auto cleaned"),{fd:S,token:t}}}catch{c.removeSync(e);let r=c.openSync(e,"wx");return c.writeSync(r,o),console.warn("Detected invalid start lock, auto cleaned"),{fd:r,token:t}}throw s}}function Q(e){let t=e?.fd,o=e?.token;try{t!=null&&c.closeSync(t)}catch{}try{let s=F();if(c.existsSync(s)){let i=!1;if(!o)i=!0;else try{let r=c.readFileSync(s,"utf8");i=JSON.parse(r)?.token===o}catch{i=!0}i&&c.removeSync(s)}}catch{}}function k(){return process.platform==="win32"}async function Y(e=n.defaultStopTimeout){let t=x();return t?X(t.pid)?(console.log(`Existing service process detected (PID: ${t.pid}), stopping before start...`),await h(t.pid,!1)&&await D(t.pid,e)?(v(),{success:!0,message:`Existing process ${t.pid} stopped gracefully`}):(console.warn(`Graceful stop timeout or failed for PID ${t.pid}, force stop...`),await h(t.pid,!0)?await D(t.pid,e)?(v(),{success:!0,message:`Existing process ${t.pid} stopped forcibly`}):{success:!1,message:`Existing process ${t.pid} did not exit after force stop`}:{success:!1,message:`Failed to stop existing process ${t.pid}`})):(console.log(`Found stale PID file (PID: ${t.pid}), clean it...`),v(),{success:!0,message:"Stale PID file cleaned"}):{success:!0,message:"No existing service process"}}async function h(e,t=!1){return new Promise(o=>{if(!g(e)){console.debug(`Process ${e} does not exist, already stopped`),o(!0);return}let s=t?"SIGKILL":"SIGTERM",i=k()?"taskkill":"tree-kill";if(console.debug(`Use ${i} to stop process ${e} (signal: ${s})`),k()){let r=t?["/F","/PID",String(e)]:["/PID",String(e)],a=E("taskkill",r,{stdio:["ignore","pipe","pipe"],windowsHide:!0}),u="";a.stdout.on("data",l=>{u+=l.toString()}),a.stderr.on("data",l=>{u+=l.toString()}),a.on("error",l=>{console.error(`Stop process failed: ${l.message}`),o(!1)}),a.on("close",l=>{l===0?(console.debug(`Process ${e} stopped`),o(!0)):(console.warn(`taskkill exit code: ${l}, output: ${u}`),t?o(!1):h(e,!0).then(o))})}else V(e,s,r=>{r?r.code==="ESRCH"?(console.debug(`Process ${e} does not exist`),o(!0)):(console.error(`Stop process failed: ${r.message}`),o(!1)):(console.debug(`Process ${e} stopped`),o(!0))})})}async function D(e,t=n.defaultStopTimeout){let o=Date.now();for(;g(e);){if(Date.now()-o>t)return console.warn(`Wait for process ${e} to stop timeout (${t}ms)`),!1;await new Promise(i=>setTimeout(i,n.checkInterval))}let s=Date.now()-o;return console.debug(`Process ${e} stopped after ${s}ms`),!0}async function Z(e,t=n.defaultStartTimeout){let o=Number(e);if(!Number.isFinite(o)||o<=0)return!1;let s=Date.now();for(;Date.now()-s<=t;){if(await new Promise(r=>{let a=M.get({host:"127.0.0.1",port:o,path:"/health",timeout:Math.min(2e3,n.checkInterval*4)},u=>{r(u.statusCode>=200&&u.statusCode<300),u.resume()});a.on("timeout",()=>{a.destroy(),r(!1)}),a.on("error",()=>r(!1))}))return!0;await new Promise(r=>setTimeout(r,n.checkInterval))}return!1}async function N(e={}){let{env:t,port:o,config:s}=e,i=null;console.log(`Start service ${n.name}...`);try{i=z()}catch(r){return r.code==="EEXIST"?{success:!1,pid:null,message:"Another start operation is in progress, please retry later"}:{success:!1,pid:null,message:`Acquire start lock failed: ${r.message}`}}try{let r=Number(e.timeout)||n.defaultStopTimeout,a=await Y(r);if(!a.success)return{success:!1,pid:null,message:`Service start blocked: ${a.message}`};let u={...process.env};t&&(u.NODE_ENV=t,console.log(`Environment: ${t}`)),o&&(u.PORT=o,console.log(`Port: ${o}`)),s&&(u.CONFIG_FILE=s,console.log(`Configuration file: ${s}`));let l=J(),p=E("node",[l,...[]],{env:u,stdio:["pipe","pipe","pipe"],detached:!0,cwd:process.cwd()});if(p.stdout.on("data",m=>{process.stdout.write(m)}),p.stderr.on("data",m=>{process.stderr.write(m)}),p.on("error",m=>{console.error(`Start service failed: ${m.message}`)}),await new Promise(m=>setTimeout(m,2e3)),!g(p.pid))return{success:!1,pid:null,message:"Service start failed"};let S={pid:p.pid,startedAt:new Date().toISOString(),env:t||process.env.NODE_ENV||"production",port:o||process.env.PORT||"60000",version:W(),platform:process.platform};B(S);let P=Number(e.startTimeout)||n.defaultStartTimeout;return await Z(S.port,P)?(p.unref(),console.log(`Service started (PID: ${p.pid})`),console.log(`Service address: http://localhost:${S.port}`),{success:!0,pid:p.pid,message:"Service started successfully"}):(console.error(`Service health check timeout (${P}ms), stop failed instance...`),await h(p.pid,!0),v(),{success:!1,pid:null,message:`Service health check timeout (${P}ms)`})}finally{Q(i)}}async function T(e={}){let{force:t=!1,timeout:o=n.defaultStopTimeout}=e;console.log(`Stop service ${n.name}...`);let s=x();if(!s)return{success:!1,message:"Service not found"};if(!g(s.pid))return console.log("Service process has stopped, clean PID file..."),v(),{success:!0,message:"Service has stopped (process has exited)"};if(!await h(s.pid,t))return{success:!1,message:"Stop service failed"};let r=await D(s.pid,o);return v(),r?{success:!0,message:"Service has stopped"}:{success:!1,message:"Service stop timeout"}}async function O(e={}){console.log(`Restart service ${n.name}...`);let t=await T(e);!t.success&&t.message!=="Service not found"&&console.warn(`Stop service failed: ${t.message}`),await new Promise(s=>setTimeout(s,2e3));let o=await N(e);return o.success?{success:!0,pid:o.pid,message:"Service has restarted"}:{success:!1,pid:null,message:`Restart failed: ${o.message}`}}function _(){let e=x();return e?g(e.pid)?{running:!0,pidInfo:e,message:"Service running"}:{running:!1,pidInfo:e,message:"Service process does not exist"}:{running:!1,pidInfo:null,message:"Service not running"}}function L(e){try{let t=new Date(e),o=new Date;if(isNaN(t.getTime()))return"unknown";let s=Math.floor((o-t)/1e3),i=Math.floor(s/3600),r=Math.floor(s%3600/60),a=s%60;return i>0?`${i} hours ${r} minutes ${a} seconds`:r>0?`${r} minutes ${a} seconds`:`${a} seconds`}catch{return"unknown"}}var Pe=te(import.meta.url),A="1.5.7",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.9",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{log as i}from"../utils/log/logUtils.js";import E from"../appConfig/index.js";import p from"path";import t from"fs";import{extractZip as _}from"../utils/common/zipUtils.js";import"../utils/build/startDevUtils.js";import"../utils/build/restartDevUtils.js";import{stopDevServer as W}from"../utils/build/stopDevUtils.js";import{ValidationError as h,BusinessError as G,SystemError as P,FileError as b,ResourceError as A}from"../utils/error/errorHandler.js";import{sanitizeSensitivePaths as C}from"../utils/common/sensitiveUtils.js";import{removeNodeModules as ee}from"../utils/buildDependency/dependencyManager.js";import{backupProjectToZip as te,copyDirectoryFiltered as ie}from"../utils/project/backupUtils.js";import{createPnpmNpmrc as L}from"../utils/common/npmrcUtils.js";import{copyNodeModulesFromCache as J}from"../utils/common/templateCacheUtils.js";import{resolveProjectPath as N}from"../utils/common/projectPathUtils.js";import{ensurePrimaryAgentDirs as se,syncAgents as re}from"../utils/common/AgentWorkspaceUtils.js";async function Z(e,s="react",c={}){const a=Date.now();if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});const r=["react","vue3"];if(!r.includes(s))throw new h("Template type is invalid, only supports react or vue3",{field:"templateType",templateType:s,allowedValues:r});const m={react:E.INIT_PROJECT_NAME_REACT||"react-vite-template",vue3:E.INIT_PROJECT_NAME_VUE3||"vue3-vite-template"}[s],u=N(e,c);if(t.existsSync(u))throw new G(`Project directory ${e} already exists`,{projectId:e,projectPath:u});try{t.mkdirSync(u,{recursive:!0}),i(e,"INFO",`Project directory created successfully: ${u}`,{projectId:e});const f=E.INIT_PROJECT_DIR,w=p.join(f,`${m}.zip`),l=p.join(f,m);if(i(e,"DEBUG","Start checking template directory",{templateDir:l,templateZipPath:w}),!t.existsSync(l)){if(!t.existsSync(w))throw i(e,"ERROR",`Initialization template does not exist: ${w}`,{projectId:e,templateZipPath:w}),new A("Initialization template does not exist",{});if(i(e,"INFO",`Template directory does not exist, starting to unzip template: ${w}`,{projectId:e,templateZipPath:w}),await _(w,l),i(e,"INFO","Template unzip completed",{projectId:e}),!t.existsSync(l))throw new P("Template unzip directory still does not exist",{})}i(e,"DEBUG","Start copying template content to project directory",{templateDir:l,projectPath:u});const d=await t.promises.readdir(l,{withFileTypes:!0});for(const n of d){const y=p.join(l,n.name),R=p.join(u,n.name);n.isDirectory()?(await t.promises.mkdir(R,{recursive:!0}),await ie(y,R)):n.isFile()&&(await t.promises.mkdir(p.dirname(R),{recursive:!0}),await t.promises.copyFile(y,R))}i(e,"DEBUG","Try copying node_modules from template cache",{projectPath:u});const D=await J(u,e);if(D.cached&&i(e,"INFO",`node_modules copied from cache: ${D.templateType}`,{elapsed:D.elapsed}),i(e,"DEBUG","Start creating .npmrc configuration file",{projectPath:u}),await L(u,e),i(e,"INFO",`Project ${e} initialized successfully`,{projectId:e,elapsedMs:Date.now()-a}),E.GIT_ENABLED)try{const n=await import("./gitService.js");await n.init({workspaceType:"pageApp",projectId:e,isolationContext:c}),await n.commit({workspaceType:"pageApp",projectId:e,isolationContext:c,message:`init project: ${e}`})}catch(n){i(e,"WARN","Git init/commit failed, skipping",{error:n.message})}return{success:!0,message:`Project ${e} created successfully`,projectPath:u}}catch(f){throw i(e,"ERROR",`Project ${e} initialization failed: ${f.message}`,{projectId:e,elapsedMs:Date.now()-a}),new P(`Project ${e} initialization failed: ${f.message}`,{projectId:e,projectPath:u,originalError:f.message})}}async function ae(e){const s=await t.promises.readdir(e,{withFileTypes:!0}),c=E.TOP_LEVEL_NOISE_PATTERNS,a=s.filter(r=>{const o=r.name;return o.startsWith(".")?!1:!c.some(m=>m.endsWith("*")?o.startsWith(m.slice(0,-1)):o===m)});if(a.length===1&&a[0].isDirectory()){const r=p.join(e,a[0].name),o=p.join(e,`temp_${Date.now()}`);try{await t.promises.rename(r,o)}catch(u){if(u.code==="EXDEV")await t.promises.cp(r,o,{recursive:!0}),await t.promises.rm(r,{recursive:!0,force:!0});else throw u}const m=await t.promises.readdir(o);for(const u of m){const f=p.join(o,u),w=p.join(e,u);try{await t.promises.rename(f,w)}catch(l){if(l.code==="EXDEV")await t.promises.cp(f,w,{recursive:!0}),await t.promises.rm(f,{recursive:!0,force:!0});else throw l}}await t.promises.rm(o,{recursive:!0,force:!0})}}async function M(e,s={}){if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});const c=N(e,s);if(t.existsSync(c))try{i(e,"INFO",`Start cleaning project directory: ${c}`,{projectId:e}),await t.promises.rm(c,{recursive:!0,force:!0}),i(e,"INFO",`Project directory cleaned up: ${c}`,{projectId:e})}catch(a){throw i(e,"ERROR",`Failed to clean project directory: ${a.message}`,{projectId:e,projectPath:c,originalError:a.message}),new P(`Failed to clean project directory: ${a.message}`,{projectId:e,projectPath:c,originalError:a.message})}else i(e,"INFO",`Project directory does not exist, no need to clean: ${c}`,{projectId:e})}function oe(e){if(!t.existsSync(e))return!0;try{return t.readdirSync(e).filter(a=>!a.startsWith(".")&&a!=="node_modules").length===0}catch(s){const c=p.basename(e);return i(c,"ERROR",`Failed to check if directory is empty: ${s.message}`,{dirPath:e}),!0}}async function V(e,s,c,a,r,o,m={}){const u=Date.now(),f=N(e,m);try{if(oe(f))i(e,"INFO","Project directory is empty, directly deploying new project",{projectId:e});else{i(e,"INFO","Project directory is not empty, starting to backup current version",{projectId:e});const l=parseInt(a)-1,d=p.join(E.UPLOAD_PROJECT_DIR,e),D=p.join(d,`${e}-v${l}.zip`);if(t.existsSync(D))i(e,"INFO",`Backup file already exists, skipping backup: ${D}`,{projectId:e});else try{E.GIT_ENABLED||await O(e,l,m),i(e,"INFO",`Current version backed up: ${D}`,{projectId:e})}catch(n){throw i(e,"ERROR",`Failed to backup current version: ${n.message}`,{projectId:e}),new P(`Failed to backup current version: ${n.message}`,{projectId:e,originalError:n.message})}if(r&&!isNaN(Number(r))){const n=Number(r);i(e,"INFO",`Stopping old version dev server, PID: ${n}`,{projectId:e});try{await W(c,e,n,{strict:!0}),i(e,"INFO","Old version dev server stopped",{projectId:e})}catch(y){i(e,"WARN",`Failed to stop old version dev server: ${y.message}`,{projectId:e,pid:n})}}t.existsSync(f)&&(i(e,"INFO",`Cleaning project directory: ${f}`,{projectId:e}),await t.promises.rm(f,{recursive:!0,force:!0}))}if(t.mkdirSync(f,{recursive:!0}),i(e,"INFO",`Project directory created successfully: ${f}`,{projectId:e}),i(e,"DEBUG","Start extracting zip file to project directory",{projectId:e,zipFilePath:s}),await _(s,f),i(e,"DEBUG","Zip file extracted successfully",{projectId:e}),i(e,"DEBUG","Check and remove top level folder",{projectId:e}),await ae(f),i(e,"DEBUG","Check and remove node_modules folder",{projectId:e}),await ee(f),i(e,"DEBUG","Try copying node_modules from template cache",{projectId:e}),await J(f,e),i(e,"DEBUG","Start creating .npmrc configuration file",{projectId:e}),await L(f,e),E.GIT_ENABLED)try{const l=await import("./gitService.js");await l.init({workspaceType:"pageApp",projectId:e,isolationContext:m}),await l.commit({workspaceType:"pageApp",projectId:e,isolationContext:m,message:`upload project v${a}`})}catch(l){i(e,"WARN","Git commit failed after upload, skipping",{error:l.message})}return i(e,"INFO",`Project ${e} uploaded successfully`,{projectId:e,codeVersion:a,elapsedMs:Date.now()-u}),{success:!0,message:`Project ${e} uploaded successfully`,projectId:e,codeVersion:a}}catch(w){i(e,"ERROR",`Failed to upload project: ${w.message}`,{projectId:e,elapsedMs:Date.now()-u});try{await M(e,m),i(e,"INFO","Failed to upload project, project directory cleaned up",{projectId:e})}catch(l){i(e,"ERROR",`Failed to clean project directory: ${l.message}`,{projectId:e,originalError:l.message})}throw w.isOperational?w:new P(`Failed to upload project: ${w.message}`,{projectId:e,projectPath:f,zipFilePath:s,originalError:w.message})}}async function O(e,s,c={}){if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});if(s==null)throw new h("codeVersion cannot be empty",{field:"codeVersion"});const a=Number(s);if(!Number.isFinite(a))throw new h("codeVersion must be a number",{field:"codeVersion"});const r=N(e,c);if(!t.existsSync(r))throw new A("Project does not exist",{projectId:e});const o=p.join(E.UPLOAD_PROJECT_DIR,e);t.existsSync(o)||t.mkdirSync(o,{recursive:!0});const m=`${e}-v${a}.zip`,u=p.join(o,m);return i(e,"DEBUG","Start backing up project to zip",{projectId:e,versionNum:a,outZipPath:u}),await te(e,r,u)}async function X(e,s,c){if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});if(!s)throw new h("codeVersion cannot be empty",{field:"codeVersion"});if(!c)throw new h("Please upload a zip file",{field:"zipFile"});const a=p.join(E.UPLOAD_PROJECT_DIR,e);t.existsSync(a)||t.mkdirSync(a,{recursive:!0});const r=c.path,o=p.join(a,`${e}-v${s}.zip`);try{try{t.renameSync(r,o)}catch(m){if(m.code==="EXDEV")t.copyFileSync(r,o),t.unlinkSync(r);else throw m}return i(e,"INFO","File saved successfully",{projectId:e,codeVersion:s,filePath:o}),{success:!0,filePath:o}}catch(m){if(i(e,"ERROR","Failed to move file",{projectId:e,codeVersion:s,error:m.message}),t.existsSync(r))try{t.unlinkSync(r)}catch(u){i(e,"ERROR","Failed to clean temporary file",{projectId:e,error:u.message})}throw new P("Failed to save file",{projectId:e,codeVersion:s,originalError:m.message})}}function ne(e){if(!e)return[];if(Array.isArray(e))return e.map(s=>typeof s=="string"?s.trim():"").filter(Boolean);if(typeof e=="string"){const s=e.trim();if(!s)return[];try{const c=JSON.parse(s);if(Array.isArray(c))return c.map(a=>typeof a=="string"?a.trim():"").filter(Boolean)}catch{}return[s]}return[]}async function q(e){t.existsSync(e)&&await t.promises.rm(e,{recursive:!0,force:!0})}async function H(e,s){try{await t.promises.rename(e,s)}catch(c){if(c.code==="EXDEV"){async function a(r,o){if((await t.promises.lstat(r)).isDirectory()){await t.promises.mkdir(o,{recursive:!0});const u=await t.promises.readdir(r);for(const f of u)await a(p.join(r,f),p.join(o,f))}else await t.promises.copyFile(r,o)}await a(e,s),await t.promises.rm(e,{recursive:!0,force:!0})}else throw c}}async function le(e,s){const c=p.join(e,s);if(t.existsSync(c)&&(await t.promises.lstat(c)).isDirectory())return c;const a=await t.promises.readdir(e,{withFileTypes:!0});for(const r of a){if(!r.isDirectory())continue;const o=p.join(e,r.name,s);if(t.existsSync(o)&&(await t.promises.lstat(o)).isDirectory())return o}return null}async function ce(e,s,c){let a;try{a=await fetch(e)}catch(u){throw new b(`Failed to download skill zip from url: ${e}`,{url:e,reason:u.message})}if(!a.ok)throw new b(`Failed to download skill zip from url: ${e}`,{url:e,status:a.status,statusText:a.statusText});const r=a.headers.get("content-type")||"";r&&!r.includes("zip")&&!r.includes("octet-stream")&&i(c,"WARN","Downloaded skill url content-type is not typical zip",{url:e,contentType:r});const o=await a.arrayBuffer(),m=Buffer.from(o);await t.promises.writeFile(s,m)}async function K(e,s,c,a={}){const r=String(e),o=Date.now(),m=ne(c),u=[],f=[];if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});if(!s&&m.length===0)throw new h("file or skillUrls cannot both be empty",{field:"file|skillUrls"});if(s){if(!s.path)throw new h("Uploaded file has no valid path",{field:"file.path"});if(p.extname(s.originalname||s.filename||"").toLowerCase()!==".zip")throw new h("Only zip files are supported",{field:"file",originalName:s?.originalname})}const w=N(String(e),a);if(!t.existsSync(w))throw new h("Project does not exist",{field:"projectId"});const{skillsDir:l,agentTypes:d}=await se(w),D=p.join(E.UPLOAD_PROJECT_DIR,"temp");try{await t.promises.mkdir(l,{recursive:!0}),await t.promises.mkdir(D,{recursive:!0});const n=new Set;if(s){const k=p.join(D,`project_skill_push_${Date.now()}_${Math.round(Math.random()*1e6)}`);f.push(k),await t.promises.mkdir(k,{recursive:!0}),await _(s.path,k);const $=await le(k,"skills");if(!$)i(r,"WARN","skills directory not found in uploaded zip",{projectId:e,extractRoot:k});else{const F=(await t.promises.readdir($,{withFileTypes:!0})).filter(v=>v.isDirectory()&&!v.name.startsWith("."));for(const v of F){const T=p.join($,v.name),x=p.join(l,v.name);t.existsSync(x)&&await q(x),await H(T,x),n.add(v.name)}}}for(let k=0;k<m.length;k+=1){const $=m[k],S=p.join(D,`project_skill_url_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}.zip`);u.push(S);const F=p.join(D,`project_skill_url_extract_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}`);f.push(F),await t.promises.mkdir(F,{recursive:!0}),await ce($,S,r),await _(S,F);const T=(await t.promises.readdir(F,{withFileTypes:!0})).filter(g=>g.isDirectory()&&!g.name.startsWith(".")),B=T.find(g=>g.name==="skills")?(await t.promises.readdir(p.join(F,"skills"),{withFileTypes:!0})).filter(g=>g.isDirectory()&&!g.name.startsWith(".")).map(g=>({name:g.name,sourcePath:p.join(F,"skills",g.name)})):T.map(g=>({name:g.name,sourcePath:p.join(F,g.name)}));if(B.length===0){i(r,"WARN","No skill directory found after extracting skill url zip",{projectId:e,skillUrl:$,extractRoot:F});continue}for(const g of B){const z=p.join(l,g.name);t.existsSync(z)&&await q(z),await H(g.sourcePath,z),n.add(g.name)}}const y=Array.from(n),R=y.length>0?`Pushed ${y.length} skills: ${y.join(", ")}`:"No valid skill directories found in file or skillUrls",U=Date.now()-o;i(r,"INFO","Push skills to project workspace resources prepared",{projectId:e,updatedSkills:y,skillUrlsCount:m.length,hasFile:!!s,agentTypes:d,prepareElapsedMs:U});const I=await re(w,r);return i(r,"INFO","Push skills to project workspace completed",{projectId:e,updatedSkills:y,skillUrlsCount:m.length,hasFile:!!s,agentTypes:d,prepareElapsedMs:U,syncAgentsElapsedMs:I?.elapsedMs,elapsedMs:Date.now()-o}),{message:R,projectPath:w,updatedSkills:y}}catch(n){throw i(r,"ERROR","Push skills to project workspace failed",{projectId:e,error:n.message,elapsedMs:Date.now()-o}),n instanceof h||n instanceof b||n instanceof G||n instanceof P?n:new P(`Failed to push skills to project workspace: ${n.message}`,{projectId:e})}finally{const n=Date.now();for(const y of f)try{t.existsSync(y)&&await t.promises.rm(y,{recursive:!0,force:!0})}catch(R){i(r,"WARN","Failed to clean extracted skill temp dir",{extractRoot:y,error:R.message})}for(const y of u)try{t.existsSync(y)&&await t.promises.unlink(y)}catch(R){i(r,"WARN","Failed to clean downloaded skill zip",{zipPath:y,error:R.message})}try{s?.path&&t.existsSync(s.path)&&await t.promises.unlink(s.path)}catch(y){i(r,"WARN","Failed to clean uploaded skill zip",{tempZipPath:s?.path,error:y.message})}i(r,"INFO","Push skills to project workspace temp cleanup completed",{projectId:e,elapsedMs:Date.now()-n,totalElapsedMs:Date.now()-o})}}async function Q(e,s,c,a={}){const r=Date.now();if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});const o=null;try{if(s&&!isNaN(Number(s))){const l=Number(s);i(o,"INFO",`[delete-project] Stopping development server, PID: ${l}`,{projectId:e,pid:l});try{await W(c,e,l,{strict:!0}),i(o,"INFO","[delete-project] Development server stopped",{projectId:e})}catch(d){i(o,"WARN",`[delete-project] Failed to stop development server: ${d.message}`,{projectId:e,pid:l})}}const m=[p.join(E.UPLOAD_PROJECT_DIR,e),N(e,a),p.join(E.DIST_TARGET_DIR,e),p.join(E.LOG_BASE_DIR,e)],u=[],f=[];for(const l of m)if(t.existsSync(l))try{await t.promises.rm(l,{recursive:!0,force:!0}),u.push(l),i(o,"INFO",`[delete-project] Directory deleted successfully: ${l}`,{projectId:e})}catch(d){f.push({path:l,error:d.message}),i(o,"ERROR",`[delete-project] Directory deleted failed: ${l}`,{projectId:e,error:d.message})}else i(o,"INFO",`[delete-project] Directory does not exist, skipping deletion: ${l}`,{projectId:e});const w={success:!0,message:`Project ${e} deleted successfully`,projectId:e,deletedDirectories:u,failedDirectories:f};return f.length>0&&(w.message+=`, but ${f.length} directories deleted failed`,i(o,"WARN","[delete-project] Some directories deleted failed",{projectId:e,failedDirs:f})),i(o,"INFO",`[delete-project] Project deleted successfully: ${e}`,{projectId:e,elapsedMs:Date.now()-r}),w}catch(m){throw i(o,"ERROR",`[delete-project] Failed to delete project: ${m.message}`,{projectId:e,originalError:m.message,elapsedMs:Date.now()-r}),m.isOperational?m:new P(`Failed to delete project: ${m.message}`,{projectId:e,originalError:m.message})}}async function Y(e,s,c,a,r={}){const o=Date.now();if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});if(s==null)throw new h("codeVersion cannot be empty",{field:"codeVersion"});const m=Number(s);if(!Number.isFinite(m))throw new h("codeVersion must be a number",{field:"codeVersion"});const u=N(e,r);if(!t.existsSync(u))throw new A("Project does not exist",{projectId:e});const f=p.join(E.UPLOAD_PROJECT_DIR,e),w=`${e}-v${m}.zip`,l=p.join(f,w);if(c!=="LATEST"){if(t.existsSync(l))return i(e,"INFO",`Using existing export file: ${l}`,{projectId:e,zipPath:l}),{success:!0,projectId:e,zipPath:l};throw new A(`Specified version zip file does not exist: ${l}`,{projectId:e,zipPath:l})}const d=p.join(u,"cpage_config.json");let D=!1;try{if(a)try{const y=JSON.stringify(a,null,2);await t.promises.writeFile(d,y,"utf8"),D=!0,i(e,"INFO",`Configuration file created successfully: ${d}`,{projectId:e,configFilePath:d})}catch(y){throw i(e,"ERROR",`Failed to create configuration file: ${y.message}`,{projectId:e,error:y.message}),new b("Failed to create configuration file",{projectId:e,configFilePath:d,originalError:y.message})}i(e,"DEBUG","Start executing export and packaging",{projectId:e,codeVersion:s});const n=await O(e,s,r);return i(e,"INFO",`Project exported successfully: ${n}`,{projectId:e,zipPath:n,elapsedMs:Date.now()-o}),{success:!0,projectId:e,zipPath:n}}catch(n){throw i(e,"ERROR",`Failed to export project: ${n?.message}`,{projectId:e,elapsedMs:Date.now()-o}),n.isOperational?n:new P("Failed to export project",{projectId:e,originalError:n&&n.message?C(n.message):n&&n.message})}finally{if(D&&t.existsSync(d))try{await t.promises.unlink(d),i(e,"INFO",`Temporary configuration file deleted successfully: ${d}`,{projectId:e,configFilePath:d})}catch(n){i(e,"WARN",`Failed to delete temporary configuration file: ${n.message}`,{projectId:e,error:n.message})}}}async function j(e,s,c={}){const a=Date.now();if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});if(s==null)throw new h("codeVersion cannot be empty",{field:"codeVersion"});try{const r=await O(e,s,c);return i(e,"INFO",`Current version backed up successfully: ${r}`,{projectId:e,zipPath:r,elapsedMs:Date.now()-a}),{success:!0,projectId:e,zipPath:r}}catch(r){throw i(e,"ERROR",`Failed to backup current version: ${r?.message}`,{projectId:e,elapsedMs:Date.now()-a}),r.isOperational?r:new P("Failed to backup current version",{projectId:e,originalError:r&&r.message?C(r.message):r&&r.message})}}export{Z as createProject,V as uploadProject,j as backupCurrentVersion,Y as exportProject,O as backupProjectOfVersion,M as cleanupProjectDirectory,X as handleFileUpload,K as pushSkillsToWorkspace,Q as deleteProject};export default{createProject:Z,uploadProject:V,backupCurrentVersion:j,exportProject:Y,backupProjectOfVersion:O,cleanupProjectDirectory:M,handleFileUpload:X,pushSkillsToWorkspace:K,deleteProject:Q};
1
+ import{log as i}from"../utils/log/logUtils.js";import E from"../appConfig/index.js";import p from"path";import t from"fs";import{extractZip as b}from"../utils/common/zipUtils.js";import"../utils/build/startDevUtils.js";import"../utils/build/restartDevUtils.js";import{stopDevServer as G}from"../utils/build/stopDevUtils.js";import{ValidationError as g,BusinessError as C,SystemError as R,FileError as A,ResourceError as z}from"../utils/error/errorHandler.js";import{sanitizeSensitivePaths as L}from"../utils/common/sensitiveUtils.js";import{removeNodeModules as te}from"../utils/buildDependency/dependencyManager.js";import{backupProjectToZip as ie,copyDirectoryFiltered as se}from"../utils/project/backupUtils.js";import{createPnpmNpmrc as J}from"../utils/common/npmrcUtils.js";import{copyNodeModulesFromCache as Z}from"../utils/common/templateCacheUtils.js";import{resolveProjectPath as O}from"../utils/common/projectPathUtils.js";import{ensurePrimaryAgentDirs as re,syncAgents as ae}from"../utils/common/AgentWorkspaceUtils.js";async function V(e,s="react",l={}){const a=Date.now();if(!e)throw new g("Project ID cannot be empty",{field:"projectId"});const r=["react","vue3"];if(!r.includes(s))throw new g("Template type is invalid, only supports react or vue3",{field:"templateType",templateType:s,allowedValues:r});const c={react:E.INIT_PROJECT_NAME_REACT||"react-vite-template",vue3:E.INIT_PROJECT_NAME_VUE3||"vue3-vite-template"}[s],m=O(e,l);if(t.existsSync(m))throw new C(`Project directory ${e} already exists`,{projectId:e,projectPath:m});try{t.mkdirSync(m,{recursive:!0}),i(e,"INFO",`Project directory created successfully: ${m}`,{projectId:e});const u=E.INIT_PROJECT_DIR,w=p.join(u,`${c}.zip`),n=p.join(u,c);if(i(e,"DEBUG","Start checking template directory",{templateDir:n,templateZipPath:w}),!t.existsSync(n)){if(!t.existsSync(w))throw i(e,"ERROR",`Initialization template does not exist: ${w}`,{projectId:e,templateZipPath:w}),new z("Initialization template does not exist",{});if(i(e,"INFO",`Template directory does not exist, starting to unzip template: ${w}`,{projectId:e,templateZipPath:w}),await b(w,n),i(e,"INFO","Template unzip completed",{projectId:e}),!t.existsSync(n))throw new R("Template unzip directory still does not exist",{})}i(e,"DEBUG","Start copying template content to project directory",{templateDir:n,projectPath:m});const h=await t.promises.readdir(n,{withFileTypes:!0});for(const f of h){const y=p.join(n,f.name),d=p.join(m,f.name);f.isDirectory()?(await t.promises.mkdir(d,{recursive:!0}),await se(y,d)):f.isFile()&&(await t.promises.mkdir(p.dirname(d),{recursive:!0}),await t.promises.copyFile(y,d))}i(e,"DEBUG","Try copying node_modules from template cache",{projectPath:m});const k=await Z(m,e);if(k.cached&&i(e,"INFO",`node_modules copied from cache: ${k.templateType}`,{elapsed:k.elapsed}),i(e,"DEBUG","Start creating .npmrc configuration file",{projectPath:m}),await J(m,e),i(e,"INFO",`Project ${e} initialized successfully`,{projectId:e,elapsedMs:Date.now()-a}),E.GIT_ENABLED)try{const f=await import("./gitService.js");await f.init({workspaceType:"pageApp",projectId:e,isolationContext:l}),await f.commit({workspaceType:"pageApp",projectId:e,isolationContext:l,message:`init project: ${e}`})}catch(f){i(e,"WARN","Git init/commit failed, skipping",{error:f.message})}return{success:!0,message:`Project ${e} created successfully`,projectPath:m}}catch(u){throw i(e,"ERROR",`Project ${e} initialization failed: ${u.message}`,{projectId:e,elapsedMs:Date.now()-a}),new R(`Project ${e} initialization failed: ${u.message}`,{projectId:e,projectPath:m,originalError:u.message})}}async function oe(e){const s=await t.promises.readdir(e,{withFileTypes:!0}),l=E.TOP_LEVEL_NOISE_PATTERNS,a=s.filter(r=>{const o=r.name;return o.startsWith(".")?!1:!l.some(c=>c.endsWith("*")?o.startsWith(c.slice(0,-1)):o===c)});if(a.length===1&&a[0].isDirectory()){const r=p.join(e,a[0].name),o=p.join(e,`temp_${Date.now()}`);try{await t.promises.rename(r,o)}catch(m){if(m.code==="EXDEV")await t.promises.cp(r,o,{recursive:!0}),await t.promises.rm(r,{recursive:!0,force:!0});else throw m}const c=await t.promises.readdir(o);for(const m of c){const u=p.join(o,m),w=p.join(e,m);try{await t.promises.rename(u,w)}catch(n){if(n.code==="EXDEV")await t.promises.cp(u,w,{recursive:!0}),await t.promises.rm(u,{recursive:!0,force:!0});else throw n}}await t.promises.rm(o,{recursive:!0,force:!0})}}async function U(e,s={}){if(!e)throw new g("Project ID cannot be empty",{field:"projectId"});const l=O(e,s);if(t.existsSync(l))try{i(e,"INFO",`Start cleaning project directory: ${l}`,{projectId:e}),await t.promises.rm(l,{recursive:!0,force:!0}),i(e,"INFO",`Project directory cleaned up: ${l}`,{projectId:e})}catch(a){throw i(e,"ERROR",`Failed to clean project directory: ${a.message}`,{projectId:e,projectPath:l,originalError:a.message}),new R(`Failed to clean project directory: ${a.message}`,{projectId:e,projectPath:l,originalError:a.message})}else i(e,"INFO",`Project directory does not exist, no need to clean: ${l}`,{projectId:e})}function ne(e){if(!t.existsSync(e))return!0;try{return t.readdirSync(e).filter(a=>!a.startsWith(".")&&a!=="node_modules").length===0}catch(s){const l=p.basename(e);return i(l,"ERROR",`Failed to check if directory is empty: ${s.message}`,{dirPath:e}),!0}}async function X(e,s,l,a,r,o,c={}){const m=Date.now(),u=O(e,c);try{if(ne(u))i(e,"INFO","Project directory is empty, directly deploying new project",{projectId:e});else{i(e,"INFO","Project directory is not empty, starting to backup current version",{projectId:e});const n=parseInt(a)-1,h=p.join(E.UPLOAD_PROJECT_DIR,e),k=p.join(h,`${e}-v${n}.zip`);if(t.existsSync(k))i(e,"INFO",`Backup file already exists, skipping backup: ${k}`,{projectId:e});else try{E.GIT_ENABLED||await T(e,n,c),i(e,"INFO",`Current version backed up: ${k}`,{projectId:e})}catch(f){throw i(e,"ERROR",`Failed to backup current version: ${f.message}`,{projectId:e}),new R(`Failed to backup current version: ${f.message}`,{projectId:e,originalError:f.message})}if(r&&!isNaN(Number(r))){const f=Number(r);i(e,"INFO",`Stopping old version dev server, PID: ${f}`,{projectId:e});try{await G(l,e,f,{strict:!0}),i(e,"INFO","Old version dev server stopped",{projectId:e})}catch(y){i(e,"WARN",`Failed to stop old version dev server: ${y.message}`,{projectId:e,pid:f})}}t.existsSync(u)&&(i(e,"INFO",`Cleaning project directory: ${u}`,{projectId:e}),await t.promises.rm(u,{recursive:!0,force:!0}))}if(t.mkdirSync(u,{recursive:!0}),i(e,"INFO",`Project directory created successfully: ${u}`,{projectId:e}),i(e,"DEBUG","Start extracting zip file to project directory",{projectId:e,zipFilePath:s}),await b(s,u),i(e,"DEBUG","Zip file extracted successfully",{projectId:e}),i(e,"DEBUG","Check and remove top level folder",{projectId:e}),await oe(u),i(e,"DEBUG","Check and remove node_modules folder",{projectId:e}),await te(u),i(e,"DEBUG","Try copying node_modules from template cache",{projectId:e}),await Z(u,e),i(e,"DEBUG","Start creating .npmrc configuration file",{projectId:e}),await J(u,e),E.GIT_ENABLED)try{const n=await import("./gitService.js");await n.init({workspaceType:"pageApp",projectId:e,isolationContext:c}),await n.commit({workspaceType:"pageApp",projectId:e,isolationContext:c,message:`upload project v${a}`})}catch(n){i(e,"WARN","Git commit failed after upload, skipping",{error:n.message})}return i(e,"INFO",`Project ${e} uploaded successfully`,{projectId:e,codeVersion:a,elapsedMs:Date.now()-m}),{success:!0,message:`Project ${e} uploaded successfully`,projectId:e,codeVersion:a}}catch(w){i(e,"ERROR",`Failed to upload project: ${w.message}`,{projectId:e,elapsedMs:Date.now()-m});try{await U(e,c),i(e,"INFO","Failed to upload project, project directory cleaned up",{projectId:e})}catch(n){i(e,"ERROR",`Failed to clean project directory: ${n.message}`,{projectId:e,originalError:n.message})}throw w.isOperational?w:new R(`Failed to upload project: ${w.message}`,{projectId:e,projectPath:u,zipFilePath:s,originalError:w.message})}}async function T(e,s,l={}){if(!e)throw new g("Project ID cannot be empty",{field:"projectId"});if(s==null)throw new g("codeVersion cannot be empty",{field:"codeVersion"});const a=Number(s);if(!Number.isFinite(a))throw new g("codeVersion must be a number",{field:"codeVersion"});const r=O(e,l);if(!t.existsSync(r))throw new z("Project does not exist",{projectId:e});const o=p.join(E.UPLOAD_PROJECT_DIR,e);t.existsSync(o)||t.mkdirSync(o,{recursive:!0});const c=`${e}-v${a}.zip`,m=p.join(o,c);return i(e,"DEBUG","Start backing up project to zip",{projectId:e,versionNum:a,outZipPath:m}),await ie(e,r,m)}async function q(e,s,l){if(!e)throw new g("Project ID cannot be empty",{field:"projectId"});if(!s)throw new g("codeVersion cannot be empty",{field:"codeVersion"});if(!l)throw new g("Please upload a zip file",{field:"zipFile"});const a=p.join(E.UPLOAD_PROJECT_DIR,e);t.existsSync(a)||t.mkdirSync(a,{recursive:!0});const r=l.path,o=p.join(a,`${e}-v${s}.zip`);try{try{t.renameSync(r,o)}catch(c){if(c.code==="EXDEV")t.copyFileSync(r,o),t.unlinkSync(r);else throw c}return i(e,"INFO","File saved successfully",{projectId:e,codeVersion:s,filePath:o}),{success:!0,filePath:o}}catch(c){if(i(e,"ERROR","Failed to move file",{projectId:e,codeVersion:s,error:c.message}),t.existsSync(r))try{t.unlinkSync(r)}catch(m){i(e,"ERROR","Failed to clean temporary file",{projectId:e,error:m.message})}throw new R("Failed to save file",{projectId:e,codeVersion:s,originalError:c.message})}}function le(e){if(!e)return[];if(Array.isArray(e))return e.map(s=>typeof s=="string"?s.trim():"").filter(Boolean);if(typeof e=="string"){const s=e.trim();if(!s)return[];try{const l=JSON.parse(s);if(Array.isArray(l))return l.map(a=>typeof a=="string"?a.trim():"").filter(Boolean)}catch{}return[s]}return[]}async function H(e){t.existsSync(e)&&await t.promises.rm(e,{recursive:!0,force:!0})}async function K(e,s){try{await t.promises.rename(e,s)}catch(l){if(l.code==="EXDEV"){async function a(r,o){if((await t.promises.lstat(r)).isDirectory()){await t.promises.mkdir(o,{recursive:!0});const m=await t.promises.readdir(r);for(const u of m)await a(p.join(r,u),p.join(o,u))}else await t.promises.copyFile(r,o)}await a(e,s),await t.promises.rm(e,{recursive:!0,force:!0})}else throw l}}async function ce(e,s){const l=p.join(e,s);if(t.existsSync(l)&&(await t.promises.lstat(l)).isDirectory())return l;const a=await t.promises.readdir(e,{withFileTypes:!0});for(const r of a){if(!r.isDirectory())continue;const o=p.join(e,r.name,s);if(t.existsSync(o)&&(await t.promises.lstat(o)).isDirectory())return o}return null}async function me(e,s,l){let a;try{a=await fetch(e)}catch(m){throw new A(`Failed to download skill zip from url: ${e}`,{url:e,reason:m.message})}if(!a.ok)throw new A(`Failed to download skill zip from url: ${e}`,{url:e,status:a.status,statusText:a.statusText});const r=a.headers.get("content-type")||"";r&&!r.includes("zip")&&!r.includes("octet-stream")&&i(l,"WARN","Downloaded skill url content-type is not typical zip",{url:e,contentType:r});const o=await a.arrayBuffer(),c=Buffer.from(o);await t.promises.writeFile(s,c)}async function Q(e,s,l,a={}){const r=String(e),o=Date.now(),c=le(l),m=[],u=[],w=[];if(!e)throw new g("Project ID cannot be empty",{field:"projectId"});if(!s&&c.length===0)throw new g("file or skillUrls cannot both be empty",{field:"file|skillUrls"});if(s){if(!s.path)throw new g("Uploaded file has no valid path",{field:"file.path"});if(p.extname(s.originalname||s.filename||"").toLowerCase()!==".zip")throw new g("Only zip files are supported",{field:"file",originalName:s?.originalname})}const n=O(String(e),a);if(!t.existsSync(n))throw new g("Project does not exist",{field:"projectId"});const{skillsDir:h,agentTypes:k}=await re(n),f=p.join(E.UPLOAD_PROJECT_DIR,"temp");try{await t.promises.mkdir(h,{recursive:!0}),await t.promises.mkdir(f,{recursive:!0});const y=new Set;if(s){const P=p.join(f,`project_skill_push_${Date.now()}_${Math.round(Math.random()*1e6)}`);u.push(P),await t.promises.mkdir(P,{recursive:!0}),await b(s.path,P);const v=await ce(P,"skills");if(!v)i(r,"WARN","skills directory not found in uploaded zip",{projectId:e,extractRoot:P});else{const F=(await t.promises.readdir(v,{withFileTypes:!0})).filter($=>$.isDirectory()&&!$.name.startsWith("."));for(const $ of F){const x=p.join(v,$.name),_=p.join(h,$.name);t.existsSync(_)&&await H(_),await K(x,_),y.add($.name)}}}for(let P=0;P<c.length;P+=1){const v=c[P];try{const N=p.join(f,`project_skill_url_${Date.now()}_${P}_${Math.round(Math.random()*1e6)}.zip`);m.push(N);const F=p.join(f,`project_skill_url_extract_${Date.now()}_${P}_${Math.round(Math.random()*1e6)}`);u.push(F),await t.promises.mkdir(F,{recursive:!0}),await me(v,N,r),await b(N,F);const x=(await t.promises.readdir(F,{withFileTypes:!0})).filter(D=>D.isDirectory()&&!D.name.startsWith(".")),W=x.find(D=>D.name==="skills")?(await t.promises.readdir(p.join(F,"skills"),{withFileTypes:!0})).filter(D=>D.isDirectory()&&!D.name.startsWith(".")).map(D=>({name:D.name,sourcePath:p.join(F,"skills",D.name)})):x.map(D=>({name:D.name,sourcePath:p.join(F,D.name)}));if(W.length===0){i(r,"WARN","No skill directory found after extracting skill url zip",{projectId:e,skillUrl:v,extractRoot:F}),w.push({name:null,url:v,reason:"No skill directory found in downloaded zip"});continue}for(const D of W){const M=p.join(h,D.name);t.existsSync(M)&&await H(M),await K(D.sourcePath,M),y.add(D.name)}}catch(N){i(r,"ERROR","Download/extract skill zip failed, skip this skill",{projectId:e,skillUrl:v,error:N.message}),w.push({name:null,url:v,reason:`Download/extract skill zip failed: ${N.message}`})}}const d=Array.from(y),S=d.length>0?`Pushed ${d.length} skills: ${d.join(", ")}`:"No valid skill directories found in file or skillUrls",B=Date.now()-o;i(r,"INFO","Push skills to project workspace resources prepared",{projectId:e,updatedSkills:d,skillUrlsCount:c.length,hasFile:!!s,agentTypes:k,prepareElapsedMs:B});const ee=await ae(n,r);return i(r,"INFO","Push skills to project workspace completed",{projectId:e,updatedSkills:d,skillUrlsCount:c.length,hasFile:!!s,agentTypes:k,prepareElapsedMs:B,syncAgentsElapsedMs:ee?.elapsedMs,elapsedMs:Date.now()-o}),{message:S,projectPath:n,updatedSkills:d,failedSkills:w}}catch(y){throw i(r,"ERROR","Push skills to project workspace failed",{projectId:e,error:y.message,elapsedMs:Date.now()-o}),y instanceof g||y instanceof A||y instanceof C||y instanceof R?y:new R(`Failed to push skills to project workspace: ${y.message}`,{projectId:e})}finally{const y=Date.now();for(const d of u)try{t.existsSync(d)&&await t.promises.rm(d,{recursive:!0,force:!0})}catch(S){i(r,"WARN","Failed to clean extracted skill temp dir",{extractRoot:d,error:S.message})}for(const d of m)try{t.existsSync(d)&&await t.promises.unlink(d)}catch(S){i(r,"WARN","Failed to clean downloaded skill zip",{zipPath:d,error:S.message})}try{s?.path&&t.existsSync(s.path)&&await t.promises.unlink(s.path)}catch(d){i(r,"WARN","Failed to clean uploaded skill zip",{tempZipPath:s?.path,error:d.message})}i(r,"INFO","Push skills to project workspace temp cleanup completed",{projectId:e,elapsedMs:Date.now()-y,totalElapsedMs:Date.now()-o})}}async function Y(e,s,l,a={}){const r=Date.now();if(!e)throw new g("Project ID cannot be empty",{field:"projectId"});const o=null;try{if(s&&!isNaN(Number(s))){const n=Number(s);i(o,"INFO",`[delete-project] Stopping development server, PID: ${n}`,{projectId:e,pid:n});try{await G(l,e,n,{strict:!0}),i(o,"INFO","[delete-project] Development server stopped",{projectId:e})}catch(h){i(o,"WARN",`[delete-project] Failed to stop development server: ${h.message}`,{projectId:e,pid:n})}}const c=[p.join(E.UPLOAD_PROJECT_DIR,e),O(e,a),p.join(E.DIST_TARGET_DIR,e),p.join(E.LOG_BASE_DIR,e)],m=[],u=[];for(const n of c)if(t.existsSync(n))try{await t.promises.rm(n,{recursive:!0,force:!0}),m.push(n),i(o,"INFO",`[delete-project] Directory deleted successfully: ${n}`,{projectId:e})}catch(h){u.push({path:n,error:h.message}),i(o,"ERROR",`[delete-project] Directory deleted failed: ${n}`,{projectId:e,error:h.message})}else i(o,"INFO",`[delete-project] Directory does not exist, skipping deletion: ${n}`,{projectId:e});const w={success:!0,message:`Project ${e} deleted successfully`,projectId:e,deletedDirectories:m,failedDirectories:u};return u.length>0&&(w.message+=`, but ${u.length} directories deleted failed`,i(o,"WARN","[delete-project] Some directories deleted failed",{projectId:e,failedDirs:u})),i(o,"INFO",`[delete-project] Project deleted successfully: ${e}`,{projectId:e,elapsedMs:Date.now()-r}),w}catch(c){throw i(o,"ERROR",`[delete-project] Failed to delete project: ${c.message}`,{projectId:e,originalError:c.message,elapsedMs:Date.now()-r}),c.isOperational?c:new R(`Failed to delete project: ${c.message}`,{projectId:e,originalError:c.message})}}async function j(e,s,l,a,r={}){const o=Date.now();if(!e)throw new g("Project ID cannot be empty",{field:"projectId"});if(s==null)throw new g("codeVersion cannot be empty",{field:"codeVersion"});const c=Number(s);if(!Number.isFinite(c))throw new g("codeVersion must be a number",{field:"codeVersion"});const m=O(e,r);if(!t.existsSync(m))throw new z("Project does not exist",{projectId:e});const u=p.join(E.UPLOAD_PROJECT_DIR,e),w=`${e}-v${c}.zip`,n=p.join(u,w);if(l!=="LATEST"){if(t.existsSync(n))return i(e,"INFO",`Using existing export file: ${n}`,{projectId:e,zipPath:n}),{success:!0,projectId:e,zipPath:n};throw new z(`Specified version zip file does not exist: ${n}`,{projectId:e,zipPath:n})}const h=p.join(m,"cpage_config.json");let k=!1;try{if(a)try{const y=JSON.stringify(a,null,2);await t.promises.writeFile(h,y,"utf8"),k=!0,i(e,"INFO",`Configuration file created successfully: ${h}`,{projectId:e,configFilePath:h})}catch(y){throw i(e,"ERROR",`Failed to create configuration file: ${y.message}`,{projectId:e,error:y.message}),new A("Failed to create configuration file",{projectId:e,configFilePath:h,originalError:y.message})}i(e,"DEBUG","Start executing export and packaging",{projectId:e,codeVersion:s});const f=await T(e,s,r);return i(e,"INFO",`Project exported successfully: ${f}`,{projectId:e,zipPath:f,elapsedMs:Date.now()-o}),{success:!0,projectId:e,zipPath:f}}catch(f){throw i(e,"ERROR",`Failed to export project: ${f?.message}`,{projectId:e,elapsedMs:Date.now()-o}),f.isOperational?f:new R("Failed to export project",{projectId:e,originalError:f&&f.message?L(f.message):f&&f.message})}finally{if(k&&t.existsSync(h))try{await t.promises.unlink(h),i(e,"INFO",`Temporary configuration file deleted successfully: ${h}`,{projectId:e,configFilePath:h})}catch(f){i(e,"WARN",`Failed to delete temporary configuration file: ${f.message}`,{projectId:e,error:f.message})}}}async function I(e,s,l={}){const a=Date.now();if(!e)throw new g("Project ID cannot be empty",{field:"projectId"});if(s==null)throw new g("codeVersion cannot be empty",{field:"codeVersion"});try{const r=await T(e,s,l);return i(e,"INFO",`Current version backed up successfully: ${r}`,{projectId:e,zipPath:r,elapsedMs:Date.now()-a}),{success:!0,projectId:e,zipPath:r}}catch(r){throw i(e,"ERROR",`Failed to backup current version: ${r?.message}`,{projectId:e,elapsedMs:Date.now()-a}),r.isOperational?r:new R("Failed to backup current version",{projectId:e,originalError:r&&r.message?L(r.message):r&&r.message})}}export{V as createProject,X as uploadProject,I as backupCurrentVersion,j as exportProject,T as backupProjectOfVersion,U as cleanupProjectDirectory,q as handleFileUpload,Q as pushSkillsToWorkspace,Y as deleteProject};export default{createProject:V,uploadProject:X,backupCurrentVersion:I,exportProject:j,backupProjectOfVersion:T,cleanupProjectDirectory:U,handleFileUpload:q,pushSkillsToWorkspace:Q,deleteProject:Y};
@@ -1,2 +1,2 @@
1
- import c from"fs";import h from"path";import fe from"archiver";import{lookup as Ne}from"mime-types";import P from"../../appConfig/index.js";import{log as f}from"../log/logUtils.js";import{ValidationError as E,SystemError as T,FileError as ke}from"../error/errorHandler.js";import{extractZip as xe}from"../common/zipUtils.js";import{moveDirectory as Ce,movePath as oe}from"../common/fileSystemUtils.js";import{resolveLogDir as Pe,resolveWorkspaceDir as O,resolveWorkspaceRoot as me}from"./workspaceContext.js";const X=new Set([".git",".agents",".claude",".codex",".opencode",".tmp",".logs"]),_e=100*1024*1024,H=P.DOWNLOAD_MAX_FILE_SIZE_BYTES||_e;function q(e){return h.sep==="\\"?e.replace(/\\/g,"/"):e}function de(e,r){return q(h.relative(e,r))}function pe(e,r){return e?`${e}/${r}`:r}function Z(e){return e!=null&&e.limit!=null&&e.pushed>=e.limit}function K(e){e!=null&&(e.pushed+=1)}async function he(e,r,t,i,o,a={}){const{typeFilter:n=null,limitState:m=null,levelsLeft:u=1}=a,g=[],y=await c.promises.readdir(e,{withFileTypes:!0});y.sort((s,w)=>s.isDirectory()&&!w.isDirectory()?-1:!s.isDirectory()&&w.isDirectory()?1:s.name.toLowerCase().localeCompare(w.name.toLowerCase()));const l=de(r,e);for(const s of y){if(Z(m))break;if(s.name.startsWith(".")&&s.name!==".gitignore"||(P.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(s.name)||s.isDirectory()&&P.TRAVERSE_EXCLUDE_DIRS.includes(s.name))continue;const d=pe(l,s.name);if(s.isDirectory()){if(n!=="file"&&(g.push({name:d,isDir:!0}),K(m)),u>1&&!Z(m))try{const p=await he(h.join(e,s.name),r,t,i,o,{typeFilter:n,limitState:m,levelsLeft:u-1});g.push(...p)}catch(p){f(t,"WARN",`Depth descent failed, skip subtree: ${d}`,{error:p.message})}continue}if(n!=="dir"&&!(s.isSymbolicLink()&&await $(r,h.join(e,s.name))))try{g.push({name:d,isDir:!1,fileProxyUrl:Q(i,d,o),isLink:s.isSymbolicLink()}),K(m)}catch(p){f(t,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${e}/${s.name}`,{error:p.message})}}return g}async function we(e,r,t,i,o="",a={}){const{typeFilter:n=null,limitState:m=null}=a,u=a.rootDir||e,g=[];let y=0;const l=await c.promises.readdir(e,{withFileTypes:!0});l.sort((s,w)=>s.isDirectory()&&!w.isDirectory()?-1:!s.isDirectory()&&w.isDirectory()?1:s.name.toLowerCase().localeCompare(w.name.toLowerCase()));for(const s of l){if(Z(m))break;const w=h.join(e,s.name);if(s.name.startsWith(".")&&s.name!==".gitignore"||(P.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(s.name)||s.isDirectory()&&P.TRAVERSE_EXCLUDE_DIRS.includes(s.name)||s.isSymbolicLink()&&await $(u,w))continue;const p=pe(o,s.name);if(y+=1,s.isDirectory()){const S=await we(w,r,t,i,p,a);S.naturalCount===0?n!=="file"&&!Z(m)&&(g.push({name:p,isDir:!0}),K(m)):g.push(...S.entries)}else{if(n==="dir")continue;try{g.push({name:p,isDir:!1,fileProxyUrl:Q(t,p,i),isLink:s.isSymbolicLink()}),K(m)}catch(S){f(r,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${w}`,{error:S.message})}}}return{entries:g,naturalCount:y}}const Y=process.platform==="win32"?/^[\/\\]+/:/^\/+/;async function $(e,r){try{const t=await c.promises.realpath(e),i=await c.promises.realpath(r);return i!==t&&!i.startsWith(t+h.sep)}catch{return!1}}function ge(e,r){const t=r==null?"":String(r),i=t.trim();if(!i||i==="."||i==="/")return h.resolve(e);const o=h.normalize(t).replace(Y,"");if(!o||o===".")return h.resolve(e);if(h.isAbsolute(t)||o.split(h.sep).includes(".."))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:r});const a=h.resolve(e),n=h.resolve(e,o);if(n!==a&&!n.startsWith(a+h.sep))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:r});return n}function J(e,r){const t=r==null?"":String(r);if(!t.trim())return null;const i=h.resolve(e);let o;if(h.isAbsolute(t))o=h.resolve(t);else{const n=h.normalize(t).replace(Y,"");if(!n||n==="."||n.split(h.sep).includes(".."))return null;o=h.resolve(e,n)}if(o!==i&&!o.startsWith(i+h.sep))return null;const a=q(h.relative(i,o));return!a||a.startsWith("..")?null:{absPath:o,name:a}}function Q(e,r,t){if(!e||!r)return null;const i=r.split("/").map(a=>encodeURIComponent(a)).join("/");let o=`${e}/${i}`;return t&&(o+=`?customTargetDir=${encodeURIComponent(t)}`),o}async function Le(e,r,t,i,o,a=null){if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!r)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});if(!t||!String(t).trim())throw new E("filePath \u4E0D\u80FD\u4E3A\u7A7A",{field:"filePath"});const n=String(e),m=String(r),u=o&&o.trim()?o.trim():null,g=u||O(a,n,m);if(!c.existsSync(g))return{exists:!1};let y=J(g,t);if(!y&&String(t).startsWith("/")){const l=String(t).replace(Y,"");l&&(y=J(g,l))}if(!y)return{exists:!1};try{if(await $(g,y.absPath))return{exists:!1};if(!(await c.promises.stat(y.absPath)).isFile())return{exists:!1}}catch{return{exists:!1}}return{exists:!0,name:y.name,fileProxyUrl:Q(i,y.name,u)}}const Ae=100,ze=1e3,Te=8,ye=1e3;function Oe(e){return h.extname(e).replace(/^\./,"").toLowerCase()||""}async function $e(e,r,t){const i=new Array(e.length),o=Math.max(1,Math.min(r,e.length));let a=0;return await Promise.all(Array.from({length:o},async()=>{for(;;){const n=a++;if(n>=e.length)return;i[n]=await t(e[n],n)}})),i}function Me(e){return!(e.startsWith(".")&&e!==".gitignore"||(P.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(e)||P.TRAVERSE_EXCLUDE_DIRS.includes(e))}async function We(e){const r=await c.promises.opendir(e);let t=0;for await(const i of r)if(Me(i.name)&&(t+=1,t>=ye))return ye;return t}function ae(e,r){return{path:e,isDir:null,isLink:null,size:null,mtimeMs:null,extension:null,mimeType:null,linkTarget:null,childCount:null,...r?{error:r}:{}}}async function Ue(e,r,t){const i=r==null?"":String(r);let o=i.trim()?J(e,i):null;if(!o&&i.startsWith("/")){const a=i.replace(Y,"");a&&(o=J(e,a))}if(!o||await $(e,o.absPath))return ae(i,"illegal path");try{const a=await c.promises.lstat(o.absPath),n=a.isDirectory(),m=a.isSymbolicLink(),u={path:i,isDir:n,isLink:m,size:n?null:a.size,mtimeMs:a.mtimeMs,extension:n?null:Oe(i),mimeType:n?null:Ne(i)||"application/octet-stream",linkTarget:null,childCount:null};if(m)try{u.linkTarget=await c.promises.readlink(o.absPath)}catch(g){f(t,"WARN","Readlink failed, linkTarget set to null",{filePath:i,error:g.message})}if(n)try{u.childCount=await We(o.absPath)}catch(g){f(t,"WARN","Count directory entries failed, childCount set to null",{filePath:i,error:g.message})}return u}catch(a){return ae(i,a.code||a.message)}}async function je(e,r,t,i,o=null,a=null){const n=Date.now(),m=`computer:${e}:${r}`;if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!r)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});if(!Array.isArray(t)||t.length===0)throw new E("filePaths \u5FC5\u987B\u4E3A\u975E\u7A7A\u6570\u7EC4",{field:"filePaths"});const u=Number.parseInt(a,10),g=Number.isFinite(u)&&u>0?Math.min(u,ze):Ae;if(t.length>g)throw new E(`filePaths \u5355\u6B21\u6700\u591A ${g} \u6761`,{field:"filePaths",count:t.length});const y=String(e),l=String(r),w=(i==null?"":String(i).trim())||null,d=w||O(o,y,l);if(!c.existsSync(d))return f(m,"INFO","Target directory does not exist, returning empty metas",{targetDir:d,userId:y,cId:l}),{metas:[]};const p=await $e(t,Te,S=>Ue(d,S,m));return f(m,"INFO","File metadata queried",{targetDir:d,count:p.length,failed:p.filter(S=>S.error).length,customTargetDir:w,userId:y,cId:l,elapsedMs:Date.now()-n}),{metas:p}}async function Ee(e,r,t,i,o=""){const a=await c.promises.readdir(h.join(e,o),{withFileTypes:!0});let n=0;for(const m of a){const u=o?h.join(o,m.name):m.name,g=u.split(h.sep).filter(Boolean);if(g.some(w=>w.startsWith(".")))continue;const y=g[g.length-1];if(r.includes(y)||g.some(w=>t.includes(w)))continue;const l=h.join(e,u);let s;try{s=await c.promises.lstat(l)}catch(w){f(i,"WARN","Error occurred when getting file stats, skipping",{filePath:u.replace(/\\/g,"/"),error:w.message});continue}s.isSymbolicLink()||s.nlink>1||(s.isDirectory()?n+=await Ee(e,r,t,i,u):s.isFile()&&(n+=s.size))}return n}async function Be(e,r,t,i,o,a,n,m,u,g=null){const y=Date.now(),l=`computer:${e}:${r}`,s=!(a===!1||a==="false");if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!r)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const w=m==null?"":String(m).trim().toLowerCase();let d=null;if(w&&w!=="all")if(w==="file")d="file";else if(w==="dir"||w==="directory")d="dir";else throw new E("type \u4EC5\u652F\u6301 file/dir/all",{field:"type",value:m});const p=D=>/^\d+$/.test(String(D))?parseInt(String(D),10):NaN;let S=null;if(u!=null&&u!==""){const D=p(u);if(!Number.isInteger(D)||D<0)throw new E("limit \u5FC5\u987B\u4E3A\u975E\u8D1F\u6574\u6570",{field:"limit",value:u});S=D}let F=null;if(!s&&n!=null&&n!==""){const D=p(n);if(!Number.isInteger(D)||D<1||D>10)throw new E("depth \u9700\u4E3A 1-10 \u7684\u6574\u6570",{field:"depth",value:n});F=D}const R=String(e),b=String(r),v=i&&i.trim()?i.trim():null,I=v||O(g,R,b);if(!c.existsSync(I))return f(l,"INFO","Directory does not exist, returning empty list",{targetDir:I,userId:R,cId:b}),{files:[],recursive:s,depth:F,type:d||"all",limit:S};const N=ge(I,o);if(await $(I,N))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:o});if(!c.existsSync(N))return f(l,"INFO","List path does not exist, returning empty list",{targetDir:I,listDir:N,relativePath:o||"",userId:R,cId:b}),{files:[],recursive:s,depth:F,type:d||"all",limit:S};if(!(await c.promises.stat(N)).isDirectory())throw new E("relativePath \u5FC5\u987B\u662F\u76EE\u5F55",{field:"relativePath",relativePath:o});f(l,"DEBUG","Start getting user file list",{targetDir:I,listDir:N,relativePath:o||"",recursive:s,userId:R,cId:b});try{const D={typeFilter:d,limitState:{limit:S,pushed:0},levelsLeft:!s&&F!=null?F:1},ee=s?(await we(N,l,t,v,de(I,N),{...D,rootDir:I})).entries:await he(N,I,l,t,v,D);return f(l,"INFO","User file list obtained successfully",{fileCount:ee.length,targetDir:I,listDir:N,relativePath:o||"",recursive:s,depth:F,type:d||"all",limit:S,userId:R,cId:b,elapsedMs:Date.now()-y}),{files:ee,recursive:s,depth:F,type:d||"all",limit:S}}catch(D){throw D instanceof E?D:(f(l,"ERROR","Failed to get user file list",{targetDir:I,listDir:N,relativePath:o||"",recursive:s,userId:R,cId:b,error:D.message,elapsedMs:Date.now()-y}),new T(`Failed to get file list: ${D.message}`,{targetDir:I,originalError:D.message}))}}const Xe=5e3;function le(e,r){const t=Number.parseInt(e,10);if(!Number.isFinite(t)||t<=0)throw new E(`${r} \u5FC5\u987B\u4E3A\u6B63\u6574\u6570`,{field:r,value:e});return t}function Se(e,r,t){return t?r.toLowerCase().includes(t)?!0:e.toLowerCase().includes(t):!1}const Ve=new Set([".git",".svn",".hg","node_modules","__pycache__",".venv","venv","virtualenv",".mypy_cache",".pytest_cache",".ruff_cache",".cache",".tmp"]),Ge=new Set([".DS_Store","Thumbs.db","desktop.ini"]);function Re(e,r,t){return!!(Ge.has(e.name)||r.includes(e.name)||e.isDirectory()&&(Ve.has(e.name)||t.includes(e.name)))}function Ie(e,r,t,i){for(let o=r;o<e.length;o++)if(!Re(e[o],t,i))return!0;return!1}function Fe(e,r){return Date.now()-e>=r}async function He(e,r,t,i,o,a,n,m,u,g,y=null){const l=Date.now(),s=`computer:${e}:${r}`;if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!r)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const w=(i==null?"":String(i)).trim();if(!w)throw new E("kw \u4E0D\u80FD\u4E3A\u7A7A",{field:"kw"});const d=le(m,"limit"),p=le(u,"maxVisit"),S=le(g,"timeoutMs"),F=w.toLowerCase(),R=(()=>{const k=n==null?"":String(n).trim().toLowerCase();return k==="file"?"file":k==="dir"||k==="directory"?"dir":null})(),b=k=>R==null||R==="dir"===k,v=String(e),I=String(r),N=o&&o.trim()?o.trim():null,C=N||O(y,v,I);if(!c.existsSync(C))return{files:[],truncated:!1,visited:0};const D=ge(C,a);if(!c.existsSync(D))return{files:[],truncated:!1,visited:0};if(await $(C,D))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:a});if(!(await c.promises.stat(D)).isDirectory())throw new E("relativePath \u5FC5\u987B\u662F\u76EE\u5F55",{field:"relativePath",relativePath:a});const ve=q(h.relative(C,D)),te=P.CONTENT_TRAVERSE_EXCLUDE_FILES||[],re=P.TRAVERSE_EXCLUDE_DIRS||[],_=[],ie=new Set;let M=0,ne=!1,A=!1;const z=[ve],j=()=>M>=p||Fe(l,S);for(;z.length>0;){if(j()){A=!0;break}if(_.length>=d){z.length>0&&(A=!0);break}const k=z.shift();if(ie.has(k))continue;ie.add(k);const B=k?h.join(C,k):C;let W;try{W=await c.promises.readdir(B,{withFileTypes:!0})}catch(x){f(s,"WARN","search readdir failed",{dirRel:k,error:x.message});continue}W.sort((x,L)=>x.isDirectory()&&!L.isDirectory()?-1:!x.isDirectory()&&L.isDirectory()?1:x.name.toLowerCase().localeCompare(L.name.toLowerCase()));const se=[];let V=!1,G=!1;for(let x=0;x<W.length;x++){const L=W[x];if(Re(L,te,re))continue;if(j()){A=!0,V=!0,G=!0;break}M+=1;const ue=h.join(B,L.name),U=q(h.relative(C,ue));if(L.isDirectory()){if(se.push(U),b(!0)&&_.length<d&&Se(U,L.name,F)&&(_.push({name:U,isDir:!0}),_.length>=d)){V=!0,G=Ie(W,x+1,te,re);break}continue}try{if(b(!1)&&_.length<d&&Se(U,L.name,F)&&(_.push({name:U,isDir:!1,fileProxyUrl:Q(t,U,N),isLink:L.isSymbolicLink()}),_.length>=d)){V=!0,G=Ie(W,x+1,te,re);break}}catch(be){f(s,"WARN",`search \u5904\u7406\u6587\u4EF6\u5931\u8D25: ${ue}`,{error:be.message})}}if(V||j()){(j()||G||se.length>0||z.length>0)&&(A=!0);continue}for(const x of se){if(j()){A=!0;break}if(z.length>=Xe){A=!0;break}if(M+z.length>=p){A=!0;break}ie.has(x)||z.push(x)}}return _.sort((k,B)=>k.isDir!==B.isDir?k.isDir?-1:1:String(k.name).localeCompare(String(B.name))),ne=A||z.length>0||M>=p||Fe(l,S),f(s,"INFO","File search completed",{kw:w,matchCount:_.length,visited:M,truncated:ne,relativePath:a||"",elapsedMs:Date.now()-l}),{files:_,truncated:ne,visited:M}}async function qe(e,r,t,i,o=null){const a=Date.now(),n=`computer:${e}:${r}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!Array.isArray(t))throw new E("files must be an array",{field:"files"});const m=String(e),u=String(r),g=i&&i.trim()?i.trim():null,y=g||O(o,m,u);c.existsSync(y)||c.mkdirSync(y,{recursive:!0});for(let l=0;l<t.length;l++){const s=t[l];if(!s||typeof s.operation!="string")throw new E(`files[${l}].operation cannot be empty`,{field:`files[${l}].operation`});if(!s.name||typeof s.name!="string")throw new E(`files[${l}].name cannot be empty`,{field:`files[${l}].name`});const w=s.operation.toLowerCase();if(!["create","delete","rename","modify"].includes(w))throw new E(`files[${l}].operation must be one of create, delete, rename or modify`,{field:`files[${l}].operation`});if(w==="rename"&&!s.renameFrom)throw new E(`files[${l}].renameFrom cannot be empty (rename operation requires)`,{field:`files[${l}].renameFrom`});if(w==="modify"&&s.isDir!==!0&&typeof s.contents!="string")throw new E(`files[${l}].contents must be a string (modify operation requires)`,{field:`files[${l}].contents`})}f(n,"DEBUG","Start updating user files",{userId:m,cId:u,filesCount:t.length});try{for(const l of t){const s=l.operation.toLowerCase(),w=l.name,d=h.normalize(w).replace(/^[\/\\]+/,""),p=h.join(y,d),S=h.resolve(p),F=h.resolve(y);if(!S.startsWith(F+h.sep)&&S!==F){f(n,"WARN","File path is not secure, skipping",{filePath:d,resolvedPath:S});continue}switch(s){case"create":{if(l.isDir===!0){if(c.existsSync(p)){if((await c.promises.stat(p)).isFile())throw new E("Cannot create directory, file with the same name already exists",{filePath:d});f(n,"INFO","Directory already exists, skipping creation",{filePath:d});break}await c.promises.mkdir(p,{recursive:!0}),f(n,"INFO","Directory created successfully",{filePath:d});break}if(c.existsSync(p)){if((await c.promises.stat(p)).isDirectory())throw new E("Cannot create file, directory with the same name already exists",{filePath:d});f(n,"INFO","File already exists, skipping creation",{filePath:d});break}await c.promises.mkdir(h.dirname(p),{recursive:!0});const R=l.contents||"";await c.promises.writeFile(p,R,"utf8"),f(n,"INFO","File created successfully",{filePath:d});break}case"delete":{c.existsSync(p)?(await c.promises.stat(p)).isDirectory()?(await c.promises.rm(p,{recursive:!0,force:!0}),f(n,"INFO","Directory deleted successfully",{filePath:d})):(await c.promises.unlink(p),f(n,"INFO","File deleted successfully",{filePath:d})):f(n,"WARN","The file or directory to be deleted does not exist",{filePath:d});break}case"rename":{const R=l.renameFrom;if(!R||typeof R!="string"){f(n,"WARN","Rename operation missing renameFrom",{filePath:d});break}const b=h.normalize(R).replace(/^[\/\\]+/,""),v=h.join(y,b),I=h.resolve(v);if(!I.startsWith(F+h.sep)&&I!==F){f(n,"WARN","Source path is not secure, skipping rename",{sourcePath:b,targetPath:d});break}if(c.existsSync(v)){const C=(await c.promises.stat(v)).isDirectory();await c.promises.mkdir(h.dirname(p),{recursive:!0}),await c.promises.rename(v,p),f(n,"INFO",C?"Directory renamed successfully":"File renamed successfully",{sourcePath:b,targetPath:d})}else f(n,"WARN","The file or directory to be renamed does not exist",{sourcePath:b});break}case"modify":{if(!c.existsSync(p)){f(n,"WARN","The file to be modified does not exist",{filePath:d});break}if((await c.promises.stat(p)).isDirectory()){f(n,"INFO","The target is a directory, skipping modification",{filePath:d});break}const b=typeof l.contents=="string"?l.contents:"";if(await c.promises.readFile(p,"utf8")===b){f(n,"INFO","File content has no changes, skipping write",{filePath:d});break}await c.promises.writeFile(p,b,"utf8"),f(n,"INFO","File modified successfully",{filePath:d});break}default:{f(n,"WARN","Unsupported operation type",{operation:s,filePath:d});break}}}return f(n,"INFO","User files updated successfully",{userId:m,cId:u,filesCount:t.length,elapsedMs:Date.now()-a}),{success:!0,message:"User files updated successfully",userId:m,cId:u,filesCount:t.length}}catch(l){throw f(n,"ERROR","User files updated failed",{userId:m,cId:u,error:l.message,elapsedMs:Date.now()-a}),new T(`User files updated failed: ${l.message}`,{userId:m,cId:u,originalError:l.message})}}async function ce(e,r,t,i,o,a=null){const n=Date.now(),m=`computer:${e}:${r}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!t)throw new E("file cannot be empty",{field:"file"});if(!i||typeof i!="string")throw new E("filePath cannot be empty",{field:"filePath"});const u=String(e),g=String(r),y=o&&o.trim()?o.trim():null,l=y||O(a,u,g);c.existsSync(l)||c.mkdirSync(l,{recursive:!0});const s=h.normalize(i).replace(/^[\/\\]+/,""),w=h.join(l,s),d=h.resolve(w),p=h.resolve(l);if(!d.startsWith(p+h.sep)&&d!==p)throw new E("File path is not secure, cannot exceed user directory",{field:"filePath",providedPath:i,resolvedPath:d});try{if(await c.promises.mkdir(h.dirname(w),{recursive:!0}),t.buffer)await c.promises.writeFile(w,t.buffer);else if(typeof t.contents=="string")await c.promises.writeFile(w,t.contents,"utf8");else throw new E("File content format is incorrect",{field:"file",hasBuffer:!!t.buffer,hasContents:typeof t.contents});return f(m,"INFO","File uploaded successfully",{userId:u,cId:g,filePath:s,targetPath:d,fileSize:t.buffer?t.buffer.length:t.contents?Buffer.byteLength(t.contents,"utf8"):0,elapsedMs:Date.now()-n}),{success:!0,message:"File uploaded successfully",fileSize:t.buffer?t.buffer.length:t.contents?Buffer.byteLength(t.contents,"utf8"):0}}catch(S){throw f(m,"ERROR","File upload failed",{userId:u,cId:g,filePath:s,error:S.message,elapsedMs:Date.now()-n}),new T(`File upload failed: ${S.message}`,{userId:u,cId:g,filePath:s,originalError:S.message})}}async function Ze(e,r,t,i,o,a=null){const n=Date.now(),m=`computer:${e}:${r}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!Array.isArray(t))throw new E("files must be an array",{field:"files"});if(!Array.isArray(i))throw new E("filePaths must be an array",{field:"filePaths"});if(t.length!==i.length)throw new E(`File count (${t.length}) does not match path count (${i.length})`,{field:"filePaths"});f(m,"DEBUG","Start batch uploading files",{userId:e,cId:r,filesCount:t.length});const u=[];try{for(let l=0;l<t.length;l++){const s=t[l],w=i[l];if(!s){f(m,"WARN","Empty file object encountered in batch upload, skipping",{index:l,filePath:w}),u.push({success:!1,filePath:w,error:"Empty file object"});continue}if(!w||typeof w!="string"){f(m,"WARN","Invalid file path in batch upload, skipping",{index:l,originalname:s.originalname}),u.push({success:!1,filePath:w||"",originalname:s.originalname,error:"Invalid file path"});continue}try{const d=await ce(e,r,s,w,o,a);u.push({success:!0,filePath:w,originalname:s.originalname,...d})}catch(d){f(m,"ERROR","Single file upload failed in batch upload",{filePath:w,originalname:s.originalname,error:d.message}),u.push({success:!1,filePath:w,originalname:s.originalname,error:d.message})}}const g=u.filter(l=>l.success).length,y=u.filter(l=>!l.success).length;return f(m,"INFO","Batch upload files completed",{userId:e,cId:r,totalCount:t.length,successCount:g,failCount:y,elapsedMs:Date.now()-n}),{success:!0,message:"Batch upload completed",totalCount:t.length,successCount:g,failCount:y,results:u}}catch(g){throw f(m,"ERROR","Batch upload files failed",{userId:e,cId:r,error:g.message,elapsedMs:Date.now()-n}),new T(`Batch upload files failed: ${g.message}`,{userId:e,cId:r,originalError:g.message})}}async function Ke(e,r,t,i=null){const o=Date.now(),a=`computer:${e}:${r}`,n=me(i);if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!n&&!i?.workspacePath)throw new T("COMPUTER_WORKSPACE_DIR is not configured, cannot create zip");const m=String(e),u=String(r),g=t&&t.trim()?t.trim():null,y=g||O(i,m,u);if(!c.existsSync(y)){const S=`${m}_${u}.zip`;f(a,"WARN","Workspace directory does not exist, returning empty zip",{targetDir:y,userId:m,cId:u,zipFileName:S});const F=fe("zip",{zlib:{level:9}});return F.append(null,{name:`${m}_${u}/`,type:"directory"}),F.on("warning",R=>{if(R.code==="ENOENT")f(a,"WARN","Encountered file problem when creating empty zip",{message:R.message,code:R.code});else throw f(a,"ERROR","Encountered warning when creating empty zip",{message:R.message,code:R.code}),R}),F.on("error",R=>{f(a,"ERROR","Failed to create empty zip",{message:R.message})}),{archive:F,zipFileName:S}}const l=`${m}_${u}.zip`;f(a,"DEBUG","Start creating workspace directory zip",{targetDir:y,zipFileName:l});const s=fe("zip",{zlib:{level:9}}),w=P.CONTENT_TRAVERSE_EXCLUDE_FILES||[],d=P.TRAVERSE_EXCLUDE_DIRS||[],p=await Ee(y,w,d,a);if(p>H){const S=H/1024/1024,F=(p/1024/1024).toFixed(2);throw f(a,"WARN","Download rejected due to oversized workspace",{targetDir:y,downloadableSize:p,maxSizeBytes:H}),new E(`Download failed: total file size ${F}MB exceeds limit ${S}MB`,{field:"downloadSize",downloadableSize:p,maxSizeBytes:H})}return s.directory(y,`${m}_${u}`,S=>{const F=S.name||"",R=F.split(/[\/\\]/).filter(Boolean);if(R.some(v=>v.startsWith(".")))return!1;const b=R[R.length-1];if(w.includes(b)||R.some(v=>d.includes(v)))return!1;try{const v=h.join(y,F),I=c.lstatSync(v);if(I.isSymbolicLink()||I.nlink>1)return!1}catch(v){return f(a,"WARN","Error occurred when detecting link file, skipping",{filePath:F,error:v.message}),!1}return S}),s.on("warning",S=>{if(S.code==="ENOENT")f(a,"WARN","Encountered file problem when creating zip",{message:S.message,code:S.code});else throw f(a,"ERROR","Encountered warning when creating zip",{message:S.message,code:S.code}),S}),s.on("error",S=>{f(a,"ERROR","Failed to create zip",{message:S.message,elapsedMs:Date.now()-o})}),s.on("end",()=>{f(a,"INFO","Workspace directory zip created successfully",{targetDir:y,zipFileName:l,elapsedMs:Date.now()-o})}),{archive:s,zipFileName:l}}async function Ye(e,r,t=200,i=null){const o=Date.now(),a=`computer:${e}:${r}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});const n=String(e),m=String(r),u=Pe(i,n,m);if(!u)return f(a,"WARN","COMPUTER_WORKSPACE_DIR is not configured"),{success:!0,message:"Log workspace is not configured",logs:[],totalLines:0,startIndex:1,logFileName:null};if(!c.existsSync(u))return f(a,"DEBUG","Log directory does not exist",{logDir:u}),{success:!0,message:"Log directory does not exist",logs:[],totalLines:0,startIndex:1,logFileName:null};const y=(await c.promises.readdir(u,{withFileTypes:!0})).filter(I=>I.isFile()).map(I=>I.name);if(y.length===0)return f(a,"DEBUG","No log file found",{logDir:u}),{success:!0,message:"No log file found",logs:[],totalLines:0,startIndex:1,logFileName:null};const l=[];for(const I of y){const N=h.join(u,I),C=await c.promises.stat(N);l.push({name:I,fullPath:N,mtime:C.mtimeMs,size:C.size})}l.sort((I,N)=>N.mtime-I.mtime);const s=l[0],d=(await c.promises.readFile(s.fullPath,"utf8")).split(`
2
- `).filter(I=>I.length>0),p=d.length,S=Math.max(1,t),F=Math.max(0,p-S),R=F+1,v=d.slice(F).map((I,N)=>({line:R+N,content:I}));return f(a,"DEBUG","Get latest logs",{fileName:s.name,fileSize:s.size,totalLines:p,returnedLines:v.length,elapsedMs:Date.now()-o}),{success:!0,message:"Get log successfully",logs:v,totalLines:p,startIndex:R,logFileName:s.name}}async function De(e,r){try{const i=(await c.promises.readdir(e,{withFileTypes:!0})).filter(m=>!m.name.startsWith(".")&&m.name!=="node_modules");if(i.length!==1||!i[0].isDirectory())return;const o=h.join(e,i[0].name),a=h.join(e,`..tmp_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{await c.promises.rename(o,a)}catch(m){if(m.code==="EXDEV")await c.promises.cp(o,a,{recursive:!0}),await c.promises.rm(o,{recursive:!0,force:!0});else throw m}const n=await c.promises.readdir(a);for(const m of n){const u=h.join(a,m),g=h.join(e,m);try{await c.promises.rename(u,g)}catch(y){if(y.code==="EXDEV")(await c.promises.lstat(u)).isDirectory()?await Ce(u,g):(await c.promises.copyFile(u,g),await c.promises.unlink(u));else throw y}}await c.promises.rm(a,{recursive:!0,force:!0}),f(r,"INFO","Removed single top-level directory from imported zip",{removedDir:i[0].name})}catch(t){f(r,"WARN","Failed to remove top-level directory from imported zip, continuing",{error:t.message})}}async function Je(e,r){if(!c.existsSync(e)){await c.promises.mkdir(e,{recursive:!0});return}const t=await c.promises.readdir(e,{withFileTypes:!0});for(const i of t){if(X.has(i.name))continue;const o=h.join(e,i.name);await c.promises.rm(o,{recursive:!0,force:!0}),f(r,"DEBUG","Removed workspace entry during import",{name:i.name})}}async function Qe(e,r,t){if(await c.promises.mkdir(r,{recursive:!0}),!c.existsSync(e)){await c.promises.mkdir(e,{recursive:!0});return}const i=await c.promises.readdir(e,{withFileTypes:!0});for(const o of i){if(X.has(o.name))continue;const a=h.join(e,o.name),n=h.join(r,o.name);await oe(a,n),f(t,"DEBUG","Backed up workspace entry before import",{name:o.name})}}async function et(e,r,t){if(!c.existsSync(e))return;const i=await c.promises.readdir(e,{withFileTypes:!0});for(const o of i){if(X.has(o.name))continue;const a=h.join(e,o.name),n=h.join(r,o.name);await oe(a,n)}f(t,"INFO","Workspace restored from backup after import failure")}async function tt(e,r,t){const i=await c.promises.readdir(e,{withFileTypes:!0}),o=[];try{for(const a of i){if(X.has(a.name)){f(t,"INFO","Skipping preserved entry from imported zip",{name:a.name});continue}const n=h.join(e,a.name),m=h.join(r,a.name);await oe(n,m),o.push(a.name)}}catch(a){throw a.mergedEntries=o,a}}async function rt(e,r,t,i,o=null){const a=Date.now(),n=`computer:${e}:${r}`,m=me(o);if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!t||!t.path)throw new E("file is required",{field:"file"});if(!m&&!o?.workspacePath)throw new T("COMPUTER_WORKSPACE_DIR is not configured");if(h.extname(t.originalname||t.filename||"").toLowerCase()!==".zip")throw new E("Only zip files are supported",{field:"file",originalName:t.originalname});const g=String(e),y=String(r),l=i&&i.trim()?i.trim():O(o,g,y),s=h.join(l,".tmp"),w=h.join(s,`import_project_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);let d=null;f(n,"INFO","Start importing project from zip",{userId:g,cId:y,targetDir:l,fileName:t.originalname});try{await c.promises.mkdir(l,{recursive:!0}),await c.promises.mkdir(s,{recursive:!0}),await c.promises.mkdir(w,{recursive:!0}),await xe(t.path,w),await De(w,n),d=h.join(s,`import_backup_${Date.now()}_${Math.round(Math.random()*1e6)}`),await Qe(l,d,n);try{await tt(w,l,n)}catch(p){throw f(n,"ERROR","Merge imported project failed, restoring workspace",{error:p.message,mergedEntries:p.mergedEntries||[]}),await Je(l,n),await et(d,l,n),p}return c.existsSync(d)&&(await c.promises.rm(d,{recursive:!0,force:!0}),d=null),f(n,"INFO","Project imported successfully",{userId:g,cId:y,targetDir:l,elapsedMs:Date.now()-a}),{success:!0,message:"Project imported successfully",userId:g,cId:y,targetDir:l}}catch(p){throw f(n,"ERROR","Failed to import project",{userId:g,cId:y,targetDir:l,error:p.message,elapsedMs:Date.now()-a}),p instanceof E||p instanceof T||p instanceof ke?p:new T(`Failed to import project: ${p.message}`,{userId:g,cId:y,originalError:p.message})}finally{if(c.existsSync(w))try{await c.promises.rm(w,{recursive:!0,force:!0})}catch(p){f(n,"WARN","Failed to clean import extract directory",{extractRoot:w,error:p.message})}if(d&&c.existsSync(d))try{await c.promises.rm(d,{recursive:!0,force:!0})}catch(p){f(n,"WARN","Failed to clean import backup directory",{backupDir:d,error:p.message})}if(t?.path&&c.existsSync(t.path))try{await c.promises.unlink(t.path)}catch(p){f(n,"WARN","Failed to clean uploaded zip file",{tempZipPath:t.path,error:p.message})}}}async function it(e,r,t,i,o,a=null){const n=`computer:${e}:${r}`;if(!t||typeof t!="string"||!t.trim())throw new E("fileName cannot be empty",{field:"fileName"});if(i!=null&&typeof i!="string")throw new E("content must be a string",{field:"content"});const m=t.trim(),u=i??"";return f(n,"INFO","Generate file from text content",{userId:e,cId:r,fileName:m,contentLength:u.length}),{...await ce(e,r,{contents:u,originalname:h.basename(m),size:Buffer.byteLength(u,"utf8")},m,o,a),message:"File generated successfully",fileName:m}}export{$ as escapesRoot,Be as getFileList,je as getFileMeta,Le as resolveExistingFile,He as searchFiles,qe as updateFiles,ce as uploadFile,Ze as uploadFiles,it as generateFile,Ke as downloadAllFiles,Ye as getLatestLogs,rt as importProject,X as IMPORT_PROJECT_PRESERVED_ENTRIES,De as removeTopLevelDir};
1
+ import c from"fs";import h from"path";import fe from"archiver";import{lookup as Ce}from"mime-types";import P from"../../appConfig/index.js";import{log as f}from"../log/logUtils.js";import{ValidationError as E,SystemError as z,FileError as Pe}from"../error/errorHandler.js";import{extractZip as _e}from"../common/zipUtils.js";import{moveDirectory as Le,movePath as oe}from"../common/fileSystemUtils.js";import{resolveLogDir as Ae,resolveWorkspaceDir as O,resolveWorkspaceRoot as me}from"./workspaceContext.js";const B=new Set([".git",".agents",".claude",".codex",".opencode",".tmp",".logs"]),Te=100*1024*1024,H=P.DOWNLOAD_MAX_FILE_SIZE_BYTES||Te;function q(e){return h.sep==="\\"?e.replace(/\\/g,"/"):e}function de(e,r){return q(h.relative(e,r))}function pe(e,r){return e?`${e}/${r}`:r}function Z(e){return e!=null&&e.limit!=null&&e.pushed>=e.limit}function K(e){e!=null&&(e.pushed+=1)}async function he(e,r,t,i,o,a={}){const{typeFilter:s=null,limitState:d=null,levelsLeft:m=1}=a,w=[],g=await c.promises.readdir(e,{withFileTypes:!0});g.sort((l,y)=>l.isDirectory()&&!y.isDirectory()?-1:!l.isDirectory()&&y.isDirectory()?1:l.name.toLowerCase().localeCompare(y.name.toLowerCase()));const n=de(r,e);for(const l of g){if(Z(d))break;if(l.name.startsWith(".")&&l.name!==".gitignore"||(P.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(l.name)||l.isDirectory()&&Ee(l.name))continue;const u=pe(n,l.name);if(l.isDirectory()){if(s!=="file"&&(w.push({name:u,isDir:!0}),K(d)),m>1&&!Z(d))try{const p=await he(h.join(e,l.name),r,t,i,o,{typeFilter:s,limitState:d,levelsLeft:m-1});w.push(...p)}catch(p){f(t,"WARN",`Depth descent failed, skip subtree: ${u}`,{error:p.message})}continue}if(s!=="dir"&&!(l.isSymbolicLink()&&await ge(r,h.join(e,l.name))))try{w.push({name:u,isDir:!1,fileProxyUrl:Q(i,u,o),isLink:l.isSymbolicLink()}),K(d)}catch(p){f(t,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${e}/${l.name}`,{error:p.message})}}return w}async function we(e,r,t,i,o="",a={}){const{typeFilter:s=null,limitState:d=null}=a,m=a.rootDir||e,w=[],g=await c.promises.readdir(e,{withFileTypes:!0});g.sort((n,l)=>n.isDirectory()&&!l.isDirectory()?-1:!n.isDirectory()&&l.isDirectory()?1:n.name.toLowerCase().localeCompare(l.name.toLowerCase()));for(const n of g){if(Z(d))break;const l=h.join(e,n.name);if(n.name.startsWith(".")&&n.name!==".gitignore"||(P.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(n.name)||n.isDirectory()&&Ee(n.name)||n.isSymbolicLink()&&await ge(m,l))continue;const u=pe(o,n.name);if(n.isDirectory()){s!=="file"&&!Z(d)&&(w.push({name:u,isDir:!0}),K(d));try{const p=await we(l,r,t,i,u,a);w.push(...p.entries)}catch(p){f(r,"WARN",`Depth descent failed, skip subtree: ${u}`,{error:p.message})}continue}if(s!=="dir")try{w.push({name:u,isDir:!1,fileProxyUrl:Q(t,u,i),isLink:n.isSymbolicLink()}),K(d)}catch(p){f(r,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${l}`,{error:p.message})}}return{entries:w}}const Y=process.platform==="win32"?/^[\/\\]+/:/^\/+/;async function X(e,r){try{const t=await c.promises.realpath(e),i=await c.promises.realpath(r);return i!==t&&!i.startsWith(t+h.sep)}catch{return!1}}async function ge(e,r){try{const t=await c.promises.realpath(e),i=await c.promises.realpath(r);return i!==t&&!i.startsWith(t+h.sep)}catch{return!0}}const ye=process.platform==="win32"?new Set((P.TRAVERSE_EXCLUDE_DIRS||[]).map(e=>String(e).toLowerCase())):new Set(P.TRAVERSE_EXCLUDE_DIRS||[]);function Ee(e){return process.platform==="win32"?ye.has(e.toLowerCase()):ye.has(e)}function Se(e,r){const t=r==null?"":String(r),i=t.trim();if(!i||i==="."||i==="/")return h.resolve(e);const o=h.normalize(t).replace(Y,"");if(!o||o===".")return h.resolve(e);if(h.isAbsolute(t)||o.split(h.sep).includes(".."))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:r});const a=h.resolve(e),s=h.resolve(e,o);if(s!==a&&!s.startsWith(a+h.sep))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:r});return s}function J(e,r){const t=r==null?"":String(r);if(!t.trim())return null;const i=h.resolve(e);let o;if(h.isAbsolute(t))o=h.resolve(t);else{const s=h.normalize(t).replace(Y,"");if(!s||s==="."||s.split(h.sep).includes(".."))return null;o=h.resolve(e,s)}if(o!==i&&!o.startsWith(i+h.sep))return null;const a=q(h.relative(i,o));return!a||a.startsWith("..")?null:{absPath:o,name:a}}function Q(e,r,t){if(!e||!r)return null;const i=r.split("/").map(a=>encodeURIComponent(a)).join("/");let o=`${e}/${i}`;return t&&(o+=`?customTargetDir=${encodeURIComponent(t)}`),o}async function ze(e,r,t,i,o,a=null){if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!r)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});if(!t||!String(t).trim())throw new E("filePath \u4E0D\u80FD\u4E3A\u7A7A",{field:"filePath"});const s=String(e),d=String(r),m=o&&o.trim()?o.trim():null,w=m||O(a,s,d);if(!c.existsSync(w))return{exists:!1};let g=J(w,t);if(!g&&String(t).startsWith("/")){const n=String(t).replace(Y,"");n&&(g=J(w,n))}if(!g)return{exists:!1};try{if(await X(w,g.absPath))return{exists:!1};if(!(await c.promises.stat(g.absPath)).isFile())return{exists:!1}}catch{return{exists:!1}}return{exists:!0,name:g.name,fileProxyUrl:Q(i,g.name,m)}}const Oe=100,$e=1e3,Me=8,Re=1e3;function We(e){return h.extname(e).replace(/^\./,"").toLowerCase()||""}async function Ue(e,r,t){const i=new Array(e.length),o=Math.max(1,Math.min(r,e.length));let a=0;return await Promise.all(Array.from({length:o},async()=>{for(;;){const s=a++;if(s>=e.length)return;i[s]=await t(e[s],s)}})),i}function je(e){return!(e.startsWith(".")&&e!==".gitignore"||(P.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(e)||P.TRAVERSE_EXCLUDE_DIRS.includes(e))}async function Be(e){const r=await c.promises.opendir(e);let t=0;for await(const i of r)if(je(i.name)&&(t+=1,t>=Re))return Re;return t}function ae(e,r){return{path:e,isDir:null,isLink:null,size:null,mtimeMs:null,extension:null,mimeType:null,linkTarget:null,childCount:null,...r?{error:r}:{}}}async function Xe(e,r,t){const i=r==null?"":String(r);let o=i.trim()?J(e,i):null;if(!o&&i.startsWith("/")){const a=i.replace(Y,"");a&&(o=J(e,a))}if(!o||await X(e,o.absPath))return ae(i,"illegal path");try{const a=await c.promises.lstat(o.absPath),s=a.isDirectory(),d=a.isSymbolicLink(),m={path:i,isDir:s,isLink:d,size:s?null:a.size,mtimeMs:a.mtimeMs,extension:s?null:We(i),mimeType:s?null:Ce(i)||"application/octet-stream",linkTarget:null,childCount:null};if(d)try{m.linkTarget=await c.promises.readlink(o.absPath)}catch(w){f(t,"WARN","Readlink failed, linkTarget set to null",{filePath:i,error:w.message})}if(s)try{m.childCount=await Be(o.absPath)}catch(w){f(t,"WARN","Count directory entries failed, childCount set to null",{filePath:i,error:w.message})}return m}catch(a){return ae(i,a.code||a.message)}}async function Ve(e,r,t,i,o=null,a=null){const s=Date.now(),d=`computer:${e}:${r}`;if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!r)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});if(!Array.isArray(t)||t.length===0)throw new E("filePaths \u5FC5\u987B\u4E3A\u975E\u7A7A\u6570\u7EC4",{field:"filePaths"});const m=Number.parseInt(a,10),w=Number.isFinite(m)&&m>0?Math.min(m,$e):Oe;if(t.length>w)throw new E(`filePaths \u5355\u6B21\u6700\u591A ${w} \u6761`,{field:"filePaths",count:t.length});const g=String(e),n=String(r),y=(i==null?"":String(i).trim())||null,u=y||O(o,g,n);if(!c.existsSync(u))return f(d,"INFO","Target directory does not exist, returning empty metas",{targetDir:u,userId:g,cId:n}),{metas:[]};const p=await Ue(t,Me,S=>Xe(u,S,d));return f(d,"INFO","File metadata queried",{targetDir:u,count:p.length,failed:p.filter(S=>S.error).length,customTargetDir:y,userId:g,cId:n,elapsedMs:Date.now()-s}),{metas:p}}async function Ie(e,r,t,i,o=""){const a=await c.promises.readdir(h.join(e,o),{withFileTypes:!0});let s=0;for(const d of a){const m=o?h.join(o,d.name):d.name,w=m.split(h.sep).filter(Boolean);if(w.some(y=>y.startsWith(".")))continue;const g=w[w.length-1];if(r.includes(g)||w.some(y=>t.includes(y)))continue;const n=h.join(e,m);let l;try{l=await c.promises.lstat(n)}catch(y){f(i,"WARN","Error occurred when getting file stats, skipping",{filePath:m.replace(/\\/g,"/"),error:y.message});continue}l.isSymbolicLink()||l.nlink>1||(l.isDirectory()?s+=await Ie(e,r,t,i,m):l.isFile()&&(s+=l.size))}return s}async function Ge(e,r,t,i,o,a,s,d,m,w=null){const g=Date.now(),n=`computer:${e}:${r}`,l=!(a===!1||a==="false");if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!r)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const y=d==null?"":String(d).trim().toLowerCase();let u=null;if(y&&y!=="all")if(y==="file")u="file";else if(y==="dir"||y==="directory")u="dir";else throw new E("type \u4EC5\u652F\u6301 file/dir/all",{field:"type",value:d});const p=D=>/^\d+$/.test(String(D))?parseInt(String(D),10):NaN;let S=null;if(m!=null&&m!==""){const D=p(m);if(!Number.isInteger(D)||D<0)throw new E("limit \u5FC5\u987B\u4E3A\u975E\u8D1F\u6574\u6570",{field:"limit",value:m});S=D}let F=null;if(!l&&s!=null&&s!==""){const D=p(s);if(!Number.isInteger(D)||D<1||D>10)throw new E("depth \u9700\u4E3A 1-10 \u7684\u6574\u6570",{field:"depth",value:s});F=D}const R=String(e),b=String(r),v=i&&i.trim()?i.trim():null,I=v||O(w,R,b);if(!c.existsSync(I))return f(n,"INFO","Directory does not exist, returning empty list",{targetDir:I,userId:R,cId:b}),{files:[],recursive:l,depth:F,type:u||"all",limit:S};const k=Se(I,o);if(await X(I,k))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:o});if(!c.existsSync(k))return f(n,"INFO","List path does not exist, returning empty list",{targetDir:I,listDir:k,relativePath:o||"",userId:R,cId:b}),{files:[],recursive:l,depth:F,type:u||"all",limit:S};if(!(await c.promises.stat(k)).isDirectory())throw new E("relativePath \u5FC5\u987B\u662F\u76EE\u5F55",{field:"relativePath",relativePath:o});f(n,"DEBUG","Start getting user file list",{targetDir:I,listDir:k,relativePath:o||"",recursive:l,userId:R,cId:b});try{const D={typeFilter:u,limitState:{limit:S,pushed:0},levelsLeft:!l&&F!=null?F:1},ee=l?(await we(k,n,t,v,de(I,k),{...D,rootDir:I})).entries:await he(k,I,n,t,v,D);return f(n,"INFO","User file list obtained successfully",{fileCount:ee.length,targetDir:I,listDir:k,relativePath:o||"",recursive:l,depth:F,type:u||"all",limit:S,userId:R,cId:b,elapsedMs:Date.now()-g}),{files:ee,recursive:l,depth:F,type:u||"all",limit:S}}catch(D){throw D instanceof E?D:(f(n,"ERROR","Failed to get user file list",{targetDir:I,listDir:k,relativePath:o||"",recursive:l,userId:R,cId:b,error:D.message,elapsedMs:Date.now()-g}),new z(`Failed to get file list: ${D.message}`,{targetDir:I,originalError:D.message}))}}const He=5e3;function le(e,r){const t=Number.parseInt(e,10);if(!Number.isFinite(t)||t<=0)throw new E(`${r} \u5FC5\u987B\u4E3A\u6B63\u6574\u6570`,{field:r,value:e});return t}function Fe(e,r,t){return t?r.toLowerCase().includes(t)?!0:e.toLowerCase().includes(t):!1}const qe=new Set([".git",".svn",".hg","node_modules","__pycache__",".venv","venv","virtualenv",".mypy_cache",".pytest_cache",".ruff_cache",".cache",".tmp"]),Ze=new Set([".DS_Store","Thumbs.db","desktop.ini"]);function De(e,r,t){return!!(Ze.has(e.name)||r.includes(e.name)||e.isDirectory()&&(qe.has(e.name)||t.includes(e.name)))}function ve(e,r,t,i){for(let o=r;o<e.length;o++)if(!De(e[o],t,i))return!0;return!1}function be(e,r){return Date.now()-e>=r}async function Ke(e,r,t,i,o,a,s,d,m,w,g=null){const n=Date.now(),l=`computer:${e}:${r}`;if(!e)throw new E("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!r)throw new E("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const y=(i==null?"":String(i)).trim();if(!y)throw new E("kw \u4E0D\u80FD\u4E3A\u7A7A",{field:"kw"});const u=le(d,"limit"),p=le(m,"maxVisit"),S=le(w,"timeoutMs"),F=y.toLowerCase(),R=(()=>{const N=s==null?"":String(s).trim().toLowerCase();return N==="file"?"file":N==="dir"||N==="directory"?"dir":null})(),b=N=>R==null||R==="dir"===N,v=String(e),I=String(r),k=o&&o.trim()?o.trim():null,C=k||O(g,v,I);if(!c.existsSync(C))return{files:[],truncated:!1,visited:0};const D=Se(C,a);if(!c.existsSync(D))return{files:[],truncated:!1,visited:0};if(await X(C,D))throw new E("relativePath \u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u8D8A\u51FA\u76EE\u6807\u76EE\u5F55",{field:"relativePath",relativePath:a});if(!(await c.promises.stat(D)).isDirectory())throw new E("relativePath \u5FC5\u987B\u662F\u76EE\u5F55",{field:"relativePath",relativePath:a});const Ne=q(h.relative(C,D)),te=P.CONTENT_TRAVERSE_EXCLUDE_FILES||[],re=P.TRAVERSE_EXCLUDE_DIRS||[],_=[],ie=new Set;let $=0,ne=!1,A=!1;const T=[Ne],U=()=>$>=p||be(n,S);for(;T.length>0;){if(U()){A=!0;break}if(_.length>=u){T.length>0&&(A=!0);break}const N=T.shift();if(ie.has(N))continue;ie.add(N);const j=N?h.join(C,N):C;let M;try{M=await c.promises.readdir(j,{withFileTypes:!0})}catch(x){f(l,"WARN","search readdir failed",{dirRel:N,error:x.message});continue}M.sort((x,L)=>x.isDirectory()&&!L.isDirectory()?-1:!x.isDirectory()&&L.isDirectory()?1:x.name.toLowerCase().localeCompare(L.name.toLowerCase()));const se=[];let V=!1,G=!1;for(let x=0;x<M.length;x++){const L=M[x];if(De(L,te,re))continue;if(U()){A=!0,V=!0,G=!0;break}$+=1;const ue=h.join(j,L.name),W=q(h.relative(C,ue));if(L.isDirectory()){if(se.push(W),b(!0)&&_.length<u&&Fe(W,L.name,F)&&(_.push({name:W,isDir:!0}),_.length>=u)){V=!0,G=ve(M,x+1,te,re);break}continue}try{if(b(!1)&&_.length<u&&Fe(W,L.name,F)&&(_.push({name:W,isDir:!1,fileProxyUrl:Q(t,W,k),isLink:L.isSymbolicLink()}),_.length>=u)){V=!0,G=ve(M,x+1,te,re);break}}catch(xe){f(l,"WARN",`search \u5904\u7406\u6587\u4EF6\u5931\u8D25: ${ue}`,{error:xe.message})}}if(V||U()){(U()||G||se.length>0||T.length>0)&&(A=!0);continue}for(const x of se){if(U()){A=!0;break}if(T.length>=He){A=!0;break}if($+T.length>=p){A=!0;break}ie.has(x)||T.push(x)}}return _.sort((N,j)=>N.isDir!==j.isDir?N.isDir?-1:1:String(N.name).localeCompare(String(j.name))),ne=A||T.length>0||$>=p||be(n,S),f(l,"INFO","File search completed",{kw:y,matchCount:_.length,visited:$,truncated:ne,relativePath:a||"",elapsedMs:Date.now()-n}),{files:_,truncated:ne,visited:$}}async function Ye(e,r,t,i,o=null){const a=Date.now(),s=`computer:${e}:${r}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!Array.isArray(t))throw new E("files must be an array",{field:"files"});const d=String(e),m=String(r),w=i&&i.trim()?i.trim():null,g=w||O(o,d,m);c.existsSync(g)||c.mkdirSync(g,{recursive:!0});for(let n=0;n<t.length;n++){const l=t[n];if(!l||typeof l.operation!="string")throw new E(`files[${n}].operation cannot be empty`,{field:`files[${n}].operation`});if(!l.name||typeof l.name!="string")throw new E(`files[${n}].name cannot be empty`,{field:`files[${n}].name`});const y=l.operation.toLowerCase();if(!["create","delete","rename","modify"].includes(y))throw new E(`files[${n}].operation must be one of create, delete, rename or modify`,{field:`files[${n}].operation`});if(y==="rename"&&!l.renameFrom)throw new E(`files[${n}].renameFrom cannot be empty (rename operation requires)`,{field:`files[${n}].renameFrom`});if(y==="modify"&&l.isDir!==!0&&typeof l.contents!="string")throw new E(`files[${n}].contents must be a string (modify operation requires)`,{field:`files[${n}].contents`})}f(s,"DEBUG","Start updating user files",{userId:d,cId:m,filesCount:t.length});try{for(const n of t){const l=n.operation.toLowerCase(),y=n.name,u=h.normalize(y).replace(/^[\/\\]+/,""),p=h.join(g,u),S=h.resolve(p),F=h.resolve(g);if(!S.startsWith(F+h.sep)&&S!==F){f(s,"WARN","File path is not secure, skipping",{filePath:u,resolvedPath:S});continue}switch(l){case"create":{if(n.isDir===!0){if(c.existsSync(p)){if((await c.promises.stat(p)).isFile())throw new E("Cannot create directory, file with the same name already exists",{filePath:u});f(s,"INFO","Directory already exists, skipping creation",{filePath:u});break}await c.promises.mkdir(p,{recursive:!0}),f(s,"INFO","Directory created successfully",{filePath:u});break}if(c.existsSync(p)){if((await c.promises.stat(p)).isDirectory())throw new E("Cannot create file, directory with the same name already exists",{filePath:u});f(s,"INFO","File already exists, skipping creation",{filePath:u});break}await c.promises.mkdir(h.dirname(p),{recursive:!0});const R=n.contents||"";await c.promises.writeFile(p,R,"utf8"),f(s,"INFO","File created successfully",{filePath:u});break}case"delete":{c.existsSync(p)?(await c.promises.stat(p)).isDirectory()?(await c.promises.rm(p,{recursive:!0,force:!0}),f(s,"INFO","Directory deleted successfully",{filePath:u})):(await c.promises.unlink(p),f(s,"INFO","File deleted successfully",{filePath:u})):f(s,"WARN","The file or directory to be deleted does not exist",{filePath:u});break}case"rename":{const R=n.renameFrom;if(!R||typeof R!="string"){f(s,"WARN","Rename operation missing renameFrom",{filePath:u});break}const b=h.normalize(R).replace(/^[\/\\]+/,""),v=h.join(g,b),I=h.resolve(v);if(!I.startsWith(F+h.sep)&&I!==F){f(s,"WARN","Source path is not secure, skipping rename",{sourcePath:b,targetPath:u});break}if(c.existsSync(v)){const C=(await c.promises.stat(v)).isDirectory();await c.promises.mkdir(h.dirname(p),{recursive:!0}),await c.promises.rename(v,p),f(s,"INFO",C?"Directory renamed successfully":"File renamed successfully",{sourcePath:b,targetPath:u})}else f(s,"WARN","The file or directory to be renamed does not exist",{sourcePath:b});break}case"modify":{if(!c.existsSync(p)){f(s,"WARN","The file to be modified does not exist",{filePath:u});break}if((await c.promises.stat(p)).isDirectory()){f(s,"INFO","The target is a directory, skipping modification",{filePath:u});break}const b=typeof n.contents=="string"?n.contents:"";if(await c.promises.readFile(p,"utf8")===b){f(s,"INFO","File content has no changes, skipping write",{filePath:u});break}await c.promises.writeFile(p,b,"utf8"),f(s,"INFO","File modified successfully",{filePath:u});break}default:{f(s,"WARN","Unsupported operation type",{operation:l,filePath:u});break}}}return f(s,"INFO","User files updated successfully",{userId:d,cId:m,filesCount:t.length,elapsedMs:Date.now()-a}),{success:!0,message:"User files updated successfully",userId:d,cId:m,filesCount:t.length}}catch(n){throw f(s,"ERROR","User files updated failed",{userId:d,cId:m,error:n.message,elapsedMs:Date.now()-a}),new z(`User files updated failed: ${n.message}`,{userId:d,cId:m,originalError:n.message})}}async function ce(e,r,t,i,o,a=null){const s=Date.now(),d=`computer:${e}:${r}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!t)throw new E("file cannot be empty",{field:"file"});if(!i||typeof i!="string")throw new E("filePath cannot be empty",{field:"filePath"});const m=String(e),w=String(r),g=o&&o.trim()?o.trim():null,n=g||O(a,m,w);c.existsSync(n)||c.mkdirSync(n,{recursive:!0});const l=h.normalize(i).replace(/^[\/\\]+/,""),y=h.join(n,l),u=h.resolve(y),p=h.resolve(n);if(!u.startsWith(p+h.sep)&&u!==p)throw new E("File path is not secure, cannot exceed user directory",{field:"filePath",providedPath:i,resolvedPath:u});try{if(await c.promises.mkdir(h.dirname(y),{recursive:!0}),t.buffer)await c.promises.writeFile(y,t.buffer);else if(typeof t.contents=="string")await c.promises.writeFile(y,t.contents,"utf8");else throw new E("File content format is incorrect",{field:"file",hasBuffer:!!t.buffer,hasContents:typeof t.contents});return f(d,"INFO","File uploaded successfully",{userId:m,cId:w,filePath:l,targetPath:u,fileSize:t.buffer?t.buffer.length:t.contents?Buffer.byteLength(t.contents,"utf8"):0,elapsedMs:Date.now()-s}),{success:!0,message:"File uploaded successfully",fileSize:t.buffer?t.buffer.length:t.contents?Buffer.byteLength(t.contents,"utf8"):0}}catch(S){throw f(d,"ERROR","File upload failed",{userId:m,cId:w,filePath:l,error:S.message,elapsedMs:Date.now()-s}),new z(`File upload failed: ${S.message}`,{userId:m,cId:w,filePath:l,originalError:S.message})}}async function Je(e,r,t,i,o,a=null){const s=Date.now(),d=`computer:${e}:${r}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!Array.isArray(t))throw new E("files must be an array",{field:"files"});if(!Array.isArray(i))throw new E("filePaths must be an array",{field:"filePaths"});if(t.length!==i.length)throw new E(`File count (${t.length}) does not match path count (${i.length})`,{field:"filePaths"});f(d,"DEBUG","Start batch uploading files",{userId:e,cId:r,filesCount:t.length});const m=[];try{for(let n=0;n<t.length;n++){const l=t[n],y=i[n];if(!l){f(d,"WARN","Empty file object encountered in batch upload, skipping",{index:n,filePath:y}),m.push({success:!1,filePath:y,error:"Empty file object"});continue}if(!y||typeof y!="string"){f(d,"WARN","Invalid file path in batch upload, skipping",{index:n,originalname:l.originalname}),m.push({success:!1,filePath:y||"",originalname:l.originalname,error:"Invalid file path"});continue}try{const u=await ce(e,r,l,y,o,a);m.push({success:!0,filePath:y,originalname:l.originalname,...u})}catch(u){f(d,"ERROR","Single file upload failed in batch upload",{filePath:y,originalname:l.originalname,error:u.message}),m.push({success:!1,filePath:y,originalname:l.originalname,error:u.message})}}const w=m.filter(n=>n.success).length,g=m.filter(n=>!n.success).length;return f(d,"INFO","Batch upload files completed",{userId:e,cId:r,totalCount:t.length,successCount:w,failCount:g,elapsedMs:Date.now()-s}),{success:!0,message:"Batch upload completed",totalCount:t.length,successCount:w,failCount:g,results:m}}catch(w){throw f(d,"ERROR","Batch upload files failed",{userId:e,cId:r,error:w.message,elapsedMs:Date.now()-s}),new z(`Batch upload files failed: ${w.message}`,{userId:e,cId:r,originalError:w.message})}}async function Qe(e,r,t,i=null){const o=Date.now(),a=`computer:${e}:${r}`,s=me(i);if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!s&&!i?.workspacePath)throw new z("COMPUTER_WORKSPACE_DIR is not configured, cannot create zip");const d=String(e),m=String(r),w=t&&t.trim()?t.trim():null,g=w||O(i,d,m);if(!c.existsSync(g)){const S=`${d}_${m}.zip`;f(a,"WARN","Workspace directory does not exist, returning empty zip",{targetDir:g,userId:d,cId:m,zipFileName:S});const F=fe("zip",{zlib:{level:9}});return F.append(null,{name:`${d}_${m}/`,type:"directory"}),F.on("warning",R=>{if(R.code==="ENOENT")f(a,"WARN","Encountered file problem when creating empty zip",{message:R.message,code:R.code});else throw f(a,"ERROR","Encountered warning when creating empty zip",{message:R.message,code:R.code}),R}),F.on("error",R=>{f(a,"ERROR","Failed to create empty zip",{message:R.message})}),{archive:F,zipFileName:S}}const n=`${d}_${m}.zip`;f(a,"DEBUG","Start creating workspace directory zip",{targetDir:g,zipFileName:n});const l=fe("zip",{zlib:{level:9}}),y=P.CONTENT_TRAVERSE_EXCLUDE_FILES||[],u=P.TRAVERSE_EXCLUDE_DIRS||[],p=await Ie(g,y,u,a);if(p>H){const S=H/1024/1024,F=(p/1024/1024).toFixed(2);throw f(a,"WARN","Download rejected due to oversized workspace",{targetDir:g,downloadableSize:p,maxSizeBytes:H}),new E(`Download failed: total file size ${F}MB exceeds limit ${S}MB`,{field:"downloadSize",downloadableSize:p,maxSizeBytes:H})}return l.directory(g,`${d}_${m}`,S=>{const F=S.name||"",R=F.split(/[\/\\]/).filter(Boolean);if(R.some(v=>v.startsWith(".")))return!1;const b=R[R.length-1];if(y.includes(b)||R.some(v=>u.includes(v)))return!1;try{const v=h.join(g,F),I=c.lstatSync(v);if(I.isSymbolicLink()||I.nlink>1)return!1}catch(v){return f(a,"WARN","Error occurred when detecting link file, skipping",{filePath:F,error:v.message}),!1}return S}),l.on("warning",S=>{if(S.code==="ENOENT")f(a,"WARN","Encountered file problem when creating zip",{message:S.message,code:S.code});else throw f(a,"ERROR","Encountered warning when creating zip",{message:S.message,code:S.code}),S}),l.on("error",S=>{f(a,"ERROR","Failed to create zip",{message:S.message,elapsedMs:Date.now()-o})}),l.on("end",()=>{f(a,"INFO","Workspace directory zip created successfully",{targetDir:g,zipFileName:n,elapsedMs:Date.now()-o})}),{archive:l,zipFileName:n}}async function et(e,r,t=200,i=null){const o=Date.now(),a=`computer:${e}:${r}`;if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});const s=String(e),d=String(r),m=Ae(i,s,d);if(!m)return f(a,"WARN","COMPUTER_WORKSPACE_DIR is not configured"),{success:!0,message:"Log workspace is not configured",logs:[],totalLines:0,startIndex:1,logFileName:null};if(!c.existsSync(m))return f(a,"DEBUG","Log directory does not exist",{logDir:m}),{success:!0,message:"Log directory does not exist",logs:[],totalLines:0,startIndex:1,logFileName:null};const g=(await c.promises.readdir(m,{withFileTypes:!0})).filter(I=>I.isFile()).map(I=>I.name);if(g.length===0)return f(a,"DEBUG","No log file found",{logDir:m}),{success:!0,message:"No log file found",logs:[],totalLines:0,startIndex:1,logFileName:null};const n=[];for(const I of g){const k=h.join(m,I),C=await c.promises.stat(k);n.push({name:I,fullPath:k,mtime:C.mtimeMs,size:C.size})}n.sort((I,k)=>k.mtime-I.mtime);const l=n[0],u=(await c.promises.readFile(l.fullPath,"utf8")).split(`
2
+ `).filter(I=>I.length>0),p=u.length,S=Math.max(1,t),F=Math.max(0,p-S),R=F+1,v=u.slice(F).map((I,k)=>({line:R+k,content:I}));return f(a,"DEBUG","Get latest logs",{fileName:l.name,fileSize:l.size,totalLines:p,returnedLines:v.length,elapsedMs:Date.now()-o}),{success:!0,message:"Get log successfully",logs:v,totalLines:p,startIndex:R,logFileName:l.name}}async function ke(e,r){try{const i=(await c.promises.readdir(e,{withFileTypes:!0})).filter(d=>!d.name.startsWith(".")&&d.name!=="node_modules");if(i.length!==1||!i[0].isDirectory())return;const o=h.join(e,i[0].name),a=h.join(e,`..tmp_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{await c.promises.rename(o,a)}catch(d){if(d.code==="EXDEV")await c.promises.cp(o,a,{recursive:!0}),await c.promises.rm(o,{recursive:!0,force:!0});else throw d}const s=await c.promises.readdir(a);for(const d of s){const m=h.join(a,d),w=h.join(e,d);try{await c.promises.rename(m,w)}catch(g){if(g.code==="EXDEV")(await c.promises.lstat(m)).isDirectory()?await Le(m,w):(await c.promises.copyFile(m,w),await c.promises.unlink(m));else throw g}}await c.promises.rm(a,{recursive:!0,force:!0}),f(r,"INFO","Removed single top-level directory from imported zip",{removedDir:i[0].name})}catch(t){f(r,"WARN","Failed to remove top-level directory from imported zip, continuing",{error:t.message})}}async function tt(e,r){if(!c.existsSync(e)){await c.promises.mkdir(e,{recursive:!0});return}const t=await c.promises.readdir(e,{withFileTypes:!0});for(const i of t){if(B.has(i.name))continue;const o=h.join(e,i.name);await c.promises.rm(o,{recursive:!0,force:!0}),f(r,"DEBUG","Removed workspace entry during import",{name:i.name})}}async function rt(e,r,t){if(await c.promises.mkdir(r,{recursive:!0}),!c.existsSync(e)){await c.promises.mkdir(e,{recursive:!0});return}const i=await c.promises.readdir(e,{withFileTypes:!0});for(const o of i){if(B.has(o.name))continue;const a=h.join(e,o.name),s=h.join(r,o.name);await oe(a,s),f(t,"DEBUG","Backed up workspace entry before import",{name:o.name})}}async function it(e,r,t){if(!c.existsSync(e))return;const i=await c.promises.readdir(e,{withFileTypes:!0});for(const o of i){if(B.has(o.name))continue;const a=h.join(e,o.name),s=h.join(r,o.name);await oe(a,s)}f(t,"INFO","Workspace restored from backup after import failure")}async function nt(e,r,t){const i=await c.promises.readdir(e,{withFileTypes:!0}),o=[];try{for(const a of i){if(B.has(a.name)){f(t,"INFO","Skipping preserved entry from imported zip",{name:a.name});continue}const s=h.join(e,a.name),d=h.join(r,a.name);await oe(s,d),o.push(a.name)}}catch(a){throw a.mergedEntries=o,a}}async function st(e,r,t,i,o=null){const a=Date.now(),s=`computer:${e}:${r}`,d=me(o);if(!e)throw new E("userId cannot be empty",{field:"userId"});if(!r)throw new E("cId cannot be empty",{field:"cId"});if(!t||!t.path)throw new E("file is required",{field:"file"});if(!d&&!o?.workspacePath)throw new z("COMPUTER_WORKSPACE_DIR is not configured");if(h.extname(t.originalname||t.filename||"").toLowerCase()!==".zip")throw new E("Only zip files are supported",{field:"file",originalName:t.originalname});const w=String(e),g=String(r),n=i&&i.trim()?i.trim():O(o,w,g),l=h.join(n,".tmp"),y=h.join(l,`import_project_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);let u=null;f(s,"INFO","Start importing project from zip",{userId:w,cId:g,targetDir:n,fileName:t.originalname});try{await c.promises.mkdir(n,{recursive:!0}),await c.promises.mkdir(l,{recursive:!0}),await c.promises.mkdir(y,{recursive:!0}),await _e(t.path,y),await ke(y,s),u=h.join(l,`import_backup_${Date.now()}_${Math.round(Math.random()*1e6)}`),await rt(n,u,s);try{await nt(y,n,s)}catch(p){throw f(s,"ERROR","Merge imported project failed, restoring workspace",{error:p.message,mergedEntries:p.mergedEntries||[]}),await tt(n,s),await it(u,n,s),p}return c.existsSync(u)&&(await c.promises.rm(u,{recursive:!0,force:!0}),u=null),f(s,"INFO","Project imported successfully",{userId:w,cId:g,targetDir:n,elapsedMs:Date.now()-a}),{success:!0,message:"Project imported successfully",userId:w,cId:g,targetDir:n}}catch(p){throw f(s,"ERROR","Failed to import project",{userId:w,cId:g,targetDir:n,error:p.message,elapsedMs:Date.now()-a}),p instanceof E||p instanceof z||p instanceof Pe?p:new z(`Failed to import project: ${p.message}`,{userId:w,cId:g,originalError:p.message})}finally{if(c.existsSync(y))try{await c.promises.rm(y,{recursive:!0,force:!0})}catch(p){f(s,"WARN","Failed to clean import extract directory",{extractRoot:y,error:p.message})}if(u&&c.existsSync(u))try{await c.promises.rm(u,{recursive:!0,force:!0})}catch(p){f(s,"WARN","Failed to clean import backup directory",{backupDir:u,error:p.message})}if(t?.path&&c.existsSync(t.path))try{await c.promises.unlink(t.path)}catch(p){f(s,"WARN","Failed to clean uploaded zip file",{tempZipPath:t.path,error:p.message})}}}async function ot(e,r,t,i,o,a=null){const s=`computer:${e}:${r}`;if(!t||typeof t!="string"||!t.trim())throw new E("fileName cannot be empty",{field:"fileName"});if(i!=null&&typeof i!="string")throw new E("content must be a string",{field:"content"});const d=t.trim(),m=i??"";return f(s,"INFO","Generate file from text content",{userId:e,cId:r,fileName:d,contentLength:m.length}),{...await ce(e,r,{contents:m,originalname:h.basename(d),size:Buffer.byteLength(m,"utf8")},d,o,a),message:"File generated successfully",fileName:d}}export{X as escapesRoot,Ge as getFileList,Ve as getFileMeta,ze as resolveExistingFile,Ke as searchFiles,Ye as updateFiles,ce as uploadFile,Je as uploadFiles,ot as generateFile,Qe as downloadAllFiles,et as getLatestLogs,st as importProject,B as IMPORT_PROJECT_PRESERVED_ENTRIES,ke as removeTopLevelDir};
@@ -1,14 +1,14 @@
1
- import n from"fs";import l from"path";import{exec as ve}from"child_process";import ze from"cross-spawn";import Me from"archiver";import ee from"../../appConfig/index.js";import{extractZip as Y}from"../common/zipUtils.js";import{ValidationError as w,SystemError as U,FileError as te}from"../error/errorHandler.js";import{log as o}from"../log/logUtils.js";import{createPnpmNpmrc as We}from"../common/npmrcUtils.js";import{ensurePrimaryAgentDirs as ye,syncAgents as fe,linkWorkspaceToAgentStore as ge,forceDirSymlink as Te,copyDirContents as Ce,isWorkspaceSkillsSymlinked as Le}from"../common/AgentWorkspaceUtils.js";import{getProjectStoreRoot as Be,getAgentStorePath as Se,readManifest as Ue,writeManifest as Ze,reverseRefs as Ge,ensureAgentStoreDirs as De,tryAcquireAgentStoreLock as de,releaseAgentStoreLock as xe,pruneAgentSkills as Ke,installSkillDir as ne,replaceAgentsDir as qe,agentSkillExists as Ve}from"../common/agentStoreUtils.js";import{writeAgentHookConfigs as Ne}from"./hookConfigUtils.js";import{ensureWorkspaceDir as J,resolveWorkspaceDir as Je,resolveWorkspaceRoot as re}from"./workspaceContext.js";function oe(e){if(!e)return[];if(Array.isArray(e))return e.map(t=>typeof t=="string"?t.trim():t&&typeof t=="object"&&typeof t.url=="string"?t.url.trim():"").filter(Boolean);if(typeof e=="object")return Object.values(e).map(t=>typeof t=="string"?t.trim():"").filter(Boolean);if(typeof e=="string"){const t=e.trim();if(!t)return[];try{const i=JSON.parse(t);return oe(i)}catch{}return[t]}return[]}async function je(e,t,i){try{return await Te(e,t,i),!0}catch(a){return o(i,"WARN","Skill view dir link unavailable, fallback to copy",{linkPath:e,targetDir:t,platform:process.platform,error:a.message}),!1}}async function He(e,t,i){const a=process.platform==="win32"?l.resolve(t):l.relative(l.dirname(e),t).split(l.sep).join("/");try{return await n.promises.symlink(a,e,"file"),!0}catch(s){return o(i,"WARN","Skill view file link unavailable, fallback to copy",{linkPath:e,targetFile:t,platform:process.platform,error:s.message}),!1}}async function Re(e,t,i,a,s,r={}){const p=Be(e,a);if(!p||!i)return!1;const{skillNames:m=null,subagentNames:c=null}=r,N=l.join(p,".view.lock");await n.promises.mkdir(p,{recursive:!0});const R=Date.now()+3e4;let h=!1;for(;Date.now()<R&&!h;)try{await n.promises.writeFile(N,String(Date.now()),{flag:"wx"}),h=!0}catch(g){if(g.code!=="EEXIST")throw g;try{const S=await n.promises.stat(N);if(Date.now()-S.mtimeMs>5*60*1e3){await n.promises.rm(N,{force:!0});continue}}catch{}await new Promise(S=>setTimeout(S,200))}if(!h)throw new U("Agent skill view is busy, please retry");try{const g=await Ue(p),S=g.agents[String(i)]||{skills:[],subagents:[]};m&&(S.skills=[...new Set(m.map(y=>String(y||"").trim()).filter(Boolean))]),c&&(S.subagents=[...new Set(c.map(y=>String(y||"").trim()).filter(Boolean))]),g.agents[String(i)]=S,await Ze(p,g);const f=[".agents",".claude",".opencode",".codex"],k=[];for(const y of f)for(const d of["skills","agents"]){const P=l.join(t,y,d);if(await n.promises.mkdir(l.dirname(P),{recursive:!0}),y===".agents")try{(await n.promises.lstat(P)).isSymbolicLink()&&await n.promises.rm(P,{recursive:!0,force:!0})}catch{}else{const z=l.join(t,".agents",d),D=process.platform==="win32"?l.resolve(z):"../.agents/"+d;let $=!1;try{$=(await n.promises.lstat(P)).isSymbolicLink()&&n.readlinkSync(P)===D}catch{}$||(await n.promises.rm(P,{recursive:!0,force:!0}),await je(P,z,s)||k.push([z,P]));continue}await n.promises.mkdir(P,{recursive:!0})}const L=async(y,d,P)=>{P==="skills"||(await n.promises.lstat(y)).isDirectory()?(k.length>0||!await je(d,y,s))&&await Ce(y,d):await He(d,y,s)||await n.promises.copyFile(y,d)},F=l.join(t,".agents");for(const y of["skills","agents"]){const P=Ge(g,y==="skills"?"skills":"subagents"),z=l.join(F,y),D=n.existsSync(z)?await n.promises.readdir(z):[],$=new Set(Object.keys(P));for(const j of D){const E=l.join(z,j);ce(E)||$.has(j)||(await n.promises.rm(E,{recursive:!0,force:!0}),o(s,"INFO","Skill view entry removed (no agent references it)",{kind:y,name:j}))}for(const[j,E]of Object.entries(P)){const O=l.join(z,j);if(ce(O))continue;const C=[...new Set([String(i),...E.map(String)])];let u=null;for(const W of C){const H=l.join(Se(e,W,a),y,j);if(n.existsSync(H)){u=H;break}}if(!u){await n.promises.rm(O,{recursive:!0,force:!0});continue}const _=process.platform==="win32"?l.resolve(u):l.relative(z,u).split(l.sep).join("/");let x=!1;try{x=!(await n.promises.lstat(O)).isSymbolicLink()||n.readlinkSync(O)!==_}catch{x=!0}x&&(await n.promises.rm(O,{recursive:!0,force:!0}),await L(u,O,y))}if(y==="skills"){const j=[...new Set([String(i),...Object.keys(g.agents||{})])];for(const E of j){const O=l.join(Se(e,E,a),"skills");let C=[];try{C=await n.promises.readdir(O,{withFileTypes:!0})}catch{continue}for(const u of C){if(!u.isDirectory())continue;const _=l.join(O,u.name);if(!ce(_))continue;const x=l.join(z,u.name);n.existsSync(x)||(await L(_,x,y),o(s,"INFO","Dynamic skill linked into view",{owner:E,name:u.name}))}}}}for(const[y,d]of k)await n.promises.rm(d,{recursive:!0,force:!0}),await n.promises.cp(y,d,{recursive:!0,dereference:!0,force:!0});return o(s,"INFO","Shared skill view synced",{userWorkspaceRoot:t,projectStoreRoot:p,agentId:i,skills:S.skills,subagents:S.subagents,copyFallbackDirs:k.length}),!0}finally{await n.promises.rm(N,{force:!0})}}function _e(e){if(!e)return[];if(Array.isArray(e)){const t=[];for(const i of e)if(typeof i=="string"&&i.trim())t.push({name:null,url:i.trim()});else if(i&&typeof i=="object"){const a=typeof i.url=="string"?i.url.trim():"",s=i.name!=null?String(i.name).trim():"";a&&t.push({name:s||null,url:a})}return t}if(typeof e=="object")return Object.entries(e).map(([t,i])=>({name:String(t||"").trim()||null,url:typeof i=="string"?i.trim():""})).filter(t=>t.url);if(typeof e=="string"){const t=e.trim();if(!t)return[];try{return _e(JSON.parse(t))}catch{return[{name:null,url:t}]}}return[]}function Ye(e){if(!e)return{};let t=e;if(typeof e=="string")try{t=JSON.parse(e)}catch{return{}}if(!t||typeof t!="object"||Array.isArray(t))return{};const i={};for(const[a,s]of Object.entries(t)){const r=String(a||"").trim(),p=typeof s=="string"?s.trim():"";r&&p&&(i[r]=p)}return i}function Pe(e){if(!e)return[];if(Array.isArray(e))return e.map(t=>String(t||"").trim()).filter(Boolean);if(typeof e=="string"){const t=e.trim();if(!t)return[];try{const i=JSON.parse(t);if(Array.isArray(i))return i.map(a=>String(a||"").trim()).filter(Boolean)}catch{}return t.split(",").map(i=>i.trim()).filter(Boolean)}return[]}async function le(e,t,i){let a;try{a=await fetch(e)}catch(m){throw new te(`Failed to download skill zip from url: ${e}`,{url:e,reason:m.message})}if(!a.ok)throw new te(`Failed to download skill zip from url: ${e}`,{url:e,status:a.status,statusText:a.statusText});const s=a.headers.get("content-type")||"";s&&!s.includes("zip")&&!s.includes("octet-stream")&&o(i,"WARN","Downloaded skill url content-type is not typical zip",{url:e,contentType:s});const r=await a.arrayBuffer(),p=Buffer.from(r);await n.promises.writeFile(t,p)}async function ie(e,t){const i=l.join(e,t);if(n.existsSync(i)&&(await n.promises.lstat(i)).isDirectory())return i;const a=await n.promises.readdir(e,{withFileTypes:!0});for(const s of a){if(!s.isDirectory())continue;const r=l.join(e,s.name),p=l.join(r,t);if(n.existsSync(p)&&(await n.promises.lstat(p)).isDirectory())return p}return null}async function Ft(e){return ie(e,"skills")}const Xe=".dynamic_add.lock";function ce(e){const t=l.join(e,Xe);return n.existsSync(t)&&n.statSync(t).isFile()}async function X(e){n.existsSync(e)&&await n.promises.rm(e,{recursive:!0,force:!0})}async function Q(e,t){try{await n.promises.rename(e,t)}catch(i){if(i.code==="EXDEV"){async function a(s,r){if((await n.promises.lstat(s)).isDirectory()){await n.promises.mkdir(r,{recursive:!0});const m=await n.promises.readdir(s);for(const c of m)await a(l.join(s,c),l.join(r,c))}else await n.promises.copyFile(s,r)}await a(e,t),await n.promises.rm(e,{recursive:!0,force:!0})}else throw i}}async function we(e){const i=(await n.promises.readdir(e,{withFileTypes:!0})).filter(s=>s.isDirectory()&&!s.name.startsWith("."));return i.find(s=>s.name==="skills")?(await n.promises.readdir(l.join(e,"skills"),{withFileTypes:!0})).filter(r=>r.isDirectory()&&!r.name.startsWith(".")).map(r=>({name:r.name,sourcePath:l.join(e,"skills",r.name)})):i.map(s=>({name:s.name,sourcePath:l.join(e,s.name)}))}const se=8;async function ae(e,t,i){if(!e||e.length===0)return;const a=Math.max(1,Math.min(t,e.length));let s=0;await Promise.all(Array.from({length:a},async()=>{for(;;){const r=s++;if(r>=e.length)return;await i(e[r],r)}}))}async function Qe(e){const{userId:t,cId:i,agentId:a,file:s,skillUrls:r,skillUrlMap:p,skillNames:m,updateSkillNames:c,mcpServersConfig:N,permissionsConfig:R,hooksConfig:h,hookScripts:g,service:S}=e,f=Date.now(),k=`computer:${t}:${i}`,L=Ye(p),F=_e(r),y=Pe(m),d=c!=null,P=new Set(Pe(c)),z=[],D=[],$=new Set,j=new Set;let E=null,O=!1;if(!t)throw new w("userId cannot be empty",{field:"userId"});if(!i)throw new w("cId cannot be empty",{field:"cId"});if(!a)throw new w("agentId cannot be empty",{field:"agentId"});const C=re(S),u=await J(S,t,i,k),_=l.join(u,".tmp");await n.promises.mkdir(u,{recursive:!0}),await n.promises.mkdir(_,{recursive:!0});const{agentStorePath:x,skillsDir:W,agentsDir:H}=await De(t,a,k,S),T=M=>!M||!d||P.has(M)?!0:!Ve(W,M),q=async(M,b,B)=>{const G=l.join(_,`skill_url_${Date.now()}_${B}_${Math.round(Math.random()*1e6)}.zip`);z.push(G);const Z=l.join(_,`skill_url_extract_${Date.now()}_${B}_${Math.round(Math.random()*1e6)}`);D.push(Z),await n.promises.mkdir(Z,{recursive:!0}),o(k,"INFO","Start download skill zip from url",{userId:t,cId:i,agentId:a,skillName:b,skillUrl:M}),await le(M,G,k),await Y(G,Z);const V=await we(Z);if(V.length===0){o(k,"WARN","No skill dir found in downloaded zip",{skillUrl:M,destName:b});return}let v=b?V.find(A=>A.name===b):null;if(!v&&V.length===1&&(v=V[0]),b&&v){if(!T(b)){j.add(b);return}await ne(v.sourcePath,W,b,{asDynamic:!1}),$.add(b),o(k,"INFO","Skill from url updated to agent store",{userId:t,cId:i,agentId:a,skillUrl:M,skillName:b,zipDirName:v.name});return}for(const A of V){if(!T(A.name)){j.add(A.name);continue}await ne(A.sourcePath,W,A.name,{asDynamic:!1}),$.add(A.name),o(k,"INFO","Skill from url updated to agent store",{userId:t,cId:i,agentId:a,skillUrl:M,skillName:A.name})}};await Ne(u,{mcpServersConfig:N,hooksConfig:h,permissionsConfig:R,hookScripts:g},k);try{const M=await de(x,k);if(!M.acquired)O=!0;else{E=M.lockPath;const G=Date.now();let Z=null;if(s){if(!s.path)throw new w("Uploaded file has no valid path",{field:"file.path"});if(l.extname(s.originalname||s.filename||"").toLowerCase()!==".zip")throw new w("Only zip files are supported",{field:"file",originalName:s.originalname});const A=l.join(_,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);D.push(A),await n.promises.mkdir(A,{recursive:!0}),await Y(s.path,A);const K=await ie(A,"skills");if(K){const me=(await n.promises.readdir(K,{withFileTypes:!0})).filter(I=>I.isDirectory());await ae(me,se,async I=>{if(!T(I.name)){j.add(I.name);return}await ne(l.join(K,I.name),W,I.name,{asDynamic:!1}),$.add(I.name)})}Z=await ie(A,"agents")}const V=Object.entries(L);if(V.length>0){const v=[];for(const[A,K]of V){if(!T(A)){j.add(A),o(k,"INFO","Skip download skill zip (already in agent store)",{userId:t,cId:i,agentId:a,skillName:A,skillUrl:K});continue}v.push({skillName:A,skillUrl:K})}await ae(v,se,async(A,K)=>{await q(A.skillUrl,A.skillName,K)})}else{const v=[];for(let A=0;A<F.length;A++){const{name:K,url:me}=F[A];if(K&&!T(K)){j.add(K);continue}v.push({skillUrl:me,hintedName:K,index:A})}await ae(v,se,async A=>{await q(A.skillUrl,A.hintedName,A.index)})}await qe(Z,H),await Ke(W,y,k),o(k,"INFO","Agent store resources prepared",{userId:t,cId:i,agentId:a,selectiveInstall:d,updatedSkills:Array.from($),skippedSkills:Array.from(j),updateSkillNames:Array.from(P),keepSkillNames:y,skillUrlMapCount:V.length,prepareElapsedMs:Date.now()-G})}let b=null,B=!1;if(S&&(S.isUserApp||S.isNormalProject)&&S.appId){let G=null;try{G=(await n.promises.readdir(H,{withFileTypes:!0})).map(Z=>Z.name)}catch{}B=await Re(t,u,a,S,k,{skillNames:m?y:null,subagentNames:G})}return B||(b=await ge(u,W,H,k)),o(k,"INFO","Workspace created successfully (agent store)",{userId:t,cId:i,agentId:a,agentStorePath:x,updatedSkills:Array.from($),skippedSkills:Array.from(j),skippedStoreUpdate:O,linkElapsedMs:b?.elapsedMs,elapsedMs:Date.now()-f}),{message:O?"Workspace linked to agent store (store update skipped, lock held)":"Workspace created successfully, linked to agent store",workspaceRoot:C,agentStorePath:x,updatedSkills:Array.from($),skippedSkills:Array.from(j),skippedStoreUpdate:O}}catch(M){throw o(k,"ERROR","Failed to create workspace with agent store",{userId:t,cId:i,agentId:a,error:M.message,elapsedMs:Date.now()-f}),M instanceof w||M instanceof te||M instanceof U?M:new U(`Failed to create workspace: ${M.message}`,{userId:t,cId:i,agentId:a})}finally{await xe(E,k);const M=Date.now();for(const b of D)try{n.existsSync(b)&&await n.promises.rm(b,{recursive:!0,force:!0})}catch(B){o(k,"WARN","Failed to clean up temporary extracted zip",{error:B.message})}for(const b of z)try{n.existsSync(b)&&await n.promises.unlink(b)}catch(B){o(k,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:b,error:B.message})}try{s&&s.path&&n.existsSync(s.path)&&await n.promises.unlink(s.path)}catch(b){o(k,"WARN","Failed to clean up uploaded zip file",{tempZipPath:s?.path,error:b.message})}o(k,"INFO","Workspace temp cleanup completed",{userId:t,cId:i,agentId:a,elapsedMs:Date.now()-M,totalElapsedMs:Date.now()-f})}}async function Ie(e,t,i,a,s,r,p,m,c,N,R,h,g=null){if(c!=null&&String(c).trim()!=="")return Qe({userId:e,cId:t,agentId:c,file:i,skillUrls:a,skillUrlMap:h,skillNames:N,updateSkillNames:R,mcpServersConfig:s,permissionsConfig:r,hooksConfig:p,hookScripts:m,service:g});const S=Date.now(),f=`computer:${e}:${t}`,k=oe(a),L=[],F=[],y=new Set;if(!e)throw new w("userId cannot be empty",{field:"userId"});if(!t)throw new w("cId cannot be empty",{field:"cId"});const d=re(g),P=await J(g,e,t,f),z=l.join(P,".tmp"),{skillsDir:D,agentsDir:$,agentTypes:j}=await ye(P);n.existsSync(P)||await n.promises.mkdir(P,{recursive:!0});const E=l.join(z,`preserved_skills_${Date.now()}_${Math.round(Math.random()*1e6)}`);if(n.existsSync(D)){const _=(await n.promises.readdir(D,{withFileTypes:!0})).filter(x=>x.isDirectory()&&ce(l.join(D,x.name)));if(_.length>0){await n.promises.mkdir(E,{recursive:!0});for(const x of _){const W=l.join(D,x.name),H=l.join(E,x.name);await Q(W,H)}o(f,"INFO","\u4FDD\u7559\u542B .dynamic_add.lock \u7684 skill",{preserved:_.map(x=>x.name)})}}if(await X(D),await X($),await n.promises.mkdir(D,{recursive:!0}),await n.promises.mkdir($,{recursive:!0}),n.existsSync(E)){const u=await n.promises.readdir(E,{withFileTypes:!0});for(const _ of u)if(_.isDirectory()){const x=l.join(E,_.name),W=l.join(D,_.name);await Q(x,W)}await X(E)}const O=n.existsSync(D),C=n.existsSync($);if(o(f,"INFO","Deleted old skills and agents directories completed",{userId:e,cId:t,targetSkillsDir:D,targetAgentsDir:$,agentTypes:j,skillsExists:O,agentsExists:C}),await Ne(P,{mcpServersConfig:s,hooksConfig:p,permissionsConfig:r,hookScripts:m},f),!i&&k.length===0){const u=await fe(P,f);return o(f,"INFO","Created workspace (no uploaded file, no skills and agents)",{userId:e,cId:t,workspaceRoot:d,skillsDir:null,agentsDir:null,agentTypes:j,syncAgentsElapsedMs:u?.elapsedMs,elapsedMs:Date.now()-S}),{message:"Workspace created (no uploaded file, no skills and agents)",workspaceRoot:d}}if(i){if(!i.path)throw new w("Uploaded file has no valid path",{field:"file.path"});if(l.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new w("Only zip files are supported",{field:"file",originalName:i.originalname})}o(f,"DEBUG","Start processing workspace resources",{userId:e,cId:t,workspaceRoot:d,hasUploadedZip:!!i,skillUrlsCount:k.length});try{n.existsSync(z)||await n.promises.mkdir(z,{recursive:!0});const u=[];if(i){const T=l.join(z,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);F.push(T),await n.promises.mkdir(T,{recursive:!0}),o(f,"DEBUG","Start extracting uploaded zip file",{extractRoot:T}),await Y(i.path,T),o(f,"DEBUG","Uploaded zip file extracted successfully",{extractRoot:T});const q=await ie(T,"skills"),M=await ie(T,"agents");if(q){await n.promises.mkdir(D,{recursive:!0});const b=await n.promises.readdir(q,{withFileTypes:!0});for(const B of b){if(!B.isDirectory())continue;const G=l.join(q,B.name),Z=l.join(D,B.name);n.existsSync(Z)&&await X(Z),await Q(G,Z),y.add(B.name)}u.push("skills"),o(f,"INFO","skills updated to workspace",{userId:e,cId:t,workspaceRoot:d,targetSkillsDir:D,agentTypes:j})}else o(f,"INFO","skills directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:T});M?(await X($),await Q(M,$),u.push("agents"),o(f,"INFO","agents updated to workspace",{userId:e,cId:t,workspaceRoot:d,targetAgentsDir:$,agentTypes:j})):o(f,"INFO","agents directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:T})}for(let T=0;T<k.length;T+=1){const q=k[T],M=l.join(z,`skill_url_${Date.now()}_${T}_${Math.round(Math.random()*1e6)}.zip`);L.push(M);const b=l.join(z,`skill_url_extract_${Date.now()}_${T}_${Math.round(Math.random()*1e6)}`);F.push(b),await n.promises.mkdir(b,{recursive:!0}),o(f,"INFO","Start download skill zip from url",{userId:e,cId:t,skillUrl:q}),await le(q,M,f),o(f,"INFO","Skill zip downloaded, start extracting",{userId:e,cId:t,skillUrl:q,downloadedZipPath:M}),await Y(M,b);const G=(await n.promises.readdir(b,{withFileTypes:!0})).filter(v=>v.isDirectory()&&!v.name.startsWith(".")),V=G.find(v=>v.name==="skills")?(await n.promises.readdir(l.join(b,"skills"),{withFileTypes:!0})).filter(v=>v.isDirectory()&&!v.name.startsWith(".")).map(v=>({name:v.name,sourcePath:l.join(b,"skills",v.name)})):G.map(v=>({name:v.name,sourcePath:l.join(b,v.name)}));if(V.length===0){o(f,"WARN","No skill directory found after extracting skill url zip",{userId:e,cId:t,skillUrl:q,extractRoot:b});continue}await n.promises.mkdir(D,{recursive:!0});for(const v of V){const A=l.join(D,v.name);n.existsSync(A)&&await X(A),await Q(v.sourcePath,A),y.add(v.name),o(f,"INFO","Skill from url updated to workspace",{userId:e,cId:t,skillUrl:q,skillName:v.name,destSkillPath:A})}u.includes("skills")||u.push("skills")}u.length===0&&o(f,"WARN","No valid skills or agents directories found",{userId:e,cId:t,hasUploadedZip:!!i,skillUrlsCount:k.length});const _=Array.from(y),x=u.length>0?`Workspace created successfully, ${u.join(" and ")} updated`:"Workspace created successfully (skills and agents directories not found)",W=Date.now()-S;o(f,"INFO","Workspace resources prepared",{userId:e,cId:t,updatedDirs:u,updatedSkills:_,agentTypes:j,prepareElapsedMs:W});const H=await fe(P,f);return o(f,"INFO",x,{userId:e,cId:t,updatedDirs:u,updatedSkills:_,agentTypes:j,prepareElapsedMs:W,syncAgentsElapsedMs:H?.elapsedMs,elapsedMs:Date.now()-S}),{message:x,workspaceRoot:d,updatedSkills:_}}catch(u){throw o(f,"ERROR","Failed to process workspace resources",{userId:e,cId:t,error:u.message,elapsedMs:Date.now()-S}),u instanceof w||u instanceof te||u instanceof U?u:new U(`Failed to create workspace: ${u.message}`,{userId:e,cId:t})}finally{const u=Date.now();try{for(const _ of F)n.existsSync(_)&&await n.promises.rm(_,{recursive:!0,force:!0})}catch(_){o(f,"WARN","Failed to clean up temporary extracted zip",{error:_.message})}for(const _ of L)try{n.existsSync(_)&&await n.promises.unlink(_)}catch(x){o(f,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:_,error:x.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(_){o(f,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:_.message})}o(f,"INFO","Workspace temp cleanup completed",{userId:e,cId:t,elapsedMs:Date.now()-u,totalElapsedMs:Date.now()-S})}}async function Fe(e,t,i,a,s,r=null){const p=Date.now(),m=`computer:${e}:${t}`,c=oe(s),N=[],R=[],h=new Set;let g=null;if(!e)throw new w("userId cannot be empty",{field:"userId"});if(!t)throw new w("cId cannot be empty",{field:"cId"});if(!i)throw new w("agentId cannot be empty",{field:"agentId"});if(!a&&c.length===0)throw new w("file or skillUrls cannot both be empty",{field:"file|skillUrls"});const S=re(r),f=await J(r,e,t,m),k=l.join(f,".tmp");await n.promises.mkdir(f,{recursive:!0}),await n.promises.mkdir(k,{recursive:!0});const{agentStorePath:L,skillsDir:F,agentsDir:y}=await De(e,i,m,r);try{let d=await de(L,m);if(!d.acquired)for(let j=1;j<=5&&!d.acquired;j++)await new Promise(E=>setTimeout(E,200*j)),d=await de(L,m);if(!d.acquired)throw new U("Agent store is busy, please retry",{userId:e,cId:t,agentId:i});if(g=d.lockPath,a){if(!a.path)throw new w("Uploaded file has no valid path",{field:"file.path"});const j=l.join(k,`push_skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);R.push(j),await n.promises.mkdir(j,{recursive:!0}),await Y(a.path,j);const E=await we(j),O=await ie(j,"skills"),C=O?(await n.promises.readdir(O,{withFileTypes:!0})).filter(u=>u.isDirectory()).map(u=>({name:u.name,sourcePath:l.join(O,u.name)})):E;await ae(C,se,async u=>{await ne(u.sourcePath,F,u.name,{asDynamic:!0}),h.add(u.name)})}await ae(c,se,async(j,E)=>{const O=l.join(k,`push_skill_url_${Date.now()}_${E}_${Math.round(Math.random()*1e6)}.zip`);N.push(O);const C=l.join(k,`push_skill_url_extract_${Date.now()}_${E}_${Math.round(Math.random()*1e6)}`);R.push(C),await n.promises.mkdir(C,{recursive:!0}),await le(j,O,m),await Y(O,C);const u=await we(C);for(const _ of u)await ne(_.sourcePath,F,_.name,{asDynamic:!0}),h.add(_.name)});let P=null,z=!1;r&&(r.isUserApp||r.isNormalProject)&&r.appId&&(z=await Re(e,f,i,r,m,{})),z||(P=await ge(f,F,y,m));const D=Array.from(h),$=D.length>0?`Pushed ${D.length} skills to agent store: ${D.join(", ")}`:"No valid skill directories found in file or skillUrls";return o(m,"INFO",$,{userId:e,cId:t,agentId:i,updatedSkills:D,linkElapsedMs:P?.elapsedMs,elapsedMs:Date.now()-p}),{message:$,workspaceRoot:S,agentStorePath:L,updatedSkills:D}}catch(d){throw o(m,"ERROR","Failed to push skills to agent store",{userId:e,cId:t,agentId:i,error:d.message,elapsedMs:Date.now()-p}),d instanceof w||d instanceof te||d instanceof U?d:new U(`Failed to push skill to workspace: ${d.message}`,{userId:e,cId:t,agentId:i})}finally{await xe(g,m);for(const d of R)try{n.existsSync(d)&&await n.promises.rm(d,{recursive:!0,force:!0})}catch(P){o(m,"WARN","Failed to clean up temporary extracted zip",{error:P.message})}for(const d of N)try{n.existsSync(d)&&await n.promises.unlink(d)}catch(P){o(m,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:d,error:P.message})}try{a&&a.path&&n.existsSync(a.path)&&await n.promises.unlink(a.path)}catch(d){o(m,"WARN","Failed to clean up uploaded zip file",{tempZipPath:a?.path,error:d.message})}}}async function et(e,t,i,a,s,r=null){if(s!=null&&String(s).trim()!==""){if(r&&(r.isUserApp||r.isNormalProject)&&r.appId)return Fe(e,t,s,i,a,r);let F=null;if(r?.isUserApp?F=Je(r,e,t):ee.COMPUTER_WORKSPACE_DIR&&(F=l.join(ee.COMPUTER_WORKSPACE_DIR,String(e),String(t))),Le(F))return Fe(e,t,s,i,a,r);o(`computer:${e}:${t}`,"INFO","Push skills: agentId present but workspace not symlinked, use legacy path",{userId:e,cId:t,agentId:s,userWorkspaceRoot:F})}const m=Date.now(),c=`computer:${e}:${t}`,N=oe(a),R=[],h=[];if(!e)throw new w("userId cannot be empty",{field:"userId"});if(!t)throw new w("cId cannot be empty",{field:"cId"});if(!i&&N.length===0)throw new w("file or skillUrls cannot both be empty",{field:"file|skillUrls"});if(i){if(!i.path)throw new w("Uploaded file has no valid path",{field:"file.path"});if(l.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new w("Only zip files are supported",{field:"file",originalName:i?.originalname})}const g=re(r),S=await J(r,e,t,c),f=l.join(S,".tmp"),{skillsDir:k,agentTypes:L}=await ye(S);try{n.existsSync(S)||await n.promises.mkdir(S,{recursive:!0}),n.existsSync(k)||await n.promises.mkdir(k,{recursive:!0}),n.existsSync(f)||await n.promises.mkdir(f,{recursive:!0});const F=[],y=new Set;if(i){const D=l.join(f,`skill_push_${Date.now()}_${Math.round(Math.random()*1e6)}`);h.push(D),await n.promises.mkdir(D,{recursive:!0}),o(c,"DEBUG","Start extracting skill zip file",{extractRoot:D}),await Y(i.path,D),o(c,"DEBUG","Skill zip file extracted successfully",{extractRoot:D});const $=await ie(D,"skills");if(!$)o(c,"WARN","skills directory not found in uploaded zip",{userId:e,cId:t,extractRoot:D});else{const E=(await n.promises.readdir($,{withFileTypes:!0})).filter(O=>O.isDirectory()&&!O.name.startsWith("."));if(E.length===0)o(c,"WARN","skills directory in uploaded zip has no skill subdirectories",{userId:e,cId:t,skillsDir:$});else for(const O of E){const C=l.join($,O.name),u=l.join(k,O.name);n.existsSync(u)&&await X(u),await Q(C,u),y.add(O.name),o(c,"INFO","skill pushed to workspace from uploaded zip",{userId:e,cId:t,skillName:O.name,destSkillPath:u})}}}for(let D=0;D<N.length;D+=1){const $=N[D],j=l.join(f,`skill_push_url_${Date.now()}_${D}_${Math.round(Math.random()*1e6)}.zip`);R.push(j);const E=l.join(f,`skill_push_url_extract_${Date.now()}_${D}_${Math.round(Math.random()*1e6)}`);h.push(E),await n.promises.mkdir(E,{recursive:!0}),o(c,"INFO","Start download skill zip for push from url",{userId:e,cId:t,skillUrl:$}),await le($,j,c),await Y(j,E);const C=(await n.promises.readdir(E,{withFileTypes:!0})).filter(x=>x.isDirectory()&&!x.name.startsWith(".")),_=C.find(x=>x.name==="skills")?(await n.promises.readdir(l.join(E,"skills"),{withFileTypes:!0})).filter(x=>x.isDirectory()&&!x.name.startsWith(".")).map(x=>({name:x.name,sourcePath:l.join(E,"skills",x.name)})):C.map(x=>({name:x.name,sourcePath:l.join(E,x.name)}));if(_.length===0){o(c,"WARN","No skill directory found after extracting push skill url zip",{userId:e,cId:t,skillUrl:$,extractRoot:E});continue}for(const x of _){const W=l.join(k,x.name);n.existsSync(W)&&await X(W),await Q(x.sourcePath,W),y.add(x.name),o(c,"INFO","skill pushed to workspace from url zip",{userId:e,cId:t,skillUrl:$,skillName:x.name,destSkillPath:W})}}for(const D of y)F.push(D);const d=F.length>0?`Pushed ${F.length} skills: ${F.join(", ")}`:"No valid skill directories found in file or skillUrls",P=Date.now()-m;o(c,"INFO","Push skills resources prepared",{userId:e,cId:t,updatedSkills:F,agentTypes:L,prepareElapsedMs:P});const z=await fe(S,c);return o(c,"INFO",d,{userId:e,cId:t,updatedSkills:F,agentTypes:L,prepareElapsedMs:P,syncAgentsElapsedMs:z?.elapsedMs,elapsedMs:Date.now()-m}),{message:d,workspaceRoot:g,updatedSkills:F}}catch(F){throw o(c,"ERROR","Failed to push skill to workspace",{userId:e,cId:t,error:F.message,elapsedMs:Date.now()-m}),F instanceof w||F instanceof te||F instanceof U?F:new U(`Failed to push skill to workspace: ${F.message}`,{userId:e,cId:t})}finally{const F=Date.now();try{for(const y of h)n.existsSync(y)&&await n.promises.rm(y,{recursive:!0,force:!0})}catch(y){o(c,"WARN","Failed to clean up temporary extracted zip",{error:y.message})}for(const y of R)try{n.existsSync(y)&&await n.promises.unlink(y)}catch(d){o(c,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:y,error:d.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(y){o(c,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:y.message})}o(c,"INFO","Push skills temp cleanup completed",{userId:e,cId:t,elapsedMs:Date.now()-F,totalElapsedMs:Date.now()-m})}}async function tt(e,t,i,a,s=null){const r=Date.now(),p=`computer:${e}:${t}`;if(!e)throw new w("userId cannot be empty",{field:"userId"});if(!t)throw new w("cId cannot be empty",{field:"cId"});if(!i||!i.path)throw new w("file is required",{field:"file"});if(l.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new w("Only zip files are supported",{field:"file",originalName:i.originalname});const c=await J(s,e,t,p),N=l.join(c,".tmp"),R=l.join(N,`template_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{n.existsSync(c)||await n.promises.mkdir(c,{recursive:!0}),n.existsSync(N)||await n.promises.mkdir(N,{recursive:!0}),await n.promises.mkdir(R,{recursive:!0}),o(p,"DEBUG","Start extracting template zip file",{extractRoot:R}),await Y(i.path,R),o(p,"DEBUG","Template zip file extracted successfully",{extractRoot:R});const h=await n.promises.readdir(R,{withFileTypes:!0});for(const g of h){const S=l.join(R,g.name),f=l.join(c,g.name);if(g.isDirectory())await Q(S,f);else{const k=l.dirname(f);n.existsSync(k)||await n.promises.mkdir(k,{recursive:!0}),await n.promises.copyFile(S,f)}}if(o(p,"INFO","Template files extracted to workspace",{userId:e,cId:t,targetDir:c,fileCount:h.length}),ee.GIT_ENABLED&&(a==="true"||a===!0)){const g=await import("../../service/gitService.js");await g.default.init({workspaceType:"taskAgent",userId:e,cId:t}),await g.default.commit({workspaceType:"taskAgent",userId:e,cId:t,message:"Initial commit"}),o(p,"INFO","Git init and initial commit done",{userId:e,cId:t})}return o(p,"INFO","Init project template completed",{userId:e,cId:t,targetDir:c,elapsedMs:Date.now()-r}),{message:"Project template initialized successfully",workspaceRoot:c}}catch(h){throw o(p,"ERROR","Failed to init project template",{userId:e,cId:t,error:h.message,elapsedMs:Date.now()-r}),h instanceof w||h instanceof te||h instanceof U?h:new U(`Failed to init project template: ${h.message}`,{userId:e,cId:t})}finally{try{n.existsSync(R)&&await n.promises.rm(R,{recursive:!0,force:!0})}catch(h){o(p,"WARN","Failed to clean up temporary extracted zip",{error:h.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(h){o(p,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:h.message})}}}async function it(e,t,i,a=null){if(!e)throw new w("userId cannot be empty",{field:"userId"});if(!t)throw new w("cId cannot be empty",{field:"cId"});if(!i||typeof i!="string"||!i.trim())throw new w("command cannot be empty",{field:"command"});const s=await J(a,e,t,"computer");if(!n.existsSync(s))throw new w("workspace directory does not exist",{field:"workDir",workDir:s});const r=`computer:${e}:${t}`;o(r,"INFO","Execute command in workspace",{userId:e,cId:t,workDir:s,command:i});const p=10*60*1e3,m={cwd:s,timeout:p,maxBuffer:50*1024*1024};return ee.BASH_PATH&&(m.shell=ee.BASH_PATH),new Promise((c,N)=>{ve(i,m,(R,h,g)=>{const S=R?R.killed?-1:R.code||1:0,f=R&&R.killed?(g||"")+`
2
- Command timed out after ${p/1e3}s`:g||"";o(r,"INFO","Execute command completed",{userId:e,cId:t,exitCode:S,killed:R?R.killed:!1,stdoutLength:h?h.length:0,stderrLength:f.length}),c({stdout:h||"",stderr:f,exitCode:S})})})}async function nt(e,t,i,a=null){const s=`computer:${e}:${t}`;if(!e)throw new w("userId cannot be empty",{field:"userId"});if(!t)throw new w("cId cannot be empty",{field:"cId"});const r=await J(a,e,t,s);if(!n.existsSync(r))throw new w("workspace directory does not exist",{field:"workDir",workDir:r});const p=ee.ZIP_WORKSPACE_EXCLUDE||[],m=Array.isArray(i)?i:[],c=new Set([...p,...m]),N=`${e}_${t}.zip`,R=Date.now();o(s,"INFO","Zip workspace request",{userId:e,cId:t,targetDir:r,excludeDirs:[...c]});const h=Me("zip",{zlib:{level:9}});return h.directory(r,!1,g=>{const S=g.name||"";if(S.split(/[\/\\]/).filter(Boolean).some(k=>c.has(k)))return!1;try{const k=l.join(r,S);if(n.lstatSync(k).isSymbolicLink())return!1}catch{return!1}return g}),h.on("warning",g=>{g.code==="ENOENT"?o(s,"WARN","Encountered file problem when creating zip",{message:g.message,code:g.code}):o(s,"ERROR","Encountered warning when creating zip",{message:g.message,code:g.code})}),h.on("error",g=>{o(s,"ERROR","Failed to create zip",{message:g.message,elapsedMs:Date.now()-R})}),h.on("end",()=>{o(s,"INFO","Workspace zip created successfully",{targetDir:r,zipFileName:N,elapsedMs:Date.now()-R})}),{archive:h,zipFileName:N}}const he=new Set([...ee.ZIP_WORKSPACE_EXCLUDE||[],"dist-packages"]);function pe(e){let t;try{t=n.readdirSync(e,{withFileTypes:!0})}catch{return null}const i=l.join(e,"scripts","package-platforms.mjs");if(n.existsSync(i))return e;for(const a of t)if(a.isDirectory()&&!he.has(a.name)){const s=pe(l.join(e,a.name));if(s)return s}return null}function ue(e,t,i,a=10*60*1e3){return new Promise(s=>{const r=ze(e,t,{...i,timeout:a});let p="",m="";r.stdout?.on("data",c=>{p+=c.toString()}),r.stderr?.on("data",c=>{m+=c.toString()}),r.on("error",c=>{s({stdout:p,stderr:m+`
3
- Failed to spawn ${e}: ${c.message}`,exitCode:1})}),r.on("close",(c,N)=>{s(c===null&&N?{stdout:p,stderr:m+`
4
- Process killed by signal ${N} (timeout after ${a/1e3}s)`,exitCode:-1}:{stdout:p,stderr:m,exitCode:c??1})})})}function st(e){if(!e)return null;let t=e;t.endsWith(".tar.gz")?t=t.slice(0,-7):t.endsWith(".tar.bz2")?t=t.slice(0,-8):(t.endsWith(".tgz")||t.endsWith(".zip"))&&(t=t.slice(0,-4));const i=t.split("-");if(i.length<4)return null;let a=-1;for(let s=i.length-1;s>=2;s--)if(/^\d+\.\d+\.\d+$/.test(i[s])){a=s;break}return a<3?null:i.slice(2,a).join("-")}async function at(e,t,i,a,s=null){const r=`computer:${e}:${t}`;if(!e)throw new w("userId cannot be empty",{field:"userId"});if(!t)throw new w("cId cannot be empty",{field:"cId"});if(!i)throw new w("agentId cannot be empty",{field:"agentId"});if(!a)throw new w("version cannot be empty",{field:"version"});const p=await J(s,e,t,r);if(!n.existsSync(p))throw new w("workspace directory does not exist",{field:"workDir",workDir:p});o(r,"INFO","Build agent package request",{userId:e,cId:t,agentId:i,version:a});const m=pe(p);if(!m)throw new w("package-platforms.mjs not found in workspace",{field:"script"});o(r,"INFO","Found package script",{projectDir:m});const c=await be(m,r);if(o(r,"INFO","pnpm install completed",{exitCode:c.exitCode,stderrLength:c.stderr.length}),c.exitCode!==0)throw o(r,"ERROR","pnpm install failed",{stderr:c.stderr}),new U(`pnpm install failed: ${c.stderr}`);const N=l.join(m,"dist-packages"),R=["scripts/package-platforms.mjs",`agent-${i}`,a,N,"--print-artifacts"],h=await ue("node",R,{cwd:m,env:ke()});if(o(r,"INFO","Build completed",{exitCode:h.exitCode,stdoutLength:h.stdout.length}),h.exitCode!==0)throw o(r,"ERROR","Build failed",{stderr:h.stderr}),new U(`Build failed: ${h.stderr}`);const g=[".tar.gz",".tar.bz2",".zip",".tgz"],S=[],f=h.stdout.split(`
5
- `);for(const k of f){const L=k.trim();if(!L)continue;const F=L.toLowerCase();if(!g.some(D=>F.endsWith(D)))continue;const y=l.resolve(m,L);let d=l.relative(p,y);d=d.split(l.sep).join("/");const P=l.basename(L),z=st(P);S.push({path:d,fileName:P,platform:z||""})}return o(r,"INFO","Build agent package completed",{artifactsCount:S.length,artifacts:S}),{artifacts:S}}async function rt(e,t,i=null){const a=`computer:${e}:${t}`;if(!e)throw new w("userId cannot be empty",{field:"userId"});if(!t)throw new w("cId cannot be empty",{field:"cId"});const s=await J(i,e,t,a);if(!n.existsSync(s))return o(a,"WARN","Workspace not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const r=pe(s);if(!r)return o(a,"WARN","package-platforms.mjs not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const p=l.join(r,"dist-packages");return n.existsSync(p)?(n.rmSync(p,{recursive:!0,force:!0}),o(a,"INFO","Cleaned dist-packages",{distPackagesDir:p}),{cleaned:!0}):(o(a,"INFO","dist-packages not found, nothing to clean",{distPackagesDir:p}),{cleaned:!1})}async function ot(e,t,i=null){const a=`computer:${e}:${t}`;if(!e)throw new w("userId cannot be empty",{field:"userId"});if(!t)throw new w("cId cannot be empty",{field:"cId"});const s=i?.workspacePath?i.workspacePath:await J(i,e,t,a);return n.existsSync(s)?(await n.promises.rm(s,{recursive:!0,force:!0}),o(a,"INFO","Workspace deleted",{userId:e,cId:t,targetDir:s})):o(a,"WARN","Workspace not found, skip delete",{userId:e,cId:t,targetDir:s}),{deleted:!0}}function Ee(e){const t=l.join(e,"package.json");if(n.existsSync(t))return e;let i;try{i=n.readdirSync(e,{withFileTypes:!0})}catch{return null}for(const a of i)if(a.isDirectory()&&!he.has(a.name)&&a.name!=="node_modules"){const s=Ee(l.join(e,a.name));if(s)return s}return null}const Ae=["neverBuiltDependencies","onlyBuiltDependencies","ignoredBuiltDependencies"],lt=["NPM_CONFIG_NEVER_BUILT_DEPENDENCIES","npm_config_never_built_dependencies","NPM_CONFIG_ONLY_BUILT_DEPENDENCIES","npm_config_only_built_dependencies","NPM_CONFIG_IGNORED_BUILT_DEPENDENCIES","npm_config_ignored_built_dependencies"];async function ct(e,t){const i=l.join(e,"package.json");if(!n.existsSync(i))return;const a=await n.promises.readFile(i,"utf8");let s;try{s=JSON.parse(a)}catch{o(t,"WARN","Skip sanitizing package.json: invalid JSON",{projectDir:e});return}if(!s.pnpm||typeof s.pnpm!="object")return;const r=Ae.filter(p=>p in s.pnpm);if(r.length!==0){for(const p of r)delete s.pnpm[p];Object.keys(s.pnpm).length===0&&delete s.pnpm,await n.promises.writeFile(i,`${JSON.stringify(s,null,2)}
6
- `,"utf8"),o(t,"INFO","Removed conflicting pnpm built dependency settings from package.json",{projectDir:e,removedKeys:r})}}async function pt(e,t){const i=l.join(e,"pnpm-workspace.yaml");if(!n.existsSync(i))return;const s=(await n.promises.readFile(i,"utf8")).split(`
7
- `),r=[];let p=-1,m=[];for(const c of s){const N=c.trimStart(),R=c.length-N.length,h=Ae.find(g=>new RegExp(`^${g}:\\s*(.*)$`).test(N));if(h){m.push(h);const g=N.slice(h.length+1).trim();(!g||g==="|"||g===">")&&(p=R);continue}if(p>=0){if(N===""||R>p)continue;p=-1}r.push(c)}m.length!==0&&(await n.promises.writeFile(i,r.join(`
8
- `),"utf8"),o(t,"INFO","Removed conflicting pnpm built dependency settings from pnpm-workspace.yaml",{projectDir:e,removedKeys:m}))}async function Oe(e){if(!n.existsSync(e))return"";const t=await n.promises.readFile(e,"utf8"),i=t.split(`
1
+ import n from"fs";import l from"path";import{exec as ve}from"child_process";import Me from"cross-spawn";import We from"archiver";import te from"../../appConfig/index.js";import{extractZip as Y}from"../common/zipUtils.js";import{ValidationError as f,SystemError as K,FileError as ie}from"../error/errorHandler.js";import{log as r}from"../log/logUtils.js";import{createPnpmNpmrc as Te}from"../common/npmrcUtils.js";import{ensurePrimaryAgentDirs as ge,syncAgents as fe,linkWorkspaceToAgentStore as Se,forceDirSymlink as Ce,copyDirContents as Le,isWorkspaceSkillsSymlinked as Be}from"../common/AgentWorkspaceUtils.js";import{getProjectStoreRoot as Ue,getAgentStorePath as De,readManifest as Ze,writeManifest as Ge,reverseRefs as Ke,ensureAgentStoreDirs as xe,tryAcquireAgentStoreLock as we,releaseAgentStoreLock as Ne,pruneAgentSkills as qe,installSkillDir as se,replaceAgentsDir as Ve,agentSkillExists as Je}from"../common/agentStoreUtils.js";import{writeAgentHookConfigs as Re}from"./hookConfigUtils.js";import{ensureWorkspaceDir as H,resolveWorkspaceDir as He,resolveWorkspaceRoot as oe}from"./workspaceContext.js";function le(e){if(!e)return[];if(Array.isArray(e))return e.map(t=>typeof t=="string"?t.trim():t&&typeof t=="object"&&typeof t.url=="string"?t.url.trim():"").filter(Boolean);if(typeof e=="object")return Object.values(e).map(t=>typeof t=="string"?t.trim():"").filter(Boolean);if(typeof e=="string"){const t=e.trim();if(!t)return[];try{const i=JSON.parse(t);return le(i)}catch{}return[t]}return[]}async function je(e,t,i){try{return await Ce(e,t,i),!0}catch(a){return r(i,"WARN","Skill view dir link unavailable, fallback to copy",{linkPath:e,targetDir:t,platform:process.platform,error:a.message}),!1}}async function Ye(e,t,i){const a=process.platform==="win32"?l.resolve(t):l.relative(l.dirname(e),t).split(l.sep).join("/");try{return await n.promises.symlink(a,e,"file"),!0}catch(s){return r(i,"WARN","Skill view file link unavailable, fallback to copy",{linkPath:e,targetFile:t,platform:process.platform,error:s.message}),!1}}async function _e(e,t,i,a,s,o={}){const p=Ue(e,a);if(!p||!i)return!1;const{skillNames:u=null,subagentNames:c=null}=o,N=l.join(p,".view.lock");await n.promises.mkdir(p,{recursive:!0});const P=Date.now()+3e4;let w=!1;for(;Date.now()<P&&!w;)try{await n.promises.writeFile(N,String(Date.now()),{flag:"wx"}),w=!0}catch(g){if(g.code!=="EEXIST")throw g;try{const D=await n.promises.stat(N);if(Date.now()-D.mtimeMs>5*60*1e3){await n.promises.rm(N,{force:!0});continue}}catch{}await new Promise(D=>setTimeout(D,200))}if(!w)throw new K("Agent skill view is busy, please retry");try{const g=await Ze(p),D=g.agents[String(i)]||{skills:[],subagents:[]};u&&(D.skills=[...new Set(u.map(y=>String(y||"").trim()).filter(Boolean))]),c&&(D.subagents=[...new Set(c.map(y=>String(y||"").trim()).filter(Boolean))]),g.agents[String(i)]=D,await Ge(p,g);const h=[".agents",".claude",".opencode",".codex"],d=[];for(const y of h)for(const $ of["skills","agents"]){const k=l.join(t,y,$);if(await n.promises.mkdir(l.dirname(k),{recursive:!0}),y===".agents")try{(await n.promises.lstat(k)).isSymbolicLink()&&await n.promises.rm(k,{recursive:!0,force:!0})}catch{}else{const b=l.join(t,".agents",$),T=process.platform==="win32"?l.resolve(b):"../.agents/"+$;let S=!1;try{S=(await n.promises.lstat(k)).isSymbolicLink()&&n.readlinkSync(k)===T}catch{}S||(await n.promises.rm(k,{recursive:!0,force:!0}),await je(k,b,s)||d.push([b,k]));continue}await n.promises.mkdir(k,{recursive:!0})}const B=async(y,$,k)=>{k==="skills"||(await n.promises.lstat(y)).isDirectory()?(d.length>0||!await je($,y,s))&&await Le(y,$):await Ye($,y,s)||await n.promises.copyFile(y,$)},A=l.join(t,".agents");for(const y of["skills","agents"]){const k=Ke(g,y==="skills"?"skills":"subagents"),b=l.join(A,y),T=n.existsSync(b)?await n.promises.readdir(b):[],S=new Set(Object.keys(k));for(const R of T){const x=l.join(b,R);pe(x)||S.has(R)||(await n.promises.rm(x,{recursive:!0,force:!0}),r(s,"INFO","Skill view entry removed (no agent references it)",{kind:y,name:R}))}for(const[R,x]of Object.entries(k)){const E=l.join(b,R);if(pe(E))continue;const v=[...new Set([String(i),...x.map(String)])];let W=null;for(const _ of v){const C=l.join(De(e,_,a),y,R);if(n.existsSync(C)){W=C;break}}if(!W){await n.promises.rm(E,{recursive:!0,force:!0});continue}const m=process.platform==="win32"?l.resolve(W):l.relative(b,W).split(l.sep).join("/");let j=!1;try{j=!(await n.promises.lstat(E)).isSymbolicLink()||n.readlinkSync(E)!==m}catch{j=!0}j&&(await n.promises.rm(E,{recursive:!0,force:!0}),await B(W,E,y))}if(y==="skills"){const R=[...new Set([String(i),...Object.keys(g.agents||{})])];for(const x of R){const E=l.join(De(e,x,a),"skills");let v=[];try{v=await n.promises.readdir(E,{withFileTypes:!0})}catch{continue}for(const W of v){if(!W.isDirectory())continue;const m=l.join(E,W.name);if(!pe(m))continue;const j=l.join(b,W.name);n.existsSync(j)||(await B(m,j,y),r(s,"INFO","Dynamic skill linked into view",{owner:x,name:W.name}))}}}}for(const[y,$]of d)await n.promises.rm($,{recursive:!0,force:!0}),await n.promises.cp(y,$,{recursive:!0,dereference:!0,force:!0});return r(s,"INFO","Shared skill view synced",{userWorkspaceRoot:t,projectStoreRoot:p,agentId:i,skills:D.skills,subagents:D.subagents,copyFallbackDirs:d.length}),!0}finally{await n.promises.rm(N,{force:!0})}}function Pe(e){if(!e)return[];if(Array.isArray(e)){const t=[];for(const i of e)if(typeof i=="string"&&i.trim())t.push({name:null,url:i.trim()});else if(i&&typeof i=="object"){const a=typeof i.url=="string"?i.url.trim():"",s=i.name!=null?String(i.name).trim():"";a&&t.push({name:s||null,url:a})}return t}if(typeof e=="object")return Object.entries(e).map(([t,i])=>({name:String(t||"").trim()||null,url:typeof i=="string"?i.trim():""})).filter(t=>t.url);if(typeof e=="string"){const t=e.trim();if(!t)return[];try{return Pe(JSON.parse(t))}catch{return[{name:null,url:t}]}}return[]}function Xe(e){if(!e)return{};let t=e;if(typeof e=="string")try{t=JSON.parse(e)}catch{return{}}if(!t||typeof t!="object"||Array.isArray(t))return{};const i={};for(const[a,s]of Object.entries(t)){const o=String(a||"").trim(),p=typeof s=="string"?s.trim():"";o&&p&&(i[o]=p)}return i}function Ee(e){if(!e)return[];if(Array.isArray(e))return e.map(t=>String(t||"").trim()).filter(Boolean);if(typeof e=="string"){const t=e.trim();if(!t)return[];try{const i=JSON.parse(t);if(Array.isArray(i))return i.map(a=>String(a||"").trim()).filter(Boolean)}catch{}return t.split(",").map(i=>i.trim()).filter(Boolean)}return[]}async function ce(e,t,i){let a;try{a=await fetch(e)}catch(u){throw new ie(`Failed to download skill zip from url: ${e}`,{url:e,reason:u.message})}if(!a.ok)throw new ie(`Failed to download skill zip from url: ${e}`,{url:e,status:a.status,statusText:a.statusText});const s=a.headers.get("content-type")||"";s&&!s.includes("zip")&&!s.includes("octet-stream")&&r(i,"WARN","Downloaded skill url content-type is not typical zip",{url:e,contentType:s});const o=await a.arrayBuffer(),p=Buffer.from(o);await n.promises.writeFile(t,p)}async function ne(e,t){const i=l.join(e,t);if(n.existsSync(i)&&(await n.promises.lstat(i)).isDirectory())return i;const a=await n.promises.readdir(e,{withFileTypes:!0});for(const s of a){if(!s.isDirectory())continue;const o=l.join(e,s.name),p=l.join(o,t);if(n.existsSync(p)&&(await n.promises.lstat(p)).isDirectory())return p}return null}async function Ft(e){return ne(e,"skills")}const Qe=".dynamic_add.lock";function pe(e){const t=l.join(e,Qe);return n.existsSync(t)&&n.statSync(t).isFile()}async function X(e){n.existsSync(e)&&await n.promises.rm(e,{recursive:!0,force:!0})}async function Q(e,t){try{await n.promises.rename(e,t)}catch(i){if(i.code==="EXDEV"){async function a(s,o){if((await n.promises.lstat(s)).isDirectory()){await n.promises.mkdir(o,{recursive:!0});const u=await n.promises.readdir(s);for(const c of u)await a(l.join(s,c),l.join(o,c))}else await n.promises.copyFile(s,o)}await a(e,t),await n.promises.rm(e,{recursive:!0,force:!0})}else throw i}}async function he(e){const i=(await n.promises.readdir(e,{withFileTypes:!0})).filter(s=>s.isDirectory()&&!s.name.startsWith("."));return i.find(s=>s.name==="skills")?(await n.promises.readdir(l.join(e,"skills"),{withFileTypes:!0})).filter(o=>o.isDirectory()&&!o.name.startsWith(".")).map(o=>({name:o.name,sourcePath:l.join(e,"skills",o.name)})):i.map(s=>({name:s.name,sourcePath:l.join(e,s.name)}))}const ae=8;async function re(e,t,i){if(!e||e.length===0)return;const a=Math.max(1,Math.min(t,e.length));let s=0;await Promise.all(Array.from({length:a},async()=>{for(;;){const o=s++;if(o>=e.length)return;await i(e[o],o)}}))}async function Ie(e){const{userId:t,cId:i,agentId:a,file:s,skillUrls:o,skillUrlMap:p,skillNames:u,updateSkillNames:c,mcpServersConfig:N,permissionsConfig:P,hooksConfig:w,hookScripts:g,service:D}=e,h=Date.now(),d=`computer:${t}:${i}`,B=Xe(p),A=Pe(o),y=Ee(u),$=c!=null,k=new Set(Ee(c)),b=[],T=[],S=new Set,R=new Set,x=[];let E=null,v=!1;if(!t)throw new f("userId cannot be empty",{field:"userId"});if(!i)throw new f("cId cannot be empty",{field:"cId"});if(!a)throw new f("agentId cannot be empty",{field:"agentId"});const W=oe(D),m=await H(D,t,i,d),j=l.join(m,".tmp");await n.promises.mkdir(m,{recursive:!0}),await n.promises.mkdir(j,{recursive:!0});const{agentStorePath:_,skillsDir:C,agentsDir:I}=await xe(t,a,d,D),L=O=>!O||!$||k.has(O)?!0:!Je(C,O),Z=async(O,F,G)=>{const V=l.join(j,`skill_url_${Date.now()}_${G}_${Math.round(Math.random()*1e6)}.zip`);b.push(V);const q=l.join(j,`skill_url_extract_${Date.now()}_${G}_${Math.round(Math.random()*1e6)}`);T.push(q),await n.promises.mkdir(q,{recursive:!0});try{r(d,"INFO","Start download skill zip from url",{userId:t,cId:i,agentId:a,skillName:F,skillUrl:O}),await ce(O,V,d),await Y(V,q)}catch(U){r(d,"ERROR","Download/extract skill zip failed, skip this skill",{userId:t,cId:i,agentId:a,skillName:F,skillUrl:O,error:U.message}),x.push({name:F||null,url:O,reason:`Download/extract skill zip failed: ${U.message}`});return}try{const U=await he(q);if(U.length===0){r(d,"WARN","No skill dir found in downloaded zip",{skillUrl:O,destName:F}),x.push({name:F||null,url:O,reason:"No skill directory found in downloaded zip"});return}let M=F?U.find(z=>z.name===F):null;if(!M&&U.length===1&&(M=U[0]),F&&M){if(!L(F)){R.add(F);return}await se(M.sourcePath,C,F,{asDynamic:!1}),S.add(F),r(d,"INFO","Skill from url updated to agent store",{userId:t,cId:i,agentId:a,skillUrl:O,skillName:F,zipDirName:M.name});return}for(const z of U){if(!L(z.name)){R.add(z.name);continue}await se(z.sourcePath,C,z.name,{asDynamic:!1}),S.add(z.name),r(d,"INFO","Skill from url updated to agent store",{userId:t,cId:i,agentId:a,skillUrl:O,skillName:z.name})}}catch(U){r(d,"ERROR","Install skill from url failed, skip this skill",{userId:t,cId:i,agentId:a,skillName:F,skillUrl:O,error:U.message}),x.push({name:F||null,url:O,reason:`Install skill failed: ${U.message}`})}};await Re(m,{mcpServersConfig:N,hooksConfig:w,permissionsConfig:P,hookScripts:g},d);try{const O=await we(_,d);if(!O.acquired)v=!0;else{E=O.lockPath;const V=Date.now();let q=null;if(s){if(!s.path)throw new f("Uploaded file has no valid path",{field:"file.path"});if(l.extname(s.originalname||s.filename||"").toLowerCase()!==".zip")throw new f("Only zip files are supported",{field:"file",originalName:s.originalname});const z=l.join(j,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);T.push(z),await n.promises.mkdir(z,{recursive:!0}),await Y(s.path,z);const J=await ne(z,"skills");if(J){const me=(await n.promises.readdir(J,{withFileTypes:!0})).filter(ee=>ee.isDirectory());await re(me,ae,async ee=>{if(!L(ee.name)){R.add(ee.name);return}await se(l.join(J,ee.name),C,ee.name,{asDynamic:!1}),S.add(ee.name)})}q=await ne(z,"agents")}const U=Object.entries(B);if(U.length>0){const M=[];for(const[z,J]of U){if(!L(z)){R.add(z),r(d,"INFO","Skip download skill zip (already in agent store)",{userId:t,cId:i,agentId:a,skillName:z,skillUrl:J});continue}M.push({skillName:z,skillUrl:J})}await re(M,ae,async(z,J)=>{await Z(z.skillUrl,z.skillName,J)})}else{const M=[];for(let z=0;z<A.length;z++){const{name:J,url:me}=A[z];if(J&&!L(J)){R.add(J);continue}M.push({skillUrl:me,hintedName:J,index:z})}await re(M,ae,async z=>{await Z(z.skillUrl,z.hintedName,z.index)})}await Ve(q,I),await qe(C,y,d),r(d,"INFO","Agent store resources prepared",{userId:t,cId:i,agentId:a,selectiveInstall:$,updatedSkills:Array.from(S),skippedSkills:Array.from(R),updateSkillNames:Array.from(k),keepSkillNames:y,skillUrlMapCount:U.length,prepareElapsedMs:Date.now()-V})}let F=null,G=!1;if(D&&(D.isUserApp||D.isNormalProject)&&D.appId){let V=null;try{V=(await n.promises.readdir(I,{withFileTypes:!0})).map(q=>q.name)}catch{}G=await _e(t,m,a,D,d,{skillNames:u?y:null,subagentNames:V})}return G||(F=await Se(m,C,I,d)),r(d,"INFO","Workspace created successfully (agent store)",{userId:t,cId:i,agentId:a,agentStorePath:_,updatedSkills:Array.from(S),skippedSkills:Array.from(R),failedSkills:x,skippedStoreUpdate:v,linkElapsedMs:F?.elapsedMs,elapsedMs:Date.now()-h}),{message:v?"Workspace linked to agent store (store update skipped, lock held)":"Workspace created successfully, linked to agent store",workspaceRoot:W,agentStorePath:_,updatedSkills:Array.from(S),skippedSkills:Array.from(R),failedSkills:x,skippedStoreUpdate:v}}catch(O){throw r(d,"ERROR","Failed to create workspace with agent store",{userId:t,cId:i,agentId:a,error:O.message,elapsedMs:Date.now()-h}),O instanceof f||O instanceof ie||O instanceof K?O:new K(`Failed to create workspace: ${O.message}`,{userId:t,cId:i,agentId:a})}finally{await Ne(E,d);const O=Date.now();for(const F of T)try{n.existsSync(F)&&await n.promises.rm(F,{recursive:!0,force:!0})}catch(G){r(d,"WARN","Failed to clean up temporary extracted zip",{error:G.message})}for(const F of b)try{n.existsSync(F)&&await n.promises.unlink(F)}catch(G){r(d,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:F,error:G.message})}try{s&&s.path&&n.existsSync(s.path)&&await n.promises.unlink(s.path)}catch(F){r(d,"WARN","Failed to clean up uploaded zip file",{tempZipPath:s?.path,error:F.message})}r(d,"INFO","Workspace temp cleanup completed",{userId:t,cId:i,agentId:a,elapsedMs:Date.now()-O,totalElapsedMs:Date.now()-h})}}async function et(e,t,i,a,s,o,p,u,c,N,P,w,g=null){if(c!=null&&String(c).trim()!=="")return Ie({userId:e,cId:t,agentId:c,file:i,skillUrls:a,skillUrlMap:w,skillNames:N,updateSkillNames:P,mcpServersConfig:s,permissionsConfig:o,hooksConfig:p,hookScripts:u,service:g});const D=Date.now(),h=`computer:${e}:${t}`,d=le(a),B=[],A=[],y=new Set,$=[];if(!e)throw new f("userId cannot be empty",{field:"userId"});if(!t)throw new f("cId cannot be empty",{field:"cId"});const k=oe(g),b=await H(g,e,t,h),T=l.join(b,".tmp"),{skillsDir:S,agentsDir:R,agentTypes:x}=await ge(b);n.existsSync(b)||await n.promises.mkdir(b,{recursive:!0});const E=l.join(T,`preserved_skills_${Date.now()}_${Math.round(Math.random()*1e6)}`);if(n.existsSync(S)){const j=(await n.promises.readdir(S,{withFileTypes:!0})).filter(_=>_.isDirectory()&&pe(l.join(S,_.name)));if(j.length>0){await n.promises.mkdir(E,{recursive:!0});for(const _ of j){const C=l.join(S,_.name),I=l.join(E,_.name);await Q(C,I)}r(h,"INFO","\u4FDD\u7559\u542B .dynamic_add.lock \u7684 skill",{preserved:j.map(_=>_.name)})}}if(await X(S),await X(R),await n.promises.mkdir(S,{recursive:!0}),await n.promises.mkdir(R,{recursive:!0}),n.existsSync(E)){const m=await n.promises.readdir(E,{withFileTypes:!0});for(const j of m)if(j.isDirectory()){const _=l.join(E,j.name),C=l.join(S,j.name);await Q(_,C)}await X(E)}const v=n.existsSync(S),W=n.existsSync(R);if(r(h,"INFO","Deleted old skills and agents directories completed",{userId:e,cId:t,targetSkillsDir:S,targetAgentsDir:R,agentTypes:x,skillsExists:v,agentsExists:W}),await Re(b,{mcpServersConfig:s,hooksConfig:p,permissionsConfig:o,hookScripts:u},h),!i&&d.length===0){const m=await fe(b,h);return r(h,"INFO","Created workspace (no uploaded file, no skills and agents)",{userId:e,cId:t,workspaceRoot:k,skillsDir:null,agentsDir:null,agentTypes:x,syncAgentsElapsedMs:m?.elapsedMs,elapsedMs:Date.now()-D}),{message:"Workspace created (no uploaded file, no skills and agents)",workspaceRoot:k}}if(i){if(!i.path)throw new f("Uploaded file has no valid path",{field:"file.path"});if(l.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new f("Only zip files are supported",{field:"file",originalName:i.originalname})}r(h,"DEBUG","Start processing workspace resources",{userId:e,cId:t,workspaceRoot:k,hasUploadedZip:!!i,skillUrlsCount:d.length});try{n.existsSync(T)||await n.promises.mkdir(T,{recursive:!0});const m=[];if(i){const L=l.join(T,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);A.push(L),await n.promises.mkdir(L,{recursive:!0}),r(h,"DEBUG","Start extracting uploaded zip file",{extractRoot:L}),await Y(i.path,L),r(h,"DEBUG","Uploaded zip file extracted successfully",{extractRoot:L});const Z=await ne(L,"skills"),O=await ne(L,"agents");if(Z){await n.promises.mkdir(S,{recursive:!0});const F=await n.promises.readdir(Z,{withFileTypes:!0});for(const G of F){if(!G.isDirectory())continue;const V=l.join(Z,G.name),q=l.join(S,G.name);n.existsSync(q)&&await X(q),await Q(V,q),y.add(G.name)}m.push("skills"),r(h,"INFO","skills updated to workspace",{userId:e,cId:t,workspaceRoot:k,targetSkillsDir:S,agentTypes:x})}else r(h,"INFO","skills directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:L});O?(await X(R),await Q(O,R),m.push("agents"),r(h,"INFO","agents updated to workspace",{userId:e,cId:t,workspaceRoot:k,targetAgentsDir:R,agentTypes:x})):r(h,"INFO","agents directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:L})}for(let L=0;L<d.length;L+=1){const Z=d[L];try{const O=l.join(T,`skill_url_${Date.now()}_${L}_${Math.round(Math.random()*1e6)}.zip`);B.push(O);const F=l.join(T,`skill_url_extract_${Date.now()}_${L}_${Math.round(Math.random()*1e6)}`);A.push(F),await n.promises.mkdir(F,{recursive:!0}),r(h,"INFO","Start download skill zip from url",{userId:e,cId:t,skillUrl:Z}),await ce(Z,O,h),r(h,"INFO","Skill zip downloaded, start extracting",{userId:e,cId:t,skillUrl:Z,downloadedZipPath:O}),await Y(O,F);const V=(await n.promises.readdir(F,{withFileTypes:!0})).filter(M=>M.isDirectory()&&!M.name.startsWith(".")),U=V.find(M=>M.name==="skills")?(await n.promises.readdir(l.join(F,"skills"),{withFileTypes:!0})).filter(M=>M.isDirectory()&&!M.name.startsWith(".")).map(M=>({name:M.name,sourcePath:l.join(F,"skills",M.name)})):V.map(M=>({name:M.name,sourcePath:l.join(F,M.name)}));if(U.length===0){r(h,"WARN","No skill directory found after extracting skill url zip",{userId:e,cId:t,skillUrl:Z,extractRoot:F}),$.push({name:null,url:Z,reason:"No skill directory found in downloaded zip"});continue}await n.promises.mkdir(S,{recursive:!0});for(const M of U){const z=l.join(S,M.name);n.existsSync(z)&&await X(z),await Q(M.sourcePath,z),y.add(M.name),r(h,"INFO","Skill from url updated to workspace",{userId:e,cId:t,skillUrl:Z,skillName:M.name,destSkillPath:z})}}catch(O){r(h,"ERROR","Download/extract skill zip failed, skip this skill",{userId:e,cId:t,skillUrl:Z,error:O.message}),$.push({name:null,url:Z,reason:`Download/extract skill zip failed: ${O.message}`})}m.includes("skills")||m.push("skills")}m.length===0&&r(h,"WARN","No valid skills or agents directories found",{userId:e,cId:t,hasUploadedZip:!!i,skillUrlsCount:d.length});const j=Array.from(y),_=m.length>0?`Workspace created successfully, ${m.join(" and ")} updated`:"Workspace created successfully (skills and agents directories not found)",C=Date.now()-D;r(h,"INFO","Workspace resources prepared",{userId:e,cId:t,updatedDirs:m,updatedSkills:j,agentTypes:x,prepareElapsedMs:C});const I=await fe(b,h);return r(h,"INFO",_,{userId:e,cId:t,updatedDirs:m,updatedSkills:j,agentTypes:x,prepareElapsedMs:C,syncAgentsElapsedMs:I?.elapsedMs,elapsedMs:Date.now()-D}),{message:_,workspaceRoot:k,updatedSkills:j,failedSkills:$}}catch(m){throw r(h,"ERROR","Failed to process workspace resources",{userId:e,cId:t,error:m.message,elapsedMs:Date.now()-D}),m instanceof f||m instanceof ie||m instanceof K?m:new K(`Failed to create workspace: ${m.message}`,{userId:e,cId:t})}finally{const m=Date.now();try{for(const j of A)n.existsSync(j)&&await n.promises.rm(j,{recursive:!0,force:!0})}catch(j){r(h,"WARN","Failed to clean up temporary extracted zip",{error:j.message})}for(const j of B)try{n.existsSync(j)&&await n.promises.unlink(j)}catch(_){r(h,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:j,error:_.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(j){r(h,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:j.message})}r(h,"INFO","Workspace temp cleanup completed",{userId:e,cId:t,elapsedMs:Date.now()-m,totalElapsedMs:Date.now()-D})}}async function Fe(e,t,i,a,s,o=null){const p=Date.now(),u=`computer:${e}:${t}`,c=le(s),N=[],P=[],w=new Set,g=[];let D=null;if(!e)throw new f("userId cannot be empty",{field:"userId"});if(!t)throw new f("cId cannot be empty",{field:"cId"});if(!i)throw new f("agentId cannot be empty",{field:"agentId"});if(!a&&c.length===0)throw new f("file or skillUrls cannot both be empty",{field:"file|skillUrls"});const h=oe(o),d=await H(o,e,t,u),B=l.join(d,".tmp");await n.promises.mkdir(d,{recursive:!0}),await n.promises.mkdir(B,{recursive:!0});const{agentStorePath:A,skillsDir:y,agentsDir:$}=await xe(e,i,u,o);try{let k=await we(A,u);if(!k.acquired)for(let x=1;x<=5&&!k.acquired;x++)await new Promise(E=>setTimeout(E,200*x)),k=await we(A,u);if(!k.acquired)throw new K("Agent store is busy, please retry",{userId:e,cId:t,agentId:i});if(D=k.lockPath,a){if(!a.path)throw new f("Uploaded file has no valid path",{field:"file.path"});const x=l.join(B,`push_skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);P.push(x),await n.promises.mkdir(x,{recursive:!0}),await Y(a.path,x);const E=await he(x),v=await ne(x,"skills"),W=v?(await n.promises.readdir(v,{withFileTypes:!0})).filter(m=>m.isDirectory()).map(m=>({name:m.name,sourcePath:l.join(v,m.name)})):E;await re(W,ae,async m=>{await se(m.sourcePath,y,m.name,{asDynamic:!0}),w.add(m.name)})}await re(c,ae,async(x,E)=>{try{const v=l.join(B,`push_skill_url_${Date.now()}_${E}_${Math.round(Math.random()*1e6)}.zip`);N.push(v);const W=l.join(B,`push_skill_url_extract_${Date.now()}_${E}_${Math.round(Math.random()*1e6)}`);P.push(W),await n.promises.mkdir(W,{recursive:!0}),await ce(x,v,u),await Y(v,W);const m=await he(W);if(m.length===0){r(u,"WARN","No skill dir found in downloaded zip",{skillUrl:x}),g.push({name:null,url:x,reason:"No skill directory found in downloaded zip"});return}for(const j of m)await se(j.sourcePath,y,j.name,{asDynamic:!0}),w.add(j.name)}catch(v){r(u,"ERROR","Download/extract/install skill zip failed, skip this skill",{userId:e,cId:t,agentId:i,skillUrl:x,error:v.message}),g.push({name:null,url:x,reason:`Download/extract/install skill zip failed: ${v.message}`})}});let b=null,T=!1;o&&(o.isUserApp||o.isNormalProject)&&o.appId&&(T=await _e(e,d,i,o,u,{})),T||(b=await Se(d,y,$,u));const S=Array.from(w),R=S.length>0?`Pushed ${S.length} skills to agent store: ${S.join(", ")}`:"No valid skill directories found in file or skillUrls";return r(u,"INFO",R,{userId:e,cId:t,agentId:i,updatedSkills:S,linkElapsedMs:b?.elapsedMs,elapsedMs:Date.now()-p}),{message:R,workspaceRoot:h,agentStorePath:A,updatedSkills:S,failedSkills:g}}catch(k){throw r(u,"ERROR","Failed to push skills to agent store",{userId:e,cId:t,agentId:i,error:k.message,elapsedMs:Date.now()-p}),k instanceof f||k instanceof ie||k instanceof K?k:new K(`Failed to push skill to workspace: ${k.message}`,{userId:e,cId:t,agentId:i})}finally{await Ne(D,u);for(const k of P)try{n.existsSync(k)&&await n.promises.rm(k,{recursive:!0,force:!0})}catch(b){r(u,"WARN","Failed to clean up temporary extracted zip",{error:b.message})}for(const k of N)try{n.existsSync(k)&&await n.promises.unlink(k)}catch(b){r(u,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:k,error:b.message})}try{a&&a.path&&n.existsSync(a.path)&&await n.promises.unlink(a.path)}catch(k){r(u,"WARN","Failed to clean up uploaded zip file",{tempZipPath:a?.path,error:k.message})}}}async function tt(e,t,i,a,s,o=null){if(s!=null&&String(s).trim()!==""){if(o&&(o.isUserApp||o.isNormalProject)&&o.appId)return Fe(e,t,s,i,a,o);let A=null;if(o?.isUserApp?A=He(o,e,t):te.COMPUTER_WORKSPACE_DIR&&(A=l.join(te.COMPUTER_WORKSPACE_DIR,String(e),String(t))),Be(A))return Fe(e,t,s,i,a,o);r(`computer:${e}:${t}`,"INFO","Push skills: agentId present but workspace not symlinked, use legacy path",{userId:e,cId:t,agentId:s,userWorkspaceRoot:A})}const u=Date.now(),c=`computer:${e}:${t}`,N=le(a),P=[],w=[];if(!e)throw new f("userId cannot be empty",{field:"userId"});if(!t)throw new f("cId cannot be empty",{field:"cId"});if(!i&&N.length===0)throw new f("file or skillUrls cannot both be empty",{field:"file|skillUrls"});if(i){if(!i.path)throw new f("Uploaded file has no valid path",{field:"file.path"});if(l.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new f("Only zip files are supported",{field:"file",originalName:i?.originalname})}const g=oe(o),D=await H(o,e,t,c),h=l.join(D,".tmp"),{skillsDir:d,agentTypes:B}=await ge(D);try{n.existsSync(D)||await n.promises.mkdir(D,{recursive:!0}),n.existsSync(d)||await n.promises.mkdir(d,{recursive:!0}),n.existsSync(h)||await n.promises.mkdir(h,{recursive:!0});const A=[],y=new Set,$=[];if(i){const S=l.join(h,`skill_push_${Date.now()}_${Math.round(Math.random()*1e6)}`);w.push(S),await n.promises.mkdir(S,{recursive:!0}),r(c,"DEBUG","Start extracting skill zip file",{extractRoot:S}),await Y(i.path,S),r(c,"DEBUG","Skill zip file extracted successfully",{extractRoot:S});const R=await ne(S,"skills");if(!R)r(c,"WARN","skills directory not found in uploaded zip",{userId:e,cId:t,extractRoot:S});else{const E=(await n.promises.readdir(R,{withFileTypes:!0})).filter(v=>v.isDirectory()&&!v.name.startsWith("."));if(E.length===0)r(c,"WARN","skills directory in uploaded zip has no skill subdirectories",{userId:e,cId:t,skillsDir:R});else for(const v of E){const W=l.join(R,v.name),m=l.join(d,v.name);n.existsSync(m)&&await X(m),await Q(W,m),y.add(v.name),r(c,"INFO","skill pushed to workspace from uploaded zip",{userId:e,cId:t,skillName:v.name,destSkillPath:m})}}}for(let S=0;S<N.length;S+=1){const R=N[S];try{const x=l.join(h,`skill_push_url_${Date.now()}_${S}_${Math.round(Math.random()*1e6)}.zip`);P.push(x);const E=l.join(h,`skill_push_url_extract_${Date.now()}_${S}_${Math.round(Math.random()*1e6)}`);w.push(E),await n.promises.mkdir(E,{recursive:!0}),r(c,"INFO","Start download skill zip for push from url",{userId:e,cId:t,skillUrl:R}),await ce(R,x,c),await Y(x,E);const W=(await n.promises.readdir(E,{withFileTypes:!0})).filter(_=>_.isDirectory()&&!_.name.startsWith(".")),j=W.find(_=>_.name==="skills")?(await n.promises.readdir(l.join(E,"skills"),{withFileTypes:!0})).filter(_=>_.isDirectory()&&!_.name.startsWith(".")).map(_=>({name:_.name,sourcePath:l.join(E,"skills",_.name)})):W.map(_=>({name:_.name,sourcePath:l.join(E,_.name)}));if(j.length===0){r(c,"WARN","No skill directory found after extracting push skill url zip",{userId:e,cId:t,skillUrl:R,extractRoot:E}),$.push({name:null,url:R,reason:"No skill directory found in downloaded zip"});continue}for(const _ of j){const C=l.join(d,_.name);n.existsSync(C)&&await X(C),await Q(_.sourcePath,C),y.add(_.name),r(c,"INFO","skill pushed to workspace from url zip",{userId:e,cId:t,skillUrl:R,skillName:_.name,destSkillPath:C})}}catch(x){r(c,"ERROR","Download/extract skill zip failed, skip this skill",{userId:e,cId:t,skillUrl:R,error:x.message}),$.push({name:null,url:R,reason:`Download/extract skill zip failed: ${x.message}`})}}for(const S of y)A.push(S);const k=A.length>0?`Pushed ${A.length} skills: ${A.join(", ")}`:"No valid skill directories found in file or skillUrls",b=Date.now()-u;r(c,"INFO","Push skills resources prepared",{userId:e,cId:t,updatedSkills:A,agentTypes:B,prepareElapsedMs:b});const T=await fe(D,c);return r(c,"INFO",k,{userId:e,cId:t,updatedSkills:A,agentTypes:B,prepareElapsedMs:b,syncAgentsElapsedMs:T?.elapsedMs,elapsedMs:Date.now()-u}),{message:k,workspaceRoot:g,updatedSkills:A,failedSkills:$}}catch(A){throw r(c,"ERROR","Failed to push skill to workspace",{userId:e,cId:t,error:A.message,elapsedMs:Date.now()-u}),A instanceof f||A instanceof ie||A instanceof K?A:new K(`Failed to push skill to workspace: ${A.message}`,{userId:e,cId:t})}finally{const A=Date.now();try{for(const y of w)n.existsSync(y)&&await n.promises.rm(y,{recursive:!0,force:!0})}catch(y){r(c,"WARN","Failed to clean up temporary extracted zip",{error:y.message})}for(const y of P)try{n.existsSync(y)&&await n.promises.unlink(y)}catch($){r(c,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:y,error:$.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(y){r(c,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:y.message})}r(c,"INFO","Push skills temp cleanup completed",{userId:e,cId:t,elapsedMs:Date.now()-A,totalElapsedMs:Date.now()-u})}}async function it(e,t,i,a,s=null){const o=Date.now(),p=`computer:${e}:${t}`;if(!e)throw new f("userId cannot be empty",{field:"userId"});if(!t)throw new f("cId cannot be empty",{field:"cId"});if(!i||!i.path)throw new f("file is required",{field:"file"});if(l.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new f("Only zip files are supported",{field:"file",originalName:i.originalname});const c=await H(s,e,t,p),N=l.join(c,".tmp"),P=l.join(N,`template_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{n.existsSync(c)||await n.promises.mkdir(c,{recursive:!0}),n.existsSync(N)||await n.promises.mkdir(N,{recursive:!0}),await n.promises.mkdir(P,{recursive:!0}),r(p,"DEBUG","Start extracting template zip file",{extractRoot:P}),await Y(i.path,P),r(p,"DEBUG","Template zip file extracted successfully",{extractRoot:P});const w=await n.promises.readdir(P,{withFileTypes:!0});for(const g of w){const D=l.join(P,g.name),h=l.join(c,g.name);if(g.isDirectory())await Q(D,h);else{const d=l.dirname(h);n.existsSync(d)||await n.promises.mkdir(d,{recursive:!0}),await n.promises.copyFile(D,h)}}if(r(p,"INFO","Template files extracted to workspace",{userId:e,cId:t,targetDir:c,fileCount:w.length}),te.GIT_ENABLED&&(a==="true"||a===!0)){const g=await import("../../service/gitService.js");await g.default.init({workspaceType:"taskAgent",userId:e,cId:t}),await g.default.commit({workspaceType:"taskAgent",userId:e,cId:t,message:"Initial commit"}),r(p,"INFO","Git init and initial commit done",{userId:e,cId:t})}return r(p,"INFO","Init project template completed",{userId:e,cId:t,targetDir:c,elapsedMs:Date.now()-o}),{message:"Project template initialized successfully",workspaceRoot:c}}catch(w){throw r(p,"ERROR","Failed to init project template",{userId:e,cId:t,error:w.message,elapsedMs:Date.now()-o}),w instanceof f||w instanceof ie||w instanceof K?w:new K(`Failed to init project template: ${w.message}`,{userId:e,cId:t})}finally{try{n.existsSync(P)&&await n.promises.rm(P,{recursive:!0,force:!0})}catch(w){r(p,"WARN","Failed to clean up temporary extracted zip",{error:w.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(w){r(p,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:w.message})}}}async function nt(e,t,i,a=null){if(!e)throw new f("userId cannot be empty",{field:"userId"});if(!t)throw new f("cId cannot be empty",{field:"cId"});if(!i||typeof i!="string"||!i.trim())throw new f("command cannot be empty",{field:"command"});const s=await H(a,e,t,"computer");if(!n.existsSync(s))throw new f("workspace directory does not exist",{field:"workDir",workDir:s});const o=`computer:${e}:${t}`;r(o,"INFO","Execute command in workspace",{userId:e,cId:t,workDir:s,command:i});const p=10*60*1e3,u={cwd:s,timeout:p,maxBuffer:50*1024*1024};return te.BASH_PATH&&(u.shell=te.BASH_PATH),new Promise((c,N)=>{ve(i,u,(P,w,g)=>{const D=P?P.killed?-1:P.code||1:0,h=P&&P.killed?(g||"")+`
2
+ Command timed out after ${p/1e3}s`:g||"";r(o,"INFO","Execute command completed",{userId:e,cId:t,exitCode:D,killed:P?P.killed:!1,stdoutLength:w?w.length:0,stderrLength:h.length}),c({stdout:w||"",stderr:h,exitCode:D})})})}async function st(e,t,i,a=null){const s=`computer:${e}:${t}`;if(!e)throw new f("userId cannot be empty",{field:"userId"});if(!t)throw new f("cId cannot be empty",{field:"cId"});const o=await H(a,e,t,s);if(!n.existsSync(o))throw new f("workspace directory does not exist",{field:"workDir",workDir:o});const p=te.ZIP_WORKSPACE_EXCLUDE||[],u=Array.isArray(i)?i:[],c=new Set([...p,...u]),N=`${e}_${t}.zip`,P=Date.now();r(s,"INFO","Zip workspace request",{userId:e,cId:t,targetDir:o,excludeDirs:[...c]});const w=We("zip",{zlib:{level:9}});return w.directory(o,!1,g=>{const D=g.name||"";if(D.split(/[\/\\]/).filter(Boolean).some(d=>c.has(d)))return!1;try{const d=l.join(o,D);if(n.lstatSync(d).isSymbolicLink())return!1}catch{return!1}return g}),w.on("warning",g=>{g.code==="ENOENT"?r(s,"WARN","Encountered file problem when creating zip",{message:g.message,code:g.code}):r(s,"ERROR","Encountered warning when creating zip",{message:g.message,code:g.code})}),w.on("error",g=>{r(s,"ERROR","Failed to create zip",{message:g.message,elapsedMs:Date.now()-P})}),w.on("end",()=>{r(s,"INFO","Workspace zip created successfully",{targetDir:o,zipFileName:N,elapsedMs:Date.now()-P})}),{archive:w,zipFileName:N}}const ke=new Set([...te.ZIP_WORKSPACE_EXCLUDE||[],"dist-packages"]);function ue(e){let t;try{t=n.readdirSync(e,{withFileTypes:!0})}catch{return null}const i=l.join(e,"scripts","package-platforms.mjs");if(n.existsSync(i))return e;for(const a of t)if(a.isDirectory()&&!ke.has(a.name)){const s=ue(l.join(e,a.name));if(s)return s}return null}function de(e,t,i,a=10*60*1e3){return new Promise(s=>{const o=Me(e,t,{...i,timeout:a});let p="",u="";o.stdout?.on("data",c=>{p+=c.toString()}),o.stderr?.on("data",c=>{u+=c.toString()}),o.on("error",c=>{s({stdout:p,stderr:u+`
3
+ Failed to spawn ${e}: ${c.message}`,exitCode:1})}),o.on("close",(c,N)=>{s(c===null&&N?{stdout:p,stderr:u+`
4
+ Process killed by signal ${N} (timeout after ${a/1e3}s)`,exitCode:-1}:{stdout:p,stderr:u,exitCode:c??1})})})}function at(e){if(!e)return null;let t=e;t.endsWith(".tar.gz")?t=t.slice(0,-7):t.endsWith(".tar.bz2")?t=t.slice(0,-8):(t.endsWith(".tgz")||t.endsWith(".zip"))&&(t=t.slice(0,-4));const i=t.split("-");if(i.length<4)return null;let a=-1;for(let s=i.length-1;s>=2;s--)if(/^\d+\.\d+\.\d+$/.test(i[s])){a=s;break}return a<3?null:i.slice(2,a).join("-")}async function rt(e,t,i,a,s=null){const o=`computer:${e}:${t}`;if(!e)throw new f("userId cannot be empty",{field:"userId"});if(!t)throw new f("cId cannot be empty",{field:"cId"});if(!i)throw new f("agentId cannot be empty",{field:"agentId"});if(!a)throw new f("version cannot be empty",{field:"version"});const p=await H(s,e,t,o);if(!n.existsSync(p))throw new f("workspace directory does not exist",{field:"workDir",workDir:p});r(o,"INFO","Build agent package request",{userId:e,cId:t,agentId:i,version:a});const u=ue(p);if(!u)throw new f("package-platforms.mjs not found in workspace",{field:"script"});r(o,"INFO","Found package script",{projectDir:u});const c=await be(u,o);if(r(o,"INFO","pnpm install completed",{exitCode:c.exitCode,stderrLength:c.stderr.length}),c.exitCode!==0)throw r(o,"ERROR","pnpm install failed",{stderr:c.stderr}),new K(`pnpm install failed: ${c.stderr}`);const N=l.join(u,"dist-packages"),P=["scripts/package-platforms.mjs",`agent-${i}`,a,N,"--print-artifacts"],w=await de("node",P,{cwd:u,env:ye()});if(r(o,"INFO","Build completed",{exitCode:w.exitCode,stdoutLength:w.stdout.length}),w.exitCode!==0)throw r(o,"ERROR","Build failed",{stderr:w.stderr}),new K(`Build failed: ${w.stderr}`);const g=[".tar.gz",".tar.bz2",".zip",".tgz"],D=[],h=w.stdout.split(`
5
+ `);for(const d of h){const B=d.trim();if(!B)continue;const A=B.toLowerCase();if(!g.some(T=>A.endsWith(T)))continue;const y=l.resolve(u,B);let $=l.relative(p,y);$=$.split(l.sep).join("/");const k=l.basename(B),b=at(k);D.push({path:$,fileName:k,platform:b||""})}return r(o,"INFO","Build agent package completed",{artifactsCount:D.length,artifacts:D}),{artifacts:D}}async function ot(e,t,i=null){const a=`computer:${e}:${t}`;if(!e)throw new f("userId cannot be empty",{field:"userId"});if(!t)throw new f("cId cannot be empty",{field:"cId"});const s=await H(i,e,t,a);if(!n.existsSync(s))return r(a,"WARN","Workspace not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const o=ue(s);if(!o)return r(a,"WARN","package-platforms.mjs not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const p=l.join(o,"dist-packages");return n.existsSync(p)?(n.rmSync(p,{recursive:!0,force:!0}),r(a,"INFO","Cleaned dist-packages",{distPackagesDir:p}),{cleaned:!0}):(r(a,"INFO","dist-packages not found, nothing to clean",{distPackagesDir:p}),{cleaned:!1})}async function lt(e,t,i=null){const a=`computer:${e}:${t}`;if(!e)throw new f("userId cannot be empty",{field:"userId"});if(!t)throw new f("cId cannot be empty",{field:"cId"});const s=i?.workspacePath?i.workspacePath:await H(i,e,t,a);return n.existsSync(s)?(await n.promises.rm(s,{recursive:!0,force:!0}),r(a,"INFO","Workspace deleted",{userId:e,cId:t,targetDir:s})):r(a,"WARN","Workspace not found, skip delete",{userId:e,cId:t,targetDir:s}),{deleted:!0}}function Ae(e){const t=l.join(e,"package.json");if(n.existsSync(t))return e;let i;try{i=n.readdirSync(e,{withFileTypes:!0})}catch{return null}for(const a of i)if(a.isDirectory()&&!ke.has(a.name)&&a.name!=="node_modules"){const s=Ae(l.join(e,a.name));if(s)return s}return null}const Oe=["neverBuiltDependencies","onlyBuiltDependencies","ignoredBuiltDependencies"],ct=["NPM_CONFIG_NEVER_BUILT_DEPENDENCIES","npm_config_never_built_dependencies","NPM_CONFIG_ONLY_BUILT_DEPENDENCIES","npm_config_only_built_dependencies","NPM_CONFIG_IGNORED_BUILT_DEPENDENCIES","npm_config_ignored_built_dependencies"];async function pt(e,t){const i=l.join(e,"package.json");if(!n.existsSync(i))return;const a=await n.promises.readFile(i,"utf8");let s;try{s=JSON.parse(a)}catch{r(t,"WARN","Skip sanitizing package.json: invalid JSON",{projectDir:e});return}if(!s.pnpm||typeof s.pnpm!="object")return;const o=Oe.filter(p=>p in s.pnpm);if(o.length!==0){for(const p of o)delete s.pnpm[p];Object.keys(s.pnpm).length===0&&delete s.pnpm,await n.promises.writeFile(i,`${JSON.stringify(s,null,2)}
6
+ `,"utf8"),r(t,"INFO","Removed conflicting pnpm built dependency settings from package.json",{projectDir:e,removedKeys:o})}}async function ut(e,t){const i=l.join(e,"pnpm-workspace.yaml");if(!n.existsSync(i))return;const s=(await n.promises.readFile(i,"utf8")).split(`
7
+ `),o=[];let p=-1,u=[];for(const c of s){const N=c.trimStart(),P=c.length-N.length,w=Oe.find(g=>new RegExp(`^${g}:\\s*(.*)$`).test(N));if(w){u.push(w);const g=N.slice(w.length+1).trim();(!g||g==="|"||g===">")&&(p=P);continue}if(p>=0){if(N===""||P>p)continue;p=-1}o.push(c)}u.length!==0&&(await n.promises.writeFile(i,o.join(`
8
+ `),"utf8"),r(t,"INFO","Removed conflicting pnpm built dependency settings from pnpm-workspace.yaml",{projectDir:e,removedKeys:u}))}async function ze(e){if(!n.existsSync(e))return"";const t=await n.promises.readFile(e,"utf8"),i=t.split(`
9
9
  `).filter(a=>!/^\s*(never-built-dependencies|only-built-dependencies|ignored-built-dependencies)\b/.test(a)).join(`
10
- `);return i!==t&&await n.promises.writeFile(e,i,"utf8"),i}async function ut(e,t){await ct(e,t),await pt(e,t),await Oe(l.join(e,".npmrc"))}async function mt(e,t){await We(e,t),await ut(e,t);const i=l.join(e,".npmrc");let a=await Oe(i);const s=[];if(/dangerously-allow-all-builds\s*=/.test(a)||s.push("dangerously-allow-all-builds=true"),/production\s*=/.test(a)||s.push("production=false"),/confirm-modules-purge\s*=/.test(a)||s.push("confirm-modules-purge=false"),s.length>0){const r=a&&!a.endsWith(`
10
+ `);return i!==t&&await n.promises.writeFile(e,i,"utf8"),i}async function dt(e,t){await pt(e,t),await ut(e,t),await ze(l.join(e,".npmrc"))}async function mt(e,t){await Te(e,t),await dt(e,t);const i=l.join(e,".npmrc");let a=await ze(i);const s=[];if(/dangerously-allow-all-builds\s*=/.test(a)||s.push("dangerously-allow-all-builds=true"),/production\s*=/.test(a)||s.push("production=false"),/confirm-modules-purge\s*=/.test(a)||s.push("confirm-modules-purge=false"),s.length>0){const o=a&&!a.endsWith(`
11
11
  `)?`
12
- `:"";await n.promises.writeFile(i,`${a}${r}${s.join(`
12
+ `:"";await n.promises.writeFile(i,`${a}${o}${s.join(`
13
13
  `)}
14
- `,"utf8"),o(t,"INFO","Updated .npmrc for pnpm install",{projectDir:e,additions:s})}}function ke(){const e={...process.env};delete e.CI,delete e.NPM_CONFIG_PRODUCTION,delete e.npm_config_production;for(const t of lt)delete e[t];return e.NODE_ENV="development",e.NPM_CONFIG_PRODUCTION="false",e}async function be(e,t){await mt(e,t);const i=ke();return ue("pnpm",["install","--prefer-offline","--config.production=false","--config.confirmModulesPurge=false","--config.dangerouslyAllowAllBuilds=true"],{cwd:e,env:i})}function $e(e){const t=l.join(e,"pyproject.toml"),i=l.join(e,"requirements.txt");if(n.existsSync(t)||n.existsSync(i))return e;let a;try{a=n.readdirSync(e,{withFileTypes:!0})}catch{return null}for(const s of a)if(s.isDirectory()&&!he.has(s.name)&&s.name!==".venv"&&s.name!=="venv"&&s.name!=="__pycache__"){const r=$e(l.join(e,s.name));if(r)return r}return null}async function ft(e,t,i,a=null){const s=`computer:${e}:${t}`,r=String(i||"").trim().toLowerCase();if(!e)throw new w("userId cannot be empty",{field:"userId"});if(!t)throw new w("cId cannot be empty",{field:"cId"});if(!r)throw new w("programmingLanguage cannot be empty",{field:"programmingLanguage"});const p=await J(a,e,t,s);if(!n.existsSync(p))throw new w("workspace directory does not exist",{field:"workDir",workDir:p});o(s,"INFO","Install project dependencies request",{userId:e,cId:t,programmingLanguage:r});const m=ke();let c=null,N=null;if(r==="typescript"||r==="ts"){if(c=pe(p)||Ee(p),!c)throw new w("TypeScript project not found in workspace",{field:"projectDir"});N=await be(c,s)}else if(r==="python"||r==="py"){if(c=$e(p),!c)throw new w("Python project not found in workspace",{field:"projectDir"});const R=l.join(c,"pyproject.toml"),h=l.join(c,"requirements.txt");if(n.existsSync(R))N=await ue("pip",["install","-e","."],{cwd:c,env:m});else if(n.existsSync(h))N=await ue("pip",["install","-r","requirements.txt"],{cwd:c,env:m});else throw new w("No pyproject.toml or requirements.txt found in workspace",{field:"projectDir"})}else throw new w("Unsupported programmingLanguage",{field:"programmingLanguage",programmingLanguage:r});if(o(s,"INFO","Project dependencies install completed",{projectDir:c,programmingLanguage:r,exitCode:N.exitCode,stderrLength:N.stderr.length}),N.exitCode!==0)throw o(s,"ERROR","Project dependencies install failed",{projectDir:c,programmingLanguage:r,stderr:N.stderr}),new U(`Project dependencies install failed: ${N.stderr||N.stdout}`);return{message:"Project dependencies installed successfully",projectDir:c,programmingLanguage:r}}export{Ie as createWorkspace,et as pushSkillsToWorkspace,tt as initProjectTemplate,it as executeCommand,ot as deleteWorkspace,nt as zipWorkspace,at as buildAgentPackage,rt as cleanupBuildArtifacts,ft as installProjectDependencies};
14
+ `,"utf8"),r(t,"INFO","Updated .npmrc for pnpm install",{projectDir:e,additions:s})}}function ye(){const e={...process.env};delete e.CI,delete e.NPM_CONFIG_PRODUCTION,delete e.npm_config_production;for(const t of ct)delete e[t];return e.NODE_ENV="development",e.NPM_CONFIG_PRODUCTION="false",e}async function be(e,t){await mt(e,t);const i=ye();return de("pnpm",["install","--prefer-offline","--config.production=false","--config.confirmModulesPurge=false","--config.dangerouslyAllowAllBuilds=true"],{cwd:e,env:i})}function $e(e){const t=l.join(e,"pyproject.toml"),i=l.join(e,"requirements.txt");if(n.existsSync(t)||n.existsSync(i))return e;let a;try{a=n.readdirSync(e,{withFileTypes:!0})}catch{return null}for(const s of a)if(s.isDirectory()&&!ke.has(s.name)&&s.name!==".venv"&&s.name!=="venv"&&s.name!=="__pycache__"){const o=$e(l.join(e,s.name));if(o)return o}return null}async function ft(e,t,i,a=null){const s=`computer:${e}:${t}`,o=String(i||"").trim().toLowerCase();if(!e)throw new f("userId cannot be empty",{field:"userId"});if(!t)throw new f("cId cannot be empty",{field:"cId"});if(!o)throw new f("programmingLanguage cannot be empty",{field:"programmingLanguage"});const p=await H(a,e,t,s);if(!n.existsSync(p))throw new f("workspace directory does not exist",{field:"workDir",workDir:p});r(s,"INFO","Install project dependencies request",{userId:e,cId:t,programmingLanguage:o});const u=ye();let c=null,N=null;if(o==="typescript"||o==="ts"){if(c=ue(p)||Ae(p),!c)throw new f("TypeScript project not found in workspace",{field:"projectDir"});N=await be(c,s)}else if(o==="python"||o==="py"){if(c=$e(p),!c)throw new f("Python project not found in workspace",{field:"projectDir"});const P=l.join(c,"pyproject.toml"),w=l.join(c,"requirements.txt");if(n.existsSync(P))N=await de("pip",["install","-e","."],{cwd:c,env:u});else if(n.existsSync(w))N=await de("pip",["install","-r","requirements.txt"],{cwd:c,env:u});else throw new f("No pyproject.toml or requirements.txt found in workspace",{field:"projectDir"})}else throw new f("Unsupported programmingLanguage",{field:"programmingLanguage",programmingLanguage:o});if(r(s,"INFO","Project dependencies install completed",{projectDir:c,programmingLanguage:o,exitCode:N.exitCode,stderrLength:N.stderr.length}),N.exitCode!==0)throw r(s,"ERROR","Project dependencies install failed",{projectDir:c,programmingLanguage:o,stderr:N.stderr}),new K(`Project dependencies install failed: ${N.stderr||N.stdout}`);return{message:"Project dependencies installed successfully",projectDir:c,programmingLanguage:o}}export{et as createWorkspace,tt as pushSkillsToWorkspace,it as initProjectTemplate,nt as executeCommand,lt as deleteWorkspace,st as zipWorkspace,rt as buildAgentPackage,ot as cleanupBuildArtifacts,ft as installProjectDependencies};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuwax-file-server",
3
3
  "displayName": "nuwax-file-server",
4
- "version": "1.5.7",
4
+ "version": "1.5.9",
5
5
  "description": "Cross-platform file service deployment tool with start/stop/restart CLI commands",
6
6
  "type": "module",
7
7
  "main": "index.js",