nuwax-file-server 1.3.8 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1 -1
- package/dist/routes/computerRoutes.js +1 -1
- package/dist/routes/router.js +1 -1
- package/dist/service/projectService.js +1 -1
- package/dist/utils/common/AgentWorkspaceUtils.js +1 -1
- package/dist/utils/common/agentStoreUtils.js +2 -0
- package/dist/utils/computer/computerUtils.js +12 -12
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as ee}from"commander";import{createRequire as te}from"module";import f from"path";import U from"os";import c from"fs-extra";import{spawn as E}from"cross-spawn";import V from"tree-kill";import{fileURLToPath as q}from"url";import{execFileSync as R}from"child_process";import M from"http";import{createRequire as G}from"module";var I=f.dirname(q(import.meta.url)),H=G(import.meta.url),n={name:"nuwax-file-server",pidDir:f.join(U.tmpdir(),"nuwax-file-server"),pidFileName:"server.pid",lockFileName:"start.lock",defaultStopTimeout:3e4,defaultStartTimeout:3e4,staleLockTimeout:12e4,checkInterval:500};function $(){return f.join(n.pidDir,n.pidFileName)}function F(){return f.join(n.pidDir,n.lockFileName)}function J(){let e=[f.join(I,"..","server.js"),f.join(I,"server.js")];for(let t of e)if(c.existsSync(t))return t;return e[0]}function W(){let e=["../../package.json","../package.json"];for(let t of e)try{let o=H(t);if(o?.version)return o.version}catch{}return"unknown"}function x(){try{let e=$();if(!c.existsSync(e))return null;let t=c.readFileSync(e,"utf8"),o=JSON.parse(t);return!o||typeof o.pid!="number"?null:o}catch(e){return e.code!=="ENOENT"&&console.error(`Read PID file failed: ${e.message}`),null}}function B(e){try{let t=$();c.ensureDirSync(n.pidDir),c.writeFileSync(t,JSON.stringify(e,null,2)),console.debug(`PID file written: ${t}`)}catch(t){throw console.error(`Write PID file failed: ${t.message}`),t}}function v(){try{let e=$();c.existsSync(e)&&(c.removeSync(e),console.debug(`PID file deleted: ${e}`))}catch(e){console.error(`Delete PID file failed: ${e.message}`)}}function g(e){try{return process.kill(e,0),!0}catch(t){return t.code!=="ESRCH"}}function K(e){try{return k()?R("powershell",["-NoProfile","-Command",`(Get-CimInstance Win32_Process -Filter "ProcessId = ${e}").CommandLine`],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim():R("ps",["-p",String(e),"-o","command="],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return""}}function X(e){if(!g(e))return!1;let t=K(e);if(!t)return!1;let o=t.toLowerCase(),s=f.basename(f.join(I,"..","server.js")).toLowerCase(),i=n.name.toLowerCase();return o.includes(s)||o.includes(i)}function z(){c.ensureDirSync(n.pidDir);let e=F(),t=`${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2,10)}`,o=JSON.stringify({pid:process.pid,token:t,createdAt:new Date().toISOString()},null,2);try{let s=c.openSync(e,"wx");return c.writeSync(s,o),{fd:s,token:t}}catch(s){if(s.code!=="EEXIST")throw s;try{let i=c.readFileSync(e,"utf8"),r=JSON.parse(i),a=Number(r?.pid),u=r?.createdAt?new Date(r.createdAt).getTime():0,l=Date.now()-u,C=Number.isFinite(a)&&a>0&&g(a),p=!u||Number.isNaN(l)||l>n.staleLockTimeout;if(!C||p){c.removeSync(e);let S=c.openSync(e,"wx");return c.writeSync(S,o),console.warn("Detected stale start lock, auto cleaned"),{fd:S,token:t}}}catch{c.removeSync(e);let r=c.openSync(e,"wx");return c.writeSync(r,o),console.warn("Detected invalid start lock, auto cleaned"),{fd:r,token:t}}throw s}}function Q(e){let t=e?.fd,o=e?.token;try{t!=null&&c.closeSync(t)}catch{}try{let s=F();if(c.existsSync(s)){let i=!1;if(!o)i=!0;else try{let r=c.readFileSync(s,"utf8");i=JSON.parse(r)?.token===o}catch{i=!0}i&&c.removeSync(s)}}catch{}}function k(){return process.platform==="win32"}async function Y(e=n.defaultStopTimeout){let t=x();return t?X(t.pid)?(console.log(`Existing service process detected (PID: ${t.pid}), stopping before start...`),await h(t.pid,!1)&&await D(t.pid,e)?(v(),{success:!0,message:`Existing process ${t.pid} stopped gracefully`}):(console.warn(`Graceful stop timeout or failed for PID ${t.pid}, force stop...`),await h(t.pid,!0)?await D(t.pid,e)?(v(),{success:!0,message:`Existing process ${t.pid} stopped forcibly`}):{success:!1,message:`Existing process ${t.pid} did not exit after force stop`}:{success:!1,message:`Failed to stop existing process ${t.pid}`})):(console.log(`Found stale PID file (PID: ${t.pid}), clean it...`),v(),{success:!0,message:"Stale PID file cleaned"}):{success:!0,message:"No existing service process"}}async function h(e,t=!1){return new Promise(o=>{if(!g(e)){console.debug(`Process ${e} does not exist, already stopped`),o(!0);return}let s=t?"SIGKILL":"SIGTERM",i=k()?"taskkill":"tree-kill";if(console.debug(`Use ${i} to stop process ${e} (signal: ${s})`),k()){let r=t?["/F","/PID",String(e)]:["/PID",String(e)],a=E("taskkill",r,{stdio:["ignore","pipe","pipe"],windowsHide:!0}),u="";a.stdout.on("data",l=>{u+=l.toString()}),a.stderr.on("data",l=>{u+=l.toString()}),a.on("error",l=>{console.error(`Stop process failed: ${l.message}`),o(!1)}),a.on("close",l=>{l===0?(console.debug(`Process ${e} stopped`),o(!0)):(console.warn(`taskkill exit code: ${l}, output: ${u}`),t?o(!1):h(e,!0).then(o))})}else V(e,s,r=>{r?r.code==="ESRCH"?(console.debug(`Process ${e} does not exist`),o(!0)):(console.error(`Stop process failed: ${r.message}`),o(!1)):(console.debug(`Process ${e} stopped`),o(!0))})})}async function D(e,t=n.defaultStopTimeout){let o=Date.now();for(;g(e);){if(Date.now()-o>t)return console.warn(`Wait for process ${e} to stop timeout (${t}ms)`),!1;await new Promise(i=>setTimeout(i,n.checkInterval))}let s=Date.now()-o;return console.debug(`Process ${e} stopped after ${s}ms`),!0}async function Z(e,t=n.defaultStartTimeout){let o=Number(e);if(!Number.isFinite(o)||o<=0)return!1;let s=Date.now();for(;Date.now()-s<=t;){if(await new Promise(r=>{let a=M.get({host:"127.0.0.1",port:o,path:"/health",timeout:Math.min(2e3,n.checkInterval*4)},u=>{r(u.statusCode>=200&&u.statusCode<300),u.resume()});a.on("timeout",()=>{a.destroy(),r(!1)}),a.on("error",()=>r(!1))}))return!0;await new Promise(r=>setTimeout(r,n.checkInterval))}return!1}async function N(e={}){let{env:t,port:o,config:s}=e,i=null;console.log(`Start service ${n.name}...`);try{i=z()}catch(r){return r.code==="EEXIST"?{success:!1,pid:null,message:"Another start operation is in progress, please retry later"}:{success:!1,pid:null,message:`Acquire start lock failed: ${r.message}`}}try{let r=Number(e.timeout)||n.defaultStopTimeout,a=await Y(r);if(!a.success)return{success:!1,pid:null,message:`Service start blocked: ${a.message}`};let u={...process.env};t&&(u.NODE_ENV=t,console.log(`Environment: ${t}`)),o&&(u.PORT=o,console.log(`Port: ${o}`)),s&&(u.CONFIG_FILE=s,console.log(`Configuration file: ${s}`));let l=J(),p=E("node",[l,...[]],{env:u,stdio:["pipe","pipe","pipe"],detached:!0,cwd:process.cwd()});if(p.stdout.on("data",m=>{process.stdout.write(m)}),p.stderr.on("data",m=>{process.stderr.write(m)}),p.on("error",m=>{console.error(`Start service failed: ${m.message}`)}),await new Promise(m=>setTimeout(m,2e3)),!g(p.pid))return{success:!1,pid:null,message:"Service start failed"};let S={pid:p.pid,startedAt:new Date().toISOString(),env:t||process.env.NODE_ENV||"production",port:o||process.env.PORT||"60000",version:W(),platform:process.platform};B(S);let P=Number(e.startTimeout)||n.defaultStartTimeout;return await Z(S.port,P)?(p.unref(),console.log(`Service started (PID: ${p.pid})`),console.log(`Service address: http://localhost:${S.port}`),{success:!0,pid:p.pid,message:"Service started successfully"}):(console.error(`Service health check timeout (${P}ms), stop failed instance...`),await h(p.pid,!0),v(),{success:!1,pid:null,message:`Service health check timeout (${P}ms)`})}finally{Q(i)}}async function T(e={}){let{force:t=!1,timeout:o=n.defaultStopTimeout}=e;console.log(`Stop service ${n.name}...`);let s=x();if(!s)return{success:!1,message:"Service not found"};if(!g(s.pid))return console.log("Service process has stopped, clean PID file..."),v(),{success:!0,message:"Service has stopped (process has exited)"};if(!await h(s.pid,t))return{success:!1,message:"Stop service failed"};let r=await D(s.pid,o);return v(),r?{success:!0,message:"Service has stopped"}:{success:!1,message:"Service stop timeout"}}async function O(e={}){console.log(`Restart service ${n.name}...`);let t=await T(e);!t.success&&t.message!=="Service not found"&&console.warn(`Stop service failed: ${t.message}`),await new Promise(s=>setTimeout(s,2e3));let o=await N(e);return o.success?{success:!0,pid:o.pid,message:"Service has restarted"}:{success:!1,pid:null,message:`Restart failed: ${o.message}`}}function _(){let e=x();return e?g(e.pid)?{running:!0,pidInfo:e,message:"Service running"}:{running:!1,pidInfo:e,message:"Service process does not exist"}:{running:!1,pidInfo:null,message:"Service not running"}}function L(e){try{let t=new Date(e),o=new Date;if(isNaN(t.getTime()))return"unknown";let s=Math.floor((o-t)/1e3),i=Math.floor(s/3600),r=Math.floor(s%3600/60),a=s%60;return i>0?`${i} hours ${r} minutes ${a} seconds`:r>0?`${r} minutes ${a} seconds`:`${a} seconds`}catch{return"unknown"}}var Pe=te(import.meta.url),A="1.3.8",d=new ee,w={reset:"\x1B[0m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",cyan:"\x1B[36m"};function j(e){console.error(`${w.red}ERROR: ${e}${w.reset}`)}function b(e){console.log(`${w.green}${e}${w.reset}`)}function oe(e){console.log(`${w.blue}${e}${w.reset}`)}function y(e){return async(...t)=>{try{let o=await e(...t);o&&o.success===!1?(j(o.message||"Command failed"),process.exitCode=1):process.exitCode=0}catch(o){j(o?.message||"Command failed with unexpected error"),process.exitCode=1}}}async function se(e){let t=await N({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(`Service started (PID: ${t.pid})`),t}async function re(e){let t=await T({force:e.force,timeout:Number(e.timeout)});return t.success&&b(t.message||"Service stopped"),t}async function ne(e){let t=await O({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(t.message||"Service restarted"),t}function ie(){let e=_();return oe(`${n.name} service status:`),console.log(""),console.log(` Service name: ${n.name}`),console.log(` Running status: ${e.running?"Running":"Stopped"}`),console.log(` Message: ${e.message}`),console.log(` PID file: ${$()}`),e.pidInfo&&(console.log(` Process ID: ${e.pidInfo.pid}`),console.log(` Environment: ${e.pidInfo.env||"Unknown"}`),console.log(` Port: ${e.pidInfo.port||"Unknown"}`),console.log(` Version: ${e.pidInfo.version||A}`),console.log(` Platform: ${e.pidInfo.platform||process.platform}`),console.log(` Started at: ${e.pidInfo.startedAt||"Unknown"}`),console.log(` Uptime: ${L(e.pidInfo.startedAt)}`)),console.log(""),{success:e.running,message:e.message}}function ce(){d.name("nuwax-file-server").description("Cross-platform file service deployment tool, supporting start/stop/restart/status").version(A,"-v, --version","Display version number").helpOption("-h, --help","Display help information"),d.command("start").allowUnknownOption().description("Start service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(se)),d.command("stop").description("Stop service").option("--force","Force stop").option("--timeout <ms>","\u505C\u6B62\u670D\u52A1\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).action(y(re)),d.command("restart").allowUnknownOption().description("Restart service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(ne)),d.command("status").description("View service status").action(y(ie)),d.command("help").description("Display help information").action(()=>{d.outputHelp()}),d.parse(process.argv),process.argv.slice(2).length||(d.outputHelp(),process.exitCode=0)}ce();
|
|
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.4.0",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 S from"express";import F from"multer";import h from"fs";import w from"path";import{ValidationError as k,asyncHandler as c}from"../utils/error/errorHandler.js";import{log as u}from"../utils/log/logUtils.js";import N from"../appConfig/index.js";import{createWorkspace as j,pushSkillsToWorkspace as z,initProjectTemplate as b,installProjectDependencies as C,executeCommand as A,deleteWorkspace as D,zipWorkspace as O,buildAgentPackage as P,cleanupBuildArtifacts as T}from"../utils/computer/computerUtils.js";import{getFileList as E,resolveExistingFile as x,searchFiles as U,updateFiles as v,uploadFile as q,uploadFiles as R,generateFile as H,downloadAllFiles as M,getLatestLogs as _,importProject as L}from"../utils/computer/computerFileUtils.js";const y=S.Router(),g=F({storage:F.diskStorage({destination:(s,i,t)=>{try{const e=N.COMPUTER_WORKSPACE_DIR;if(!e)return t(new Error("COMPUTER_WORKSPACE_DIR is not configured, cannot determine upload temporary directory"));const o=s.body?.userId||"unknown",a=s.body?.cId||"unknown",r=w.join(e,String(o),String(a),".tmp");h.existsSync(r)||h.mkdirSync(r,{recursive:!0}),t(null,r)}catch(e){t(e)}},filename:(s,i,t)=>{const e=w.extname(i.originalname)||".zip",o=w.basename(i.originalname,e),a=`${Date.now()}_${Math.round(Math.random()*1e6)}`;t(null,`${o}_${a}${e}`)}}),limits:{fileSize:N.UPLOAD_MAX_FILE_SIZE_BYTES}});function W(s,i,t,e){if(s&&s.name==="MulterError"||s&&(s.name==="ValidationError"||s instanceof k))return e(s);e(s)}const B=[{path:"/create-workspace",method:"post",middleware:g.single("file"),handler:c(async(s,i)=>{const{userId:t,cId:e}=s.body||{},o=s.file||null,a=`computer:${t}:${e}`;u(a,"INFO","Create workspace request",{userId:t,cId:e,hasFile:!!o,fileName:o?.originalname,fileSize:o?.size});const r=await j(t,e,o);i.status(200).json({success:!0,...r})})},{path:"/create-workspace-v2",method:"post",middleware:g.single("file"),handler:c(async(s,i)=>{const{userId:t,cId:e,skillUrls:o,mcpServersConfig:a,hooksConfig:r,permissionsConfig:n,hookScripts:l}=s.body||{},d=s.file||null,f=`computer:${t}:${e}`;let p=o;if(typeof o=="string")try{const $=JSON.parse(o);p=Array.isArray($)?$:[o]}catch{p=[o]}let m=l;if(typeof l=="string")try{m=JSON.parse(l)}catch{m=null}u(f,"INFO","Create workspace v2 request",{userId:t,cId:e,hasFile:!!d,fileName:d?.originalname,fileSize:d?.size,skillUrlsCount:Array.isArray(p)?p.length:0,hasMcpServersConfig:!!a,hasHooksConfig:!!r,hasPermissionsConfig:!!n,hookScriptsCount:Array.isArray(m)?m.length:0});const I=await j(t,e,d,p,a,n,r,m);i.status(200).json({success:!0,...I})})},{path:"/push-skills-to-workspace",method:"post",middleware:g.single("file"),handler:c(async(s,i)=>{const{userId:t,cId:e}=s.body||{},o=s.file||null,a=`computer:${t}:${e}`;u(a,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42",{userId:t,cId:e,hasFile:!!o,fileName:o?.originalname,fileSize:o?.size});const r=await z(t,e,o);i.status(200).json({success:!0,...r})})},{path:"/push-skills-to-workspace-v2",method:"post",middleware:g.single("file"),handler:c(async(s,i)=>{const{userId:t,cId:e,skillUrls:o}=s.body||{},a=s.file||null,r=`computer:${t}:${e}`;let n=o;if(typeof o=="string")try{const d=JSON.parse(o);n=Array.isArray(d)?d:[o]}catch{n=[o]}u(r,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42(v2)",{userId:t,cId:e,hasFile:!!a,fileName:a?.originalname,fileSize:a?.size,skillUrlsCount:Array.isArray(n)?n.length:0});const l=await z(t,e,a,n);i.status(200).json({success:!0,...l})})},{path:"/get-file-list",method:"get",handler:c(async(s,i)=>{const{userId:t,cId:e,proxyPath:o,customTargetDir:a,relativePath:r,recursive:n}=s.query,l=await E(t,e,o,a,r,n);i.status(200).json({success:!0,...l})})},{path:"/resolve-file",method:"get",handler:c(async(s,i)=>{const{userId:t,cId:e,proxyPath:o,customTargetDir:a,filePath:r}=s.query,n=await x(t,e,r,o,a);i.status(200).json({success:!0,...n})})},{path:"/search-files",method:"get",handler:c(async(s,i)=>{const{userId:t,cId:e,proxyPath:o,customTargetDir:a,relativePath:r,kw:n,limit:l,maxVisit:d,timeoutMs:f}=s.query,p=await U(t,e,o,n,a,r,l,d,f);i.status(200).json({success:!0,...p})})},{path:"/files-update",method:"post",handler:c(async(s,i)=>{const{userId:t,cId:e,files:o,customTargetDir:a}=s.body||{},r=`computer:${t}:${e}`;u(r,"INFO","Files update",{userId:t,cId:e,filesCount:o?o.length:0}),o&&Array.isArray(o)&&o.forEach(l=>{if(l&&typeof l.contents=="string"&&l.contents)try{l.contents=decodeURIComponent(l.contents)}catch(d){u(r,"WARN","Decode file content failed",{fileName:l.name,error:d.message})}});const n=await v(t,e,o,a);i.status(200).json(n)})},{path:"/upload-file",method:"post",middleware:g.single("file"),handler:c(async(s,i)=>{const{userId:t,cId:e,filePath:o,customTargetDir:a}=s.body||{},r=s.file,n=`computer:${t}:${e}`;u(n,"INFO","Upload single file",{userId:t,cId:e,filePath:o});const d={buffer:await h.promises.readFile(r.path),originalname:r.originalname,mimetype:r.mimetype,size:r.size};try{const f=await q(t,e,d,o,a);i.status(200).json(f)}finally{h.existsSync(r.path)&&await h.promises.unlink(r.path)}})},{path:"/generate-file",method:"post",handler:c(async(s,i)=>{const{userId:t,cId:e,fileName:o,content:a,customTargetDir:r}=s.body||{},n=`computer:${t}:${e}`;u(n,"INFO","Generate file request",{userId:t,cId:e,fileName:o,contentLength:typeof a=="string"?a.length:0});const l=await H(t,e,o,a,r);i.status(200).json(l)})},{path:"/import-project",method:"post",middleware:g.single("file"),handler:c(async(s,i)=>{const{userId:t,cId:e,customTargetDir:o}=s.body||{},a=s.file,r=`computer:${t}:${e}`;if(u(r,"INFO","Import project request",{userId:t,cId:e,fileName:a?.originalname,fileSize:a?.size}),!a)throw new k("file is required",{field:"file"});const n=await L(t,e,a,o);i.status(200).json(n)})},{path:"/upload-files",method:"post",middleware:g.array("files"),handler:c(async(s,i)=>{const{userId:t,cId:e,filePaths:o,customTargetDir:a}=s.body||{},r=s.files||[],n=`computer:${t}:${e}`,l=Array.isArray(o)?o:typeof o=="string"?[o]:o;u(n,"INFO","Batch upload files request",{userId:t,cId:e,filesCount:r.length,filePathsCount:Array.isArray(l)?l.length:0});const d=[],f=[];try{for(const m of r){f.push(m.path);const I=await h.promises.readFile(m.path);d.push({buffer:I,originalname:m.originalname,mimetype:m.mimetype,size:m.size})}const p=await R(t,e,d,l,a);i.status(200).json(p)}finally{for(const p of f)if(h.existsSync(p))try{await h.promises.unlink(p)}catch(m){u(n,"WARN","Clean temporary file failed",{tempPath:p,error:m.message})}}})},{path:"/install-project",method:"post",handler:c(async(s,i)=>{const{userId:t,cId:e,programmingLanguage:o}=s.body||{},a=`computer:${t}:${e}`;u(a,"INFO","Install project request",{userId:t,cId:e,programmingLanguage:o});const r=await C(t,e,o);i.status(200).json({success:!0,...r})})},{path:"/init-project-template",method:"post",middleware:g.single("file"),handler:c(async(s,i)=>{const{userId:t,cId:e,enableGit:o}=s.body||{},a=s.file||null,r=`computer:${t}:${e}`;u(r,"INFO","Init project template request",{userId:t,cId:e,hasFile:!!a,fileName:a?.originalname,fileSize:a?.size,enableGit:o});const n=await b(t,e,a,o);i.status(200).json({success:!0,...n})})},{path:"/download-all-files",method:"get",handler:c(async(s,i)=>{const{userId:t,cId:e,customTargetDir:o}=s.query||{},a=`computer:${t}:${e}`;u(a,"INFO","Download all files request",{userId:t,cId:e});const{archive:r,zipFileName:n}=await M(t,e,o);i.setHeader("Content-Type","application/zip");const l=encodeURIComponent(n);i.setHeader("Content-Disposition",`attachment; filename="${l}"; filename*=UTF-8''${l}`),r.on("error",d=>{i.destroy(d)}),r.pipe(i),r.finalize()})},{path:"/execute-command",method:"post",handler:c(async(s,i)=>{const{userId:t,cId:e,command:o}=s.body||{},a=`computer:${t}:${e}`;u(a,"INFO","Execute command request",{userId:t,cId:e,command:o});const r=await A(t,e,o);i.status(200).json({success:!0,...r})})},{path:"/delete-workspace",method:"post",handler:c(async(s,i)=>{const{userId:t,cId:e}=s.body||{},o=`computer:${t}:${e}`;u(o,"INFO","Delete workspace request",{userId:t,cId:e});const a=await D(t,e);i.status(200).json({success:!0,...a})})},{path:"/get-logs",method:"get",handler:c(async(s,i)=>{const{userId:t,cId:e,tailLines:o}=s.query,a=o?parseInt(o,10):200,r=Number.isFinite(a)&&a>0?a:200,n=await _(t,e,r);i.status(200).json(n)})},{path:"/zip-workspace",method:"post",handler:c(async(s,i)=>{const{userId:t,cId:e,excludeDirs:o}=s.body||{},a=`computer:${t}:${e}`;u(a,"INFO","Zip workspace request",{userId:t,cId:e,hasExcludeDirs:!!o});const{archive:r,zipFileName:n}=await O(t,e,o||null);i.setHeader("Content-Type","application/zip");const l=encodeURIComponent(n);i.setHeader("Content-Disposition",`attachment; filename="${l}"; filename*=UTF-8''${l}`),r.on("error",d=>{i.destroy(d)}),r.pipe(i),r.finalize()})},{path:"/build-agent-package",method:"post",handler:c(async(s,i)=>{const{userId:t,cId:e,agentId:o,version:a}=s.body||{},r=`computer:${t}:${e}`;u(r,"INFO","Build agent package request",{userId:t,cId:e,agentId:o,version:a});const n=await P(t,e,o,a);i.status(200).json({success:!0,...n})})},{path:"/cleanup-build-artifacts",method:"post",handler:c(async(s,i)=>{const{userId:t,cId:e}=s.body||{},o=`computer:${t}:${e}`;u(o,"INFO","Cleanup build artifacts request",{userId:t,cId:e});const a=await T(t,e);i.status(200).json({success:!0,...a})})}];B.forEach(s=>{if(s.middleware)y[s.method](s.path,s.middleware,s.handler);else if(s.customHandler){const i=[];s.handler&&i.push(s.handler),s.decodeMiddleware&&i.push(s.decodeMiddleware),i.push(W),i.push(s.customHandler),y[s.method](s.path,...i)}else y[s.method](s.path,s.handler)});export default y;
|
|
1
|
+
import E from"express";import z from"multer";import y from"fs";import j from"path";import{ValidationError as A,asyncHandler as c}from"../utils/error/errorHandler.js";import{log as d}from"../utils/log/logUtils.js";import b from"../appConfig/index.js";import{createWorkspace as C,pushSkillsToWorkspace as O,initProjectTemplate as x,installProjectDependencies as v,executeCommand as q,deleteWorkspace as M,zipWorkspace as R,buildAgentPackage as H,cleanupBuildArtifacts as _}from"../utils/computer/computerUtils.js";import{getFileList as B,resolveExistingFile as L,searchFiles as W,updateFiles as J,uploadFile as V,uploadFiles as G,generateFile as K,downloadAllFiles as Z,getLatestLogs as X,importProject as Y}from"../utils/computer/computerFileUtils.js";const S=E.Router(),g=z({storage:z.diskStorage({destination:(s,r,t)=>{try{const e=b.COMPUTER_WORKSPACE_DIR;if(!e)return t(new Error("COMPUTER_WORKSPACE_DIR is not configured, cannot determine upload temporary directory"));const a=s.body?.userId||"unknown",o=s.body?.cId||"unknown",i=j.join(e,String(a),String(o),".tmp");y.existsSync(i)||y.mkdirSync(i,{recursive:!0}),t(null,i)}catch(e){t(e)}},filename:(s,r,t)=>{const e=j.extname(r.originalname)||".zip",a=j.basename(r.originalname,e),o=`${Date.now()}_${Math.round(Math.random()*1e6)}`;t(null,`${a}_${o}${e}`)}}),limits:{fileSize:b.UPLOAD_MAX_FILE_SIZE_BYTES}});function Q(s,r,t,e){if(s&&s.name==="MulterError"||s&&(s.name==="ValidationError"||s instanceof A))return e(s);e(s)}const ee=[{path:"/create-workspace",method:"post",middleware:g.single("file"),handler:c(async(s,r)=>{const{userId:t,cId:e}=s.body||{},a=s.file||null,o=`computer:${t}:${e}`;d(o,"INFO","Create workspace request",{userId:t,cId:e,hasFile:!!a,fileName:a?.originalname,fileSize:a?.size});const i=await C(t,e,a);r.status(200).json({success:!0,...i})})},{path:"/create-workspace-v2",method:"post",middleware:g.single("file"),handler:c(async(s,r)=>{const{userId:t,cId:e,agentId:a,skillUrls:o,skillUrlMap:i,skillNames:l,updateSkillNames:n,mcpServersConfig:u,hooksConfig:p,permissionsConfig:f,hookScripts:m}=s.body||{},w=s.file||null,D=`computer:${t}:${e}`;let k=o;if(typeof o=="string")try{k=JSON.parse(o)}catch{k=[o]}let I=i;if(typeof i=="string")try{I=JSON.parse(i)}catch{I=null}let N=l;if(typeof l=="string")try{const h=JSON.parse(l);N=Array.isArray(h)?h:[l]}catch{N=l.split(",").map(h=>h.trim()).filter(Boolean)}let $=n;if(typeof n=="string")try{const h=JSON.parse(n);$=Array.isArray(h)?h:[n]}catch{$=n.split(",").map(h=>h.trim()).filter(Boolean)}let F=m;if(typeof m=="string")try{F=JSON.parse(m)}catch{F=null}const P=Array.isArray(k)?k.length:0,U=I&&typeof I=="object"&&!Array.isArray(I)?Object.keys(I).length:0;d(D,"INFO","Create workspace v2 request",{userId:t,cId:e,agentId:a,hasFile:!!w,fileName:w?.originalname,fileSize:w?.size,skillUrlsCount:P,skillUrlMapCount:U,skillNamesCount:Array.isArray(N)?N.length:0,updateSkillNamesCount:Array.isArray($)?$.length:0,hasMcpServersConfig:!!u,hasHooksConfig:!!p,hasPermissionsConfig:!!f,hookScriptsCount:Array.isArray(F)?F.length:0});const T=await C(t,e,w,k,u,f,p,F,a,N,$,I);r.status(200).json({success:!0,...T})})},{path:"/push-skills-to-workspace",method:"post",middleware:g.single("file"),handler:c(async(s,r)=>{const{userId:t,cId:e}=s.body||{},a=s.file||null,o=`computer:${t}:${e}`;d(o,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42",{userId:t,cId:e,hasFile:!!a,fileName:a?.originalname,fileSize:a?.size});const i=await O(t,e,a);r.status(200).json({success:!0,...i})})},{path:"/push-skills-to-workspace-v2",method:"post",middleware:g.single("file"),handler:c(async(s,r)=>{const{userId:t,cId:e,agentId:a,skillUrls:o}=s.body||{},i=s.file||null,l=`computer:${t}:${e}`;let n=o;if(typeof o=="string")try{const p=JSON.parse(o);n=Array.isArray(p)?p:[o]}catch{n=[o]}d(l,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42(v2)",{userId:t,cId:e,agentId:a,hasFile:!!i,fileName:i?.originalname,fileSize:i?.size,skillUrlsCount:Array.isArray(n)?n.length:0});const u=await O(t,e,i,n,a);r.status(200).json({success:!0,...u})})},{path:"/get-file-list",method:"get",handler:c(async(s,r)=>{const{userId:t,cId:e,proxyPath:a,customTargetDir:o,relativePath:i,recursive:l}=s.query,n=await B(t,e,a,o,i,l);r.status(200).json({success:!0,...n})})},{path:"/resolve-file",method:"get",handler:c(async(s,r)=>{const{userId:t,cId:e,proxyPath:a,customTargetDir:o,filePath:i}=s.query,l=await L(t,e,i,a,o);r.status(200).json({success:!0,...l})})},{path:"/search-files",method:"get",handler:c(async(s,r)=>{const{userId:t,cId:e,proxyPath:a,customTargetDir:o,relativePath:i,kw:l,limit:n,maxVisit:u,timeoutMs:p}=s.query,f=await W(t,e,a,l,o,i,n,u,p);r.status(200).json({success:!0,...f})})},{path:"/files-update",method:"post",handler:c(async(s,r)=>{const{userId:t,cId:e,files:a,customTargetDir:o}=s.body||{},i=`computer:${t}:${e}`;d(i,"INFO","Files update",{userId:t,cId:e,filesCount:a?a.length:0}),a&&Array.isArray(a)&&a.forEach(n=>{if(n&&typeof n.contents=="string"&&n.contents)try{n.contents=decodeURIComponent(n.contents)}catch(u){d(i,"WARN","Decode file content failed",{fileName:n.name,error:u.message})}});const l=await J(t,e,a,o);r.status(200).json(l)})},{path:"/upload-file",method:"post",middleware:g.single("file"),handler:c(async(s,r)=>{const{userId:t,cId:e,filePath:a,customTargetDir:o}=s.body||{},i=s.file,l=`computer:${t}:${e}`;d(l,"INFO","Upload single file",{userId:t,cId:e,filePath:a});const u={buffer:await y.promises.readFile(i.path),originalname:i.originalname,mimetype:i.mimetype,size:i.size};try{const p=await V(t,e,u,a,o);r.status(200).json(p)}finally{y.existsSync(i.path)&&await y.promises.unlink(i.path)}})},{path:"/generate-file",method:"post",handler:c(async(s,r)=>{const{userId:t,cId:e,fileName:a,content:o,customTargetDir:i}=s.body||{},l=`computer:${t}:${e}`;d(l,"INFO","Generate file request",{userId:t,cId:e,fileName:a,contentLength:typeof o=="string"?o.length:0});const n=await K(t,e,a,o,i);r.status(200).json(n)})},{path:"/import-project",method:"post",middleware:g.single("file"),handler:c(async(s,r)=>{const{userId:t,cId:e,customTargetDir:a}=s.body||{},o=s.file,i=`computer:${t}:${e}`;if(d(i,"INFO","Import project request",{userId:t,cId:e,fileName:o?.originalname,fileSize:o?.size}),!o)throw new A("file is required",{field:"file"});const l=await Y(t,e,o,a);r.status(200).json(l)})},{path:"/upload-files",method:"post",middleware:g.array("files"),handler:c(async(s,r)=>{const{userId:t,cId:e,filePaths:a,customTargetDir:o}=s.body||{},i=s.files||[],l=`computer:${t}:${e}`,n=Array.isArray(a)?a:typeof a=="string"?[a]:a;d(l,"INFO","Batch upload files request",{userId:t,cId:e,filesCount:i.length,filePathsCount:Array.isArray(n)?n.length:0});const u=[],p=[];try{for(const m of i){p.push(m.path);const w=await y.promises.readFile(m.path);u.push({buffer:w,originalname:m.originalname,mimetype:m.mimetype,size:m.size})}const f=await G(t,e,u,n,o);r.status(200).json(f)}finally{for(const f of p)if(y.existsSync(f))try{await y.promises.unlink(f)}catch(m){d(l,"WARN","Clean temporary file failed",{tempPath:f,error:m.message})}}})},{path:"/install-project",method:"post",handler:c(async(s,r)=>{const{userId:t,cId:e,programmingLanguage:a}=s.body||{},o=`computer:${t}:${e}`;d(o,"INFO","Install project request",{userId:t,cId:e,programmingLanguage:a});const i=await v(t,e,a);r.status(200).json({success:!0,...i})})},{path:"/init-project-template",method:"post",middleware:g.single("file"),handler:c(async(s,r)=>{const{userId:t,cId:e,enableGit:a}=s.body||{},o=s.file||null,i=`computer:${t}:${e}`;d(i,"INFO","Init project template request",{userId:t,cId:e,hasFile:!!o,fileName:o?.originalname,fileSize:o?.size,enableGit:a});const l=await x(t,e,o,a);r.status(200).json({success:!0,...l})})},{path:"/download-all-files",method:"get",handler:c(async(s,r)=>{const{userId:t,cId:e,customTargetDir:a}=s.query||{},o=`computer:${t}:${e}`;d(o,"INFO","Download all files request",{userId:t,cId:e});const{archive:i,zipFileName:l}=await Z(t,e,a);r.setHeader("Content-Type","application/zip");const n=encodeURIComponent(l);r.setHeader("Content-Disposition",`attachment; filename="${n}"; filename*=UTF-8''${n}`),i.on("error",u=>{r.destroy(u)}),i.pipe(r),i.finalize()})},{path:"/execute-command",method:"post",handler:c(async(s,r)=>{const{userId:t,cId:e,command:a}=s.body||{},o=`computer:${t}:${e}`;d(o,"INFO","Execute command request",{userId:t,cId:e,command:a});const i=await q(t,e,a);r.status(200).json({success:!0,...i})})},{path:"/delete-workspace",method:"post",handler:c(async(s,r)=>{const{userId:t,cId:e}=s.body||{},a=`computer:${t}:${e}`;d(a,"INFO","Delete workspace request",{userId:t,cId:e});const o=await M(t,e);r.status(200).json({success:!0,...o})})},{path:"/get-logs",method:"get",handler:c(async(s,r)=>{const{userId:t,cId:e,tailLines:a}=s.query,o=a?parseInt(a,10):200,i=Number.isFinite(o)&&o>0?o:200,l=await X(t,e,i);r.status(200).json(l)})},{path:"/zip-workspace",method:"post",handler:c(async(s,r)=>{const{userId:t,cId:e,excludeDirs:a}=s.body||{},o=`computer:${t}:${e}`;d(o,"INFO","Zip workspace request",{userId:t,cId:e,hasExcludeDirs:!!a});const{archive:i,zipFileName:l}=await R(t,e,a||null);r.setHeader("Content-Type","application/zip");const n=encodeURIComponent(l);r.setHeader("Content-Disposition",`attachment; filename="${n}"; filename*=UTF-8''${n}`),i.on("error",u=>{r.destroy(u)}),i.pipe(r),i.finalize()})},{path:"/build-agent-package",method:"post",handler:c(async(s,r)=>{const{userId:t,cId:e,agentId:a,version:o}=s.body||{},i=`computer:${t}:${e}`;d(i,"INFO","Build agent package request",{userId:t,cId:e,agentId:a,version:o});const l=await H(t,e,a,o);r.status(200).json({success:!0,...l})})},{path:"/cleanup-build-artifacts",method:"post",handler:c(async(s,r)=>{const{userId:t,cId:e}=s.body||{},a=`computer:${t}:${e}`;d(a,"INFO","Cleanup build artifacts request",{userId:t,cId:e});const o=await _(t,e);r.status(200).json({success:!0,...o})})}];ee.forEach(s=>{if(s.middleware)S[s.method](s.path,s.middleware,s.handler);else if(s.customHandler){const r=[];s.handler&&r.push(s.handler),s.decodeMiddleware&&r.push(s.decodeMiddleware),r.push(Q),r.push(s.customHandler),S[s.method](s.path,...r)}else S[s.method](s.path,s.handler)});export default S;
|
package/dist/routes/router.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import i from"express";import
|
|
1
|
+
import i from"express";import u from"./buildRoutes.js";import c from"./projectRoutes.js";import l from"./codeRoutes.js";import d from"./computerRoutes.js";import h from"./gitRoutes.js";import a from"path";import f from"fs";import{fileURLToPath as g}from"url";const v=a.dirname(g(import.meta.url));let s="1.0.0";try{const r=a.resolve(v,"..","..","package.json");s=JSON.parse(f.readFileSync(r,"utf8")).version||"1.0.0"}catch{}const e=i.Router();e.get("/",(r,o)=>{o.send("Hello")}),e.get("/health",(r,o)=>{const p=Math.floor(process.uptime()),t=process.memoryUsage(),m={heapUsed:Math.round(t.heapUsed/1024/1024*100)/100,heapTotal:Math.round(t.heapTotal/1024/1024*100)/100,rss:Math.round(t.rss/1024/1024*100)/100,external:Math.round(t.external/1024/1024*100)/100},n={status:"ok",timestamp:Date.now(),uptime:p,version:s,platform:process.platform,nodeVersion:process.version,pid:process.pid,memory:m,env:process.env.NODE_ENV||"unknown"};o.json(n)}),e.get("/api/version",(r,o)=>{o.status(200).json({success:!0,version:s})}),e.use("/api/build",u),e.use("/api/project",c),e.use("/api/project",l),e.use("/api/computer",d),e.use("/api/git",h);export default e;
|
|
@@ -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 B}from"../utils/build/stopDevUtils.js";import{ValidationError as y,BusinessError as W,SystemError as P,FileError as b,ResourceError as A}from"../utils/error/errorHandler.js";import{sanitizeSensitivePaths as G}from"../utils/common/sensitiveUtils.js";import{removeNodeModules as j}from"../utils/buildDependency/dependencyManager.js";import{backupProjectToZip as I,copyDirectoryFiltered as ee}from"../utils/project/backupUtils.js";import{createPnpmNpmrc as L}from"../utils/common/npmrcUtils.js";import{copyNodeModulesFromCache as C}from"../utils/common/templateCacheUtils.js";import{resolveProjectPath as F}from"../utils/common/projectPathUtils.js";import{ensurePrimaryAgentDirs as te,syncAgents as ie}from"../utils/common/AgentWorkspaceUtils.js";async function J(e,r="react",c={}){const o=Date.now();if(!e)throw new y("Project ID cannot be empty",{field:"projectId"});const s=["react","vue3"];if(!s.includes(r))throw new y("Template type is invalid, only supports react or vue3",{field:"templateType",templateType:r,allowedValues:s});const m={react:E.INIT_PROJECT_NAME_REACT||"react-vite-template",vue3:E.INIT_PROJECT_NAME_VUE3||"vue3-vite-template"}[r],u=F(e,c);if(t.existsSync(u))throw new W(`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 a of d){const h=p.join(l,a.name),N=p.join(u,a.name);a.isDirectory()?(await t.promises.mkdir(N,{recursive:!0}),await ee(h,N)):a.isFile()&&(await t.promises.mkdir(p.dirname(N),{recursive:!0}),await t.promises.copyFile(h,N))}i(e,"DEBUG","Try copying node_modules from template cache",{projectPath:u});const D=await C(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()-o}),E.GIT_ENABLED)try{const a=await import("./gitService.js");await a.init({workspaceType:"pageApp",projectId:e,isolationContext:c}),await a.commit({workspaceType:"pageApp",projectId:e,isolationContext:c,message:`init project: ${e}`})}catch(a){i(e,"WARN","Git init/commit failed, skipping",{error:a.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()-o}),new P(`Project ${e} initialization failed: ${f.message}`,{projectId:e,projectPath:u,originalError:f.message})}}async function re(e){const r=await t.promises.readdir(e,{withFileTypes:!0}),c=E.TOP_LEVEL_NOISE_PATTERNS,o=r.filter(s=>{const n=s.name;return n.startsWith(".")?!1:!c.some(m=>m.endsWith("*")?n.startsWith(m.slice(0,-1)):n===m)});if(o.length===1&&o[0].isDirectory()){const s=p.join(e,o[0].name),n=p.join(e,`temp_${Date.now()}`);try{await t.promises.rename(s,n)}catch(u){if(u.code==="EXDEV")await t.promises.cp(s,n,{recursive:!0}),await t.promises.rm(s,{recursive:!0,force:!0});else throw u}const m=await t.promises.readdir(n);for(const u of m){const f=p.join(n,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(n,{recursive:!0,force:!0})}}async function U(e,r={}){if(!e)throw new y("Project ID cannot be empty",{field:"projectId"});const c=F(e,r);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(o){throw i(e,"ERROR",`Failed to clean project directory: ${o.message}`,{projectId:e,projectPath:c,originalError:o.message}),new P(`Failed to clean project directory: ${o.message}`,{projectId:e,projectPath:c,originalError:o.message})}else i(e,"INFO",`Project directory does not exist, no need to clean: ${c}`,{projectId:e})}function se(e){if(!t.existsSync(e))return!0;try{return t.readdirSync(e).filter(o=>!o.startsWith(".")&&o!=="node_modules").length===0}catch(r){const c=p.basename(e);return i(c,"ERROR",`Failed to check if directory is empty: ${r.message}`,{dirPath:e}),!0}}async function Z(e,r,c,o,s,n,m={}){const u=Date.now(),f=F(e,m);try{if(se(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(o)-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 S(e,l,m),i(e,"INFO",`Current version backed up: ${D}`,{projectId:e})}catch(a){throw i(e,"ERROR",`Failed to backup current version: ${a.message}`,{projectId:e}),new P(`Failed to backup current version: ${a.message}`,{projectId:e,originalError:a.message})}if(s&&!isNaN(Number(s))){const a=Number(s);i(e,"INFO",`Stopping old version dev server, PID: ${a}`,{projectId:e});try{await B(c,e,a,{strict:!0}),i(e,"INFO","Old version dev server stopped",{projectId:e})}catch(h){i(e,"WARN",`Failed to stop old version dev server: ${h.message}`,{projectId:e,pid:a})}}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:r}),await _(r,f),i(e,"DEBUG","Zip file extracted successfully",{projectId:e}),i(e,"DEBUG","Check and remove top level folder",{projectId:e}),await re(f),i(e,"DEBUG","Check and remove node_modules folder",{projectId:e}),await j(f),i(e,"DEBUG","Try copying node_modules from template cache",{projectId:e}),await C(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${o}`})}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:o,elapsedMs:Date.now()-u}),{success:!0,message:`Project ${e} uploaded successfully`,projectId:e,codeVersion:o}}catch(w){i(e,"ERROR",`Failed to upload project: ${w.message}`,{projectId:e,elapsedMs:Date.now()-u});try{await U(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:r,originalError:w.message})}}async function S(e,r,c={}){if(!e)throw new y("Project ID cannot be empty",{field:"projectId"});if(r==null)throw new y("codeVersion cannot be empty",{field:"codeVersion"});const o=Number(r);if(!Number.isFinite(o))throw new y("codeVersion must be a number",{field:"codeVersion"});const s=F(e,c);if(!t.existsSync(s))throw new A("Project does not exist",{projectId:e});const n=p.join(E.UPLOAD_PROJECT_DIR,e);t.existsSync(n)||t.mkdirSync(n,{recursive:!0});const m=`${e}-v${o}.zip`,u=p.join(n,m);return i(e,"DEBUG","Start backing up project to zip",{projectId:e,versionNum:o,outZipPath:u}),await I(e,s,u)}async function V(e,r,c){if(!e)throw new y("Project ID cannot be empty",{field:"projectId"});if(!r)throw new y("codeVersion cannot be empty",{field:"codeVersion"});if(!c)throw new y("Please upload a zip file",{field:"zipFile"});const o=p.join(E.UPLOAD_PROJECT_DIR,e);t.existsSync(o)||t.mkdirSync(o,{recursive:!0});const s=c.path,n=p.join(o,`${e}-v${r}.zip`);try{try{t.renameSync(s,n)}catch(m){if(m.code==="EXDEV")t.copyFileSync(s,n),t.unlinkSync(s);else throw m}return i(e,"INFO","File saved successfully",{projectId:e,codeVersion:r,filePath:n}),{success:!0,filePath:n}}catch(m){if(i(e,"ERROR","Failed to move file",{projectId:e,codeVersion:r,error:m.message}),t.existsSync(s))try{t.unlinkSync(s)}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:r,originalError:m.message})}}function ae(e){if(!e)return[];if(Array.isArray(e))return e.map(r=>typeof r=="string"?r.trim():"").filter(Boolean);if(typeof e=="string"){const r=e.trim();if(!r)return[];try{const c=JSON.parse(r);if(Array.isArray(c))return c.map(o=>typeof o=="string"?o.trim():"").filter(Boolean)}catch{}return[r]}return[]}async function X(e){t.existsSync(e)&&await t.promises.rm(e,{recursive:!0,force:!0})}async function q(e,r){try{await t.promises.rename(e,r)}catch(c){if(c.code==="EXDEV"){async function o(s,n){if((await t.promises.lstat(s)).isDirectory()){await t.promises.mkdir(n,{recursive:!0});const u=await t.promises.readdir(s);for(const f of u)await o(p.join(s,f),p.join(n,f))}else await t.promises.copyFile(s,n)}await o(e,r),await t.promises.rm(e,{recursive:!0,force:!0})}else throw c}}async function oe(e,r){const c=p.join(e,r);if(t.existsSync(c)&&(await t.promises.lstat(c)).isDirectory())return c;const o=await t.promises.readdir(e,{withFileTypes:!0});for(const s of o){if(!s.isDirectory())continue;const n=p.join(e,s.name,r);if(t.existsSync(n)&&(await t.promises.lstat(n)).isDirectory())return n}return null}async function ne(e,r,c){let o;try{o=await fetch(e)}catch(u){throw new b(`Failed to download skill zip from url: ${e}`,{url:e,reason:u.message})}if(!o.ok)throw new b(`Failed to download skill zip from url: ${e}`,{url:e,status:o.status,statusText:o.statusText});const s=o.headers.get("content-type")||"";s&&!s.includes("zip")&&!s.includes("octet-stream")&&i(c,"WARN","Downloaded skill url content-type is not typical zip",{url:e,contentType:s});const n=await o.arrayBuffer(),m=Buffer.from(n);await t.promises.writeFile(r,m)}async function H(e,r,c,o={}){const s=String(e),n=Date.now(),m=ae(c),u=[],f=[];if(!e)throw new y("Project ID cannot be empty",{field:"projectId"});if(!r&&m.length===0)throw new y("file or skillUrls cannot both be empty",{field:"file|skillUrls"});if(r){if(!r.path)throw new y("Uploaded file has no valid path",{field:"file.path"});if(p.extname(r.originalname||r.filename||"").toLowerCase()!==".zip")throw new y("Only zip files are supported",{field:"file",originalName:r?.originalname})}const w=F(String(e),o);if(!t.existsSync(w))throw new y("Project does not exist",{field:"projectId"});const{skillsDir:l,agentTypes:d}=await te(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 a=new Set;if(r){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 _(r.path,k);const $=await oe(k,"skills");if(!$)i(s,"WARN","skills directory not found in uploaded zip",{projectId:e,extractRoot:k});else{const R=(await t.promises.readdir($,{withFileTypes:!0})).filter(v=>v.isDirectory()&&!v.name.startsWith("."));for(const v of R){const T=p.join($,v.name),x=p.join(l,v.name);t.existsSync(x)&&await X(x),await q(T,x),a.add(v.name)}}}for(let k=0;k<m.length;k+=1){const $=m[k],O=p.join(D,`project_skill_url_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}.zip`);u.push(O);const R=p.join(D,`project_skill_url_extract_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}`);f.push(R),await t.promises.mkdir(R,{recursive:!0}),await ne($,O,s),await _(O,R);const T=(await t.promises.readdir(R,{withFileTypes:!0})).filter(g=>g.isDirectory()&&!g.name.startsWith(".")),M=T.find(g=>g.name==="skills")?(await t.promises.readdir(p.join(R,"skills"),{withFileTypes:!0})).filter(g=>g.isDirectory()&&!g.name.startsWith(".")).map(g=>({name:g.name,sourcePath:p.join(R,"skills",g.name)})):T.map(g=>({name:g.name,sourcePath:p.join(R,g.name)}));if(M.length===0){i(s,"WARN","No skill directory found after extracting skill url zip",{projectId:e,skillUrl:$,extractRoot:R});continue}for(const g of M){const z=p.join(l,g.name);t.existsSync(z)&&await X(z),await q(g.sourcePath,z),a.add(g.name)}}const h=Array.from(a),N=h.length>0?`Pushed ${h.length} skills: ${h.join(", ")}`:"No valid skill directories found in file or skillUrls";return i(s,"INFO","Push skills to project workspace completed",{projectId:e,updatedSkills:h,skillUrlsCount:m.length,hasFile:!!r,agentTypes:d,elapsedMs:Date.now()-n}),await ie(w),{message:N,projectPath:w,updatedSkills:h}}catch(a){throw i(s,"ERROR","Push skills to project workspace failed",{projectId:e,error:a.message,elapsedMs:Date.now()-n}),a instanceof y||a instanceof b||a instanceof W||a instanceof P?a:new P(`Failed to push skills to project workspace: ${a.message}`,{projectId:e})}finally{for(const a of f)try{t.existsSync(a)&&await t.promises.rm(a,{recursive:!0,force:!0})}catch(h){i(s,"WARN","Failed to clean extracted skill temp dir",{extractRoot:a,error:h.message})}for(const a of u)try{t.existsSync(a)&&await t.promises.unlink(a)}catch(h){i(s,"WARN","Failed to clean downloaded skill zip",{zipPath:a,error:h.message})}try{r?.path&&t.existsSync(r.path)&&await t.promises.unlink(r.path)}catch(a){i(s,"WARN","Failed to clean uploaded skill zip",{tempZipPath:r?.path,error:a.message})}}}async function K(e,r,c,o={}){const s=Date.now();if(!e)throw new y("Project ID cannot be empty",{field:"projectId"});const n=null;try{if(r&&!isNaN(Number(r))){const l=Number(r);i(n,"INFO",`[delete-project] Stopping development server, PID: ${l}`,{projectId:e,pid:l});try{await B(c,e,l,{strict:!0}),i(n,"INFO","[delete-project] Development server stopped",{projectId:e})}catch(d){i(n,"WARN",`[delete-project] Failed to stop development server: ${d.message}`,{projectId:e,pid:l})}}const m=[p.join(E.UPLOAD_PROJECT_DIR,e),F(e,o),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(n,"INFO",`[delete-project] Directory deleted successfully: ${l}`,{projectId:e})}catch(d){f.push({path:l,error:d.message}),i(n,"ERROR",`[delete-project] Directory deleted failed: ${l}`,{projectId:e,error:d.message})}else i(n,"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(n,"WARN","[delete-project] Some directories deleted failed",{projectId:e,failedDirs:f})),i(n,"INFO",`[delete-project] Project deleted successfully: ${e}`,{projectId:e,elapsedMs:Date.now()-s}),w}catch(m){throw i(n,"ERROR",`[delete-project] Failed to delete project: ${m.message}`,{projectId:e,originalError:m.message,elapsedMs:Date.now()-s}),m.isOperational?m:new P(`Failed to delete project: ${m.message}`,{projectId:e,originalError:m.message})}}async function Q(e,r,c,o,s={}){const n=Date.now();if(!e)throw new y("Project ID cannot be empty",{field:"projectId"});if(r==null)throw new y("codeVersion cannot be empty",{field:"codeVersion"});const m=Number(r);if(!Number.isFinite(m))throw new y("codeVersion must be a number",{field:"codeVersion"});const u=F(e,s);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(o)try{const h=JSON.stringify(o,null,2);await t.promises.writeFile(d,h,"utf8"),D=!0,i(e,"INFO",`Configuration file created successfully: ${d}`,{projectId:e,configFilePath:d})}catch(h){throw i(e,"ERROR",`Failed to create configuration file: ${h.message}`,{projectId:e,error:h.message}),new b("Failed to create configuration file",{projectId:e,configFilePath:d,originalError:h.message})}i(e,"DEBUG","Start executing export and packaging",{projectId:e,codeVersion:r});const a=await S(e,r,s);return i(e,"INFO",`Project exported successfully: ${a}`,{projectId:e,zipPath:a,elapsedMs:Date.now()-n}),{success:!0,projectId:e,zipPath:a}}catch(a){throw i(e,"ERROR",`Failed to export project: ${a?.message}`,{projectId:e,elapsedMs:Date.now()-n}),a.isOperational?a:new P("Failed to export project",{projectId:e,originalError:a&&a.message?G(a.message):a&&a.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(a){i(e,"WARN",`Failed to delete temporary configuration file: ${a.message}`,{projectId:e,error:a.message})}}}async function Y(e,r,c={}){const o=Date.now();if(!e)throw new y("Project ID cannot be empty",{field:"projectId"});if(r==null)throw new y("codeVersion cannot be empty",{field:"codeVersion"});try{const s=await S(e,r,c);return i(e,"INFO",`Current version backed up successfully: ${s}`,{projectId:e,zipPath:s,elapsedMs:Date.now()-o}),{success:!0,projectId:e,zipPath:s}}catch(s){throw i(e,"ERROR",`Failed to backup current version: ${s?.message}`,{projectId:e,elapsedMs:Date.now()-o}),s.isOperational?s:new P("Failed to backup current version",{projectId:e,originalError:s&&s.message?G(s.message):s&&s.message})}}export{J as createProject,Z as uploadProject,Y as backupCurrentVersion,Q as exportProject,S as backupProjectOfVersion,U as cleanupProjectDirectory,V as handleFileUpload,H as pushSkillsToWorkspace,K as deleteProject};export default{createProject:J,uploadProject:Z,backupCurrentVersion:Y,exportProject:Q,backupProjectOfVersion:S,cleanupProjectDirectory:U,handleFileUpload:V,pushSkillsToWorkspace:H,deleteProject:K};
|
|
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 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import l from"fs";import i from"path";import{log as y}from"../log/logUtils.js";const d={agents:".agents",claudecode:".claude",opencode:".opencode",codex:".codex"},m=Object.keys(d),g="agents",k=16;function N(t){let e=0;const n=[];return{async acquire(){if(e<t){e+=1;return}await new Promise(s=>{n.push(s)})},release(){const s=n.shift();if(s){s();return}e=Math.max(0,e-1)}}}const j=N(k);async function f(t){try{await l.promises.lstat(t)}catch{return}await l.promises.rm(t,{recursive:!0,force:!0})}async function c(t){l.existsSync(t)||await l.promises.mkdir(t,{recursive:!0})}async function E(t,e){await j.acquire();let n=!1;try{if((await l.promises.lstat(t)).isDirectory()){await c(e);const r=await l.promises.readdir(t);j.release(),n=!0,await Promise.all(r.map(a=>E(i.join(t,a),i.join(e,a))));return}await c(i.dirname(e)),await l.promises.copyFile(t,e)}finally{n||j.release()}}async function w(t,e){if(!l.existsSync(t))return;await c(e);const n=await l.promises.readdir(t,{withFileTypes:!0});await Promise.all(n.map(s=>E(i.join(t,s.name),i.join(e,s.name))))}async function T(t){const e=i.join(t,d[g]),n=i.join(e,"skills"),s=i.join(e,"agents");return await c(e),await c(n),await c(s),{rootDir:e,skillsDir:n,agentsDir:s,agentTypes:m}}async function A(t,e,n){await c(i.dirname(t)),await c(e),await f(t);const s=i.resolve(e);if(process.platform==="win32")try{await l.promises.symlink(s,t,"junction"),y(n||"symlink","DEBUG","Created dir junction (windows)",{linkPath:t,targetPath:s,type:"junction"});return}catch(o){y(n||"symlink","WARN","Windows junction failed, try dir symlink",{linkPath:t,targetPath:s,error:o.message}),await f(t),await l.promises.symlink(s,t,"dir"),y(n||"symlink","DEBUG","Created dir symlink (windows)",{linkPath:t,targetPath:s,type:"dir"});return}let a=i.relative(i.dirname(t),s);if(a=a.split(i.sep).join("/"),!a||i.isAbsolute(a))throw new Error(`Cannot create relative symlink from ${t} to ${e} (got: ${a||"<empty>"})`);await l.promises.symlink(a,t),y(n||"symlink","DEBUG","Created dir symlink",{linkPath:t,targetPath:s,relativeTarget:a,type:"symlink"})}async function D(t,e,n,s){const r=s||"materialize-copy";for(const p of m){const u=i.join(t,d[p]);await f(i.join(u,"skills")),await f(i.join(u,"agents"))}const a=await T(t);await f(a.skillsDir),await f(a.agentsDir),await c(a.skillsDir),await c(a.agentsDir),await w(e,a.skillsDir),await w(n,a.agentsDir);const o=await S(t,r);return y(r,"INFO","Materialized agent store by copy (legacy mode)",{workspaceRoot:t,agentSkillsDir:e,agentAgentsDir:n,syncElapsedMs:o?.elapsedMs}),o}async function M(t,e,n,s){const r=Date.now(),a=s||i.basename(t)||"linkWorkspace";y(a,"INFO","Start linkWorkspaceToAgentStore",{workspaceRoot:t,agentSkillsDir:e,agentAgentsDir:n,platform:process.platform});try{for(const p of m){const u=i.join(t,d[p]);await c(u),await A(i.join(u,"skills"),e,a),await A(i.join(u,"agents"),n,a)}const o=Date.now()-r;return y(a,"INFO","linkWorkspaceToAgentStore completed",{workspaceRoot:t,agentTypes:m,mode:"symlink",elapsedMs:o}),{agentTypes:m,elapsedMs:o,mode:"symlink"}}catch(o){y(a,"WARN","Link to agent store failed, fallback to copy mode",{workspaceRoot:t,platform:process.platform,error:o.message});const p=await D(t,e,n,a),u=Date.now()-r;return y(a,"INFO","linkWorkspaceToAgentStore completed (copy fallback)",{workspaceRoot:t,agentTypes:m,mode:"copy-fallback",elapsedMs:u,syncElapsedMs:p?.elapsedMs}),{agentTypes:m,elapsedMs:u,mode:"copy-fallback",syncElapsedMs:p?.elapsedMs}}}async function C(t,e,n,s){const r=i.join(t,d[e]),a=i.join(r,"skills"),o=i.join(r,"agents");await c(r),await f(a),await f(o),await c(a),await c(o),await Promise.all([w(n.skillsDir,a),w(n.agentsDir,o)]),y(s,"DEBUG","syncAgents target completed",{agentType:e,targetRoot:r})}async function S(t,e){const n=Date.now(),s=e||i.basename(t)||"syncAgents",r=m.filter(p=>p!==g);y(s,"INFO","Start syncAgents",{workspaceRoot:t,targetAgentTypes:r,concurrency:k});const a=await T(t);await Promise.all(r.map(p=>C(t,p,a,s)));const o=Date.now()-n;return y(s,"INFO","syncAgents completed",{workspaceRoot:t,agentTypes:m,elapsedMs:o}),{agentTypes:m,elapsedMs:o}}function O(t){if(!t)return!1;const e=i.join(t,d[g],"skills");try{return l.lstatSync(e).isSymbolicLink()}catch{return!1}}export{d as AGENT_ROOT_MAP,m as ALL_AGENT_TYPES,T as ensurePrimaryAgentDirs,S as syncAgents,M as linkWorkspaceToAgentStore,A as forceDirSymlink,O as isWorkspaceSkillsSymlinked};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import r from"fs";import a from"path";import S from"../../appConfig/index.js";import{log as m}from"../log/logUtils.js";import{ValidationError as k}from"../error/errorHandler.js";const f=".dynamic_add.lock",h=".sync.lock",A=5*60*1e3;function p(){const e=S.COMPUTER_WORKSPACE_DIR;if(!e)throw new k("COMPUTER_WORKSPACE_DIR configuration does not exist",{field:"COMPUTER_WORKSPACE_DIR"});return a.join(e,".agent-store")}function l(e,t){return a.join(p(),String(e),String(t))}async function R(e,t,o){const n=p(),s=l(e,t),i=a.join(s,"skills"),c=a.join(s,"agents");return await r.promises.mkdir(i,{recursive:!0}),await r.promises.mkdir(c,{recursive:!0}),m(o||"agent-store","DEBUG","Agent store dirs ready",{storeRoot:n,agentStorePath:s,skillsDir:i,agentsDir:c}),{storeRoot:n,agentStorePath:s,skillsDir:i,agentsDir:c}}async function v(e,t){await r.promises.mkdir(e,{recursive:!0});const o=a.join(e,h);try{const n=await r.promises.open(o,"wx");return await n.writeFile(`${process.pid}:${Date.now()}`),await n.close(),{acquired:!0,lockPath:o}}catch(n){if(n&&n.code==="EEXIST"){try{const s=await r.promises.stat(o),i=Date.now()-s.mtimeMs;if(i>A){await r.promises.rm(o,{force:!0});const c=await r.promises.open(o,"wx");return await c.writeFile(`${process.pid}:${Date.now()}`),await c.close(),m(t||"agent-store","WARN","Stole stale agent store lock",{lockPath:o,ageMs:i}),{acquired:!0,lockPath:o}}}catch(s){m(t||"agent-store","WARN","Check agent store lock failed",{lockPath:o,error:s.message})}return m(t||"agent-store","INFO","Skip agent store update, lock held by another request",{lockPath:o}),{acquired:!1,lockPath:o}}throw n}}async function C(e,t){if(e)try{await r.promises.rm(e,{force:!0})}catch(o){m(t||"agent-store","WARN","Release agent store lock failed",{lockPath:e,error:o.message})}}function d(e){const t=a.join(e,f);return r.existsSync(t)&&r.statSync(t).isFile()}async function g(e){await r.promises.mkdir(e,{recursive:!0});const t=a.join(e,f);await r.promises.writeFile(t,`${Date.now()}
|
|
2
|
+
`,"utf8")}async function E(e,t,o){const n=new Set((Array.isArray(t)?t:[]).map(u=>String(u||"").trim()).filter(Boolean));if(!r.existsSync(e))return{removed:[],keptDynamic:[]};const s=[],i=[],c=await r.promises.readdir(e,{withFileTypes:!0});for(const u of c){if(!u.isDirectory()||n.has(u.name))continue;const y=a.join(e,u.name);if(d(y)){i.push(u.name);continue}await r.promises.rm(y,{recursive:!0,force:!0}),s.push(u.name)}return m(o||"agent-store","INFO","Prune agent skills completed",{skillsDir:e,keepCount:n.size,removed:s,keptDynamic:i}),{removed:s,keptDynamic:i}}async function _(e,t,o,n={}){const{asDynamic:s=!1}=n,i=a.join(t,o);try{await r.promises.lstat(i),await r.promises.rm(i,{recursive:!0,force:!0})}catch{}if(await r.promises.mkdir(t,{recursive:!0}),await w(e,i),s)await g(i);else{const c=a.join(i,f);r.existsSync(c)&&await r.promises.rm(c,{force:!0})}return i}async function w(e,t){try{await r.promises.rename(e,t)}catch(o){if(o&&o.code==="EXDEV"){await r.promises.cp(e,t,{recursive:!0}),await r.promises.rm(e,{recursive:!0,force:!0});return}throw o}}async function O(e,t){await r.promises.rm(t,{recursive:!0,force:!0}),await r.promises.mkdir(a.dirname(t),{recursive:!0}),e&&r.existsSync(e)?await w(e,t):await r.promises.mkdir(t,{recursive:!0})}function j(e,t){if(!e||!t)return!1;try{return r.statSync(a.join(e,t)).isDirectory()}catch{return!1}}export{f as DYNAMIC_ADD_LOCK,p as getAgentStoreRoot,l as getAgentStorePath,R as ensureAgentStoreDirs,v as tryAcquireAgentStoreLock,C as releaseAgentStoreLock,d as hasDynamicAddLock,g as ensureDynamicAddLock,E as pruneAgentSkills,_ as installSkillDir,O as replaceAgentsDir,w as moveOrCopyDirectory,j as agentSkillExists};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import n from"fs";import l from"path";import{exec as ae}from"child_process";import le from"cross-spawn";import ce from"archiver";import M from"../../appConfig/index.js";import{extractZip as L}from"../common/zipUtils.js";import{ValidationError as u,SystemError as $,FileError as U}from"../error/errorHandler.js";import{log as r}from"../log/logUtils.js";import{createPnpmNpmrc as pe}from"../common/npmrcUtils.js";import{ensurePrimaryAgentDirs as I,syncAgents as V}from"../common/AgentWorkspaceUtils.js";import{writeAgentHookConfigs as de}from"./hookConfigUtils.js";function ee(e){if(!e)return[];if(Array.isArray(e))return 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);if(Array.isArray(i))return i.map(o=>typeof o=="string"?o.trim():"").filter(Boolean)}catch{}return[t]}return[]}async function te(e,t,i){let o;try{o=await fetch(e)}catch(y){throw new U(`Failed to download skill zip from url: ${e}`,{url:e,reason:y.message})}if(!o.ok)throw new U(`Failed to download skill zip from url: ${e}`,{url:e,status:o.status,statusText:o.statusText});const s=o.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 a=await o.arrayBuffer(),d=Buffer.from(a);await n.promises.writeFile(t,d)}async function A(e="computer"){const t=M.COMPUTER_WORKSPACE_DIR;if(!t)throw new u("COMPUTER_WORKSPACE_DIR configuration does not exist",{field:"COMPUTER_WORKSPACE_DIR"});return n.existsSync(t)||(await n.promises.mkdir(t,{recursive:!0}),r(e,"INFO","Created user workspace root directory",{workspaceRoot:t})),t}async function q(e,t){const i=l.join(e,t);if(n.existsSync(i)&&(await n.promises.lstat(i)).isDirectory())return i;const o=await n.promises.readdir(e,{withFileTypes:!0});for(const s of o){if(!s.isDirectory())continue;const a=l.join(e,s.name),d=l.join(a,t);if(n.existsSync(d)&&(await n.promises.lstat(d)).isDirectory())return d}return null}async function Le(e){return q(e,"skills")}const me=".dynamic_add.lock";function fe(e){const t=l.join(e,me);return n.existsSync(t)&&n.statSync(t).isFile()}async function b(e){n.existsSync(e)&&await n.promises.rm(e,{recursive:!0,force:!0})}async function T(e,t){try{await n.promises.rename(e,t)}catch(i){if(i.code==="EXDEV"){async function o(s,a){if((await n.promises.lstat(s)).isDirectory()){await n.promises.mkdir(a,{recursive:!0});const y=await n.promises.readdir(s);for(const c of y)await o(l.join(s,c),l.join(a,c))}else await n.promises.copyFile(s,a)}await o(e,t),await n.promises.rm(e,{recursive:!0,force:!0})}else throw i}}async function ue(e,t,i,o,s,a,d,y){const c=Date.now(),p=`computer:${e}:${t}`,w=ee(o),m=[],g=[],R=new Set;if(!e)throw new u("userId cannot be empty",{field:"userId"});if(!t)throw new u("cId cannot be empty",{field:"cId"});const f=await A(p),D=l.join(f,String(e),String(t),".tmp"),E=l.join(f,String(e),String(t)),{skillsDir:k,agentsDir:_,agentTypes:F}=await I(E);n.existsSync(E)||await n.promises.mkdir(E,{recursive:!0});const N=l.join(D,`preserved_skills_${Date.now()}_${Math.round(Math.random()*1e6)}`);if(n.existsSync(k)){const P=(await n.promises.readdir(k,{withFileTypes:!0})).filter(S=>S.isDirectory()&&fe(l.join(k,S.name)));if(P.length>0){await n.promises.mkdir(N,{recursive:!0});for(const S of P){const x=l.join(k,S.name),z=l.join(N,S.name);await T(x,z)}r(p,"INFO","\u4FDD\u7559\u542B .dynamic_add.lock \u7684 skill",{preserved:P.map(S=>S.name)})}}if(await b(k),await b(_),await n.promises.mkdir(k,{recursive:!0}),await n.promises.mkdir(_,{recursive:!0}),n.existsSync(N)){const h=await n.promises.readdir(N,{withFileTypes:!0});for(const P of h)if(P.isDirectory()){const S=l.join(N,P.name),x=l.join(k,P.name);await T(S,x)}await b(N)}const O=n.existsSync(k),C=n.existsSync(_);if(r(p,"INFO","Deleted old skills and agents directories completed",{userId:e,cId:t,targetSkillsDir:k,targetAgentsDir:_,agentTypes:F,skillsExists:O,agentsExists:C}),await de(E,{mcpServersConfig:s,hooksConfig:d,permissionsConfig:a,hookScripts:y},p),!i&&w.length===0)return await V(E),r(p,"INFO","Created workspace (no uploaded file, no skills and agents)",{userId:e,cId:t,workspaceRoot:f,skillsDir:null,agentsDir:null,agentTypes:F,elapsedMs:Date.now()-c}),{message:"Workspace created (no uploaded file, no skills and agents)",workspaceRoot:f};if(i){if(!i.path)throw new u("Uploaded file has no valid path",{field:"file.path"});if(l.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new u("Only zip files are supported",{field:"file",originalName:i.originalname})}r(p,"DEBUG","Start processing workspace resources",{userId:e,cId:t,workspaceRoot:f,hasUploadedZip:!!i,skillUrlsCount:w.length});try{n.existsSync(D)||await n.promises.mkdir(D,{recursive:!0});const h=[];if(i){const x=l.join(D,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);g.push(x),await n.promises.mkdir(x,{recursive:!0}),r(p,"DEBUG","Start extracting uploaded zip file",{extractRoot:x}),await L(i.path,x),r(p,"DEBUG","Uploaded zip file extracted successfully",{extractRoot:x});const z=await q(x,"skills"),W=await q(x,"agents");if(z){await n.promises.mkdir(k,{recursive:!0});const v=await n.promises.readdir(z,{withFileTypes:!0});for(const B of v){if(!B.isDirectory())continue;const G=l.join(z,B.name),Z=l.join(k,B.name);n.existsSync(Z)&&await b(Z),await T(G,Z),R.add(B.name)}h.push("skills"),r(p,"INFO","skills updated to workspace",{userId:e,cId:t,workspaceRoot:f,targetSkillsDir:k,agentTypes:F})}else r(p,"INFO","skills directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:x});W?(await b(_),await T(W,_),h.push("agents"),r(p,"INFO","agents updated to workspace",{userId:e,cId:t,workspaceRoot:f,targetAgentsDir:_,agentTypes:F})):r(p,"INFO","agents directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:x})}for(let x=0;x<w.length;x+=1){const z=w[x],W=l.join(D,`skill_url_${Date.now()}_${x}_${Math.round(Math.random()*1e6)}.zip`);m.push(W);const v=l.join(D,`skill_url_extract_${Date.now()}_${x}_${Math.round(Math.random()*1e6)}`);g.push(v),await n.promises.mkdir(v,{recursive:!0}),r(p,"INFO","Start download skill zip from url",{userId:e,cId:t,skillUrl:z}),await te(z,W,p),r(p,"INFO","Skill zip downloaded, start extracting",{userId:e,cId:t,skillUrl:z,downloadedZipPath:W}),await L(W,v);const G=(await n.promises.readdir(v,{withFileTypes:!0})).filter(j=>j.isDirectory()&&!j.name.startsWith(".")),Q=G.find(j=>j.name==="skills")?(await n.promises.readdir(l.join(v,"skills"),{withFileTypes:!0})).filter(j=>j.isDirectory()&&!j.name.startsWith(".")).map(j=>({name:j.name,sourcePath:l.join(v,"skills",j.name)})):G.map(j=>({name:j.name,sourcePath:l.join(v,j.name)}));if(Q.length===0){r(p,"WARN","No skill directory found after extracting skill url zip",{userId:e,cId:t,skillUrl:z,extractRoot:v});continue}await n.promises.mkdir(k,{recursive:!0});for(const j of Q){const K=l.join(k,j.name);n.existsSync(K)&&await b(K),await T(j.sourcePath,K),R.add(j.name),r(p,"INFO","Skill from url updated to workspace",{userId:e,cId:t,skillUrl:z,skillName:j.name,destSkillPath:K})}h.includes("skills")||h.push("skills")}h.length===0&&r(p,"WARN","No valid skills or agents directories found",{userId:e,cId:t,hasUploadedZip:!!i,skillUrlsCount:w.length});const P=Array.from(R),S=h.length>0?`Workspace created successfully, ${h.join(" and ")} updated`:"Workspace created successfully (skills and agents directories not found)";return r(p,"INFO",S,{userId:e,cId:t,updatedDirs:h,updatedSkills:P,agentTypes:F,elapsedMs:Date.now()-c}),await V(E),{message:S,workspaceRoot:f,updatedSkills:P}}catch(h){throw r(p,"ERROR","Failed to process workspace resources",{userId:e,cId:t,error:h.message,elapsedMs:Date.now()-c}),h instanceof u||h instanceof U||h instanceof $?h:new $(`Failed to create workspace: ${h.message}`,{userId:e,cId:t})}finally{try{for(const h of g)n.existsSync(h)&&await n.promises.rm(h,{recursive:!0,force:!0})}catch(h){r(p,"WARN","Failed to clean up temporary extracted zip",{error:h.message})}for(const h of m)try{n.existsSync(h)&&await n.promises.unlink(h)}catch(P){r(p,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:h,error:P.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(h){r(p,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:h.message})}}}async function we(e,t,i,o){const s=Date.now(),a=`computer:${e}:${t}`,d=ee(o),y=[],c=[];if(!e)throw new u("userId cannot be empty",{field:"userId"});if(!t)throw new u("cId cannot be empty",{field:"cId"});if(!i&&d.length===0)throw new u("file or skillUrls cannot both be empty",{field:"file|skillUrls"});if(i){if(!i.path)throw new u("Uploaded file has no valid path",{field:"file.path"});if(l.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new u("Only zip files are supported",{field:"file",originalName:i?.originalname})}const p=await A(a),w=l.join(p,String(e),String(t),".tmp"),m=l.join(p,String(e),String(t)),{skillsDir:g,agentTypes:R}=await I(m);try{n.existsSync(m)||await n.promises.mkdir(m,{recursive:!0}),n.existsSync(g)||await n.promises.mkdir(g,{recursive:!0}),n.existsSync(w)||await n.promises.mkdir(w,{recursive:!0});const f=[],D=new Set;if(i){const k=l.join(w,`skill_push_${Date.now()}_${Math.round(Math.random()*1e6)}`);c.push(k),await n.promises.mkdir(k,{recursive:!0}),r(a,"DEBUG","Start extracting skill zip file",{extractRoot:k}),await L(i.path,k),r(a,"DEBUG","Skill zip file extracted successfully",{extractRoot:k});const _=await q(k,"skills");if(!_)r(a,"WARN","skills directory not found in uploaded zip",{userId:e,cId:t,extractRoot:k});else{const N=(await n.promises.readdir(_,{withFileTypes:!0})).filter(O=>O.isDirectory()&&!O.name.startsWith("."));if(N.length===0)r(a,"WARN","skills directory in uploaded zip has no skill subdirectories",{userId:e,cId:t,skillsDir:_});else for(const O of N){const C=l.join(_,O.name),h=l.join(g,O.name);n.existsSync(h)&&await b(h),await T(C,h),D.add(O.name),r(a,"INFO","skill pushed to workspace from uploaded zip",{userId:e,cId:t,skillName:O.name,destSkillPath:h})}}}for(let k=0;k<d.length;k+=1){const _=d[k],F=l.join(w,`skill_push_url_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}.zip`);y.push(F);const N=l.join(w,`skill_push_url_extract_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}`);c.push(N),await n.promises.mkdir(N,{recursive:!0}),r(a,"INFO","Start download skill zip for push from url",{userId:e,cId:t,skillUrl:_}),await te(_,F,a),await L(F,N);const C=(await n.promises.readdir(N,{withFileTypes:!0})).filter(S=>S.isDirectory()&&!S.name.startsWith(".")),P=C.find(S=>S.name==="skills")?(await n.promises.readdir(l.join(N,"skills"),{withFileTypes:!0})).filter(S=>S.isDirectory()&&!S.name.startsWith(".")).map(S=>({name:S.name,sourcePath:l.join(N,"skills",S.name)})):C.map(S=>({name:S.name,sourcePath:l.join(N,S.name)}));if(P.length===0){r(a,"WARN","No skill directory found after extracting push skill url zip",{userId:e,cId:t,skillUrl:_,extractRoot:N});continue}for(const S of P){const x=l.join(g,S.name);n.existsSync(x)&&await b(x),await T(S.sourcePath,x),D.add(S.name),r(a,"INFO","skill pushed to workspace from url zip",{userId:e,cId:t,skillUrl:_,skillName:S.name,destSkillPath:x})}}for(const k of D)f.push(k);const E=f.length>0?`Pushed ${f.length} skills: ${f.join(", ")}`:"No valid skill directories found in file or skillUrls";return r(a,"INFO",E,{userId:e,cId:t,updatedSkills:f,agentTypes:R,elapsedMs:Date.now()-s}),await V(m),{message:E,workspaceRoot:p,updatedSkills:f}}catch(f){throw r(a,"ERROR","Failed to push skill to workspace",{userId:e,cId:t,error:f.message,elapsedMs:Date.now()-s}),f instanceof u||f instanceof U||f instanceof $?f:new $(`Failed to push skill to workspace: ${f.message}`,{userId:e,cId:t})}finally{try{for(const f of c)n.existsSync(f)&&await n.promises.rm(f,{recursive:!0,force:!0})}catch(f){r(a,"WARN","Failed to clean up temporary extracted zip",{error:f.message})}for(const f of y)try{n.existsSync(f)&&await n.promises.unlink(f)}catch(D){r(a,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:f,error:D.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(f){r(a,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:f.message})}}}async function ge(e,t,i,o){const s=Date.now(),a=`computer:${e}:${t}`;if(!e)throw new u("userId cannot be empty",{field:"userId"});if(!t)throw new u("cId cannot be empty",{field:"cId"});if(!i||!i.path)throw new u("file is required",{field:"file"});if(l.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new u("Only zip files are supported",{field:"file",originalName:i.originalname});const y=await A(a),c=l.join(y,String(e),String(t)),p=l.join(c,".tmp"),w=l.join(p,`template_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{n.existsSync(c)||await n.promises.mkdir(c,{recursive:!0}),n.existsSync(p)||await n.promises.mkdir(p,{recursive:!0}),await n.promises.mkdir(w,{recursive:!0}),r(a,"DEBUG","Start extracting template zip file",{extractRoot:w}),await L(i.path,w),r(a,"DEBUG","Template zip file extracted successfully",{extractRoot:w});const m=await n.promises.readdir(w,{withFileTypes:!0});for(const g of m){const R=l.join(w,g.name),f=l.join(c,g.name);if(g.isDirectory())await T(R,f);else{const D=l.dirname(f);n.existsSync(D)||await n.promises.mkdir(D,{recursive:!0}),await n.promises.copyFile(R,f)}}if(r(a,"INFO","Template files extracted to workspace",{userId:e,cId:t,targetDir:c,fileCount:m.length}),M.GIT_ENABLED&&(o==="true"||o===!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(a,"INFO","Git init and initial commit done",{userId:e,cId:t})}return r(a,"INFO","Init project template completed",{userId:e,cId:t,targetDir:c,elapsedMs:Date.now()-s}),{message:"Project template initialized successfully",workspaceRoot:c}}catch(m){throw r(a,"ERROR","Failed to init project template",{userId:e,cId:t,error:m.message,elapsedMs:Date.now()-s}),m instanceof u||m instanceof U||m instanceof $?m:new $(`Failed to init project template: ${m.message}`,{userId:e,cId:t})}finally{try{n.existsSync(w)&&await n.promises.rm(w,{recursive:!0,force:!0})}catch(m){r(a,"WARN","Failed to clean up temporary extracted zip",{error:m.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(m){r(a,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:m.message})}}}async function he(e,t,i){if(!e)throw new u("userId cannot be empty",{field:"userId"});if(!t)throw new u("cId cannot be empty",{field:"cId"});if(!i||typeof i!="string"||!i.trim())throw new u("command cannot be empty",{field:"command"});const o=await A("computer"),s=l.join(o,String(e),String(t));if(!n.existsSync(s))throw new u("workspace directory does not exist",{field:"workDir",workDir:s});const a=`computer:${e}:${t}`;r(a,"INFO","Execute command in workspace",{userId:e,cId:t,workDir:s,command:i});const d=10*60*1e3,y={cwd:s,timeout:d,maxBuffer:50*1024*1024};return M.BASH_PATH&&(y.shell=M.BASH_PATH),new Promise((c,p)=>{ae(i,y,(w,m,g)=>{const R=w?w.killed?-1:w.code||1:0,f=w&&w.killed?(g||"")+`
|
|
2
|
-
Command timed out after ${
|
|
3
|
-
Failed to spawn ${e}: ${
|
|
4
|
-
Process killed by signal ${
|
|
5
|
-
`);for(const
|
|
6
|
-
`,"utf8"),r(t,"INFO","Removed conflicting pnpm built dependency settings from package.json",{projectDir:e,removedKeys:
|
|
7
|
-
`),
|
|
8
|
-
`),"utf8"),r(t,"INFO","Removed conflicting pnpm built dependency settings from pnpm-workspace.yaml",{projectDir:e,removedKeys:
|
|
9
|
-
`).filter(
|
|
10
|
-
`);return i!==t&&await n.promises.writeFile(e,i,"utf8"),i}async function
|
|
1
|
+
import n from"fs";import o from"path";import{exec as Pe}from"child_process";import je from"cross-spawn";import Ee from"archiver";import X from"../../appConfig/index.js";import{extractZip as J}from"../common/zipUtils.js";import{ValidationError as m,SystemError as U,FileError as Q}from"../error/errorHandler.js";import{log as r}from"../log/logUtils.js";import{createPnpmNpmrc as Fe}from"../common/npmrcUtils.js";import{ensurePrimaryAgentDirs as fe,syncAgents as ce,linkWorkspaceToAgentStore as we,isWorkspaceSkillsSymlinked as Ae}from"../common/AgentWorkspaceUtils.js";import{ensureAgentStoreDirs as he,tryAcquireAgentStoreLock as pe,releaseAgentStoreLock as ge,pruneAgentSkills as Oe,installSkillDir as te,replaceAgentsDir as $e,agentSkillExists as ze}from"../common/agentStoreUtils.js";import{writeAgentHookConfigs as ke}from"./hookConfigUtils.js";function se(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 se(i)}catch{}return[t]}return[]}function ye(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 ye(JSON.parse(t))}catch{return[{name:null,url:t}]}}return[]}function ve(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 l=String(a||"").trim(),c=typeof s=="string"?s.trim():"";l&&c&&(i[l]=c)}return i}function Se(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 ae(e,t,i){let a;try{a=await fetch(e)}catch(d){throw new Q(`Failed to download skill zip from url: ${e}`,{url:e,reason:d.message})}if(!a.ok)throw new Q(`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 l=await a.arrayBuffer(),c=Buffer.from(l);await n.promises.writeFile(t,c)}async function q(e="computer"){const t=X.COMPUTER_WORKSPACE_DIR;if(!t)throw new m("COMPUTER_WORKSPACE_DIR configuration does not exist",{field:"COMPUTER_WORKSPACE_DIR"});return n.existsSync(t)||(await n.promises.mkdir(t,{recursive:!0}),r(e,"INFO","Created user workspace root directory",{workspaceRoot:t})),t}async function I(e,t){const i=o.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 l=o.join(e,s.name),c=o.join(l,t);if(n.existsSync(c)&&(await n.promises.lstat(c)).isDirectory())return c}return null}async function mt(e){return I(e,"skills")}const Me=".dynamic_add.lock";function We(e){const t=o.join(e,Me);return n.existsSync(t)&&n.statSync(t).isFile()}async function H(e){n.existsSync(e)&&await n.promises.rm(e,{recursive:!0,force:!0})}async function V(e,t){try{await n.promises.rename(e,t)}catch(i){if(i.code==="EXDEV"){async function a(s,l){if((await n.promises.lstat(s)).isDirectory()){await n.promises.mkdir(l,{recursive:!0});const d=await n.promises.readdir(s);for(const p of d)await a(o.join(s,p),o.join(l,p))}else await n.promises.copyFile(s,l)}await a(e,t),await n.promises.rm(e,{recursive:!0,force:!0})}else throw i}}async function de(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(o.join(e,"skills"),{withFileTypes:!0})).filter(l=>l.isDirectory()&&!l.name.startsWith(".")).map(l=>({name:l.name,sourcePath:o.join(e,"skills",l.name)})):i.map(s=>({name:s.name,sourcePath:o.join(e,s.name)}))}const ie=16;async function ne(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 l=s++;if(l>=e.length)return;await i(e[l],l)}}))}async function be(e){const{userId:t,cId:i,agentId:a,file:s,skillUrls:l,skillUrlMap:c,skillNames:d,updateSkillNames:p,mcpServersConfig:D,permissionsConfig:x,hooksConfig:f,hookScripts:g}=e,u=Date.now(),h=`computer:${t}:${i}`,W=ve(c),_=ye(l),A=Se(d),k=p!=null,z=new Set(Se(p)),b=[],y=[],R=new Set,O=new Set;let E=null,v=!1;if(!t)throw new m("userId cannot be empty",{field:"userId"});if(!i)throw new m("cId cannot be empty",{field:"cId"});if(!a)throw new m("agentId cannot be empty",{field:"agentId"});const C=await q(h),S=o.join(C,String(t),String(i)),j=o.join(S,".tmp");await n.promises.mkdir(S,{recursive:!0}),await n.promises.mkdir(j,{recursive:!0});const{agentStorePath:N,skillsDir:T,agentsDir:ee}=await he(t,a,h),M=F=>!F||!k||z.has(F)?!0:!ze(T,F),G=async(F,P,B)=>{const K=o.join(j,`skill_url_${Date.now()}_${B}_${Math.round(Math.random()*1e6)}.zip`);b.push(K);const Z=o.join(j,`skill_url_extract_${Date.now()}_${B}_${Math.round(Math.random()*1e6)}`);y.push(Z),await n.promises.mkdir(Z,{recursive:!0}),r(h,"INFO","Start download skill zip from url",{userId:t,cId:i,agentId:a,skillName:P,skillUrl:F}),await ae(F,K,h),await J(K,Z);const L=await de(Z);if(L.length===0){r(h,"WARN","No skill dir found in downloaded zip",{skillUrl:F,destName:P});return}let w=P?L.find($=>$.name===P):null;if(!w&&L.length===1&&(w=L[0]),P&&w){if(!M(P)){O.add(P);return}await te(w.sourcePath,T,P,{asDynamic:!1}),R.add(P),r(h,"INFO","Skill from url updated to agent store",{userId:t,cId:i,agentId:a,skillUrl:F,skillName:P,zipDirName:w.name});return}for(const $ of L){if(!M($.name)){O.add($.name);continue}await te($.sourcePath,T,$.name,{asDynamic:!1}),R.add($.name),r(h,"INFO","Skill from url updated to agent store",{userId:t,cId:i,agentId:a,skillUrl:F,skillName:$.name})}};await ke(S,{mcpServersConfig:D,hooksConfig:f,permissionsConfig:x,hookScripts:g},h);try{const F=await pe(N,h);if(!F.acquired)v=!0;else{E=F.lockPath;const B=Date.now();let K=null;if(s){if(!s.path)throw new m("Uploaded file has no valid path",{field:"file.path"});if(o.extname(s.originalname||s.filename||"").toLowerCase()!==".zip")throw new m("Only zip files are supported",{field:"file",originalName:s.originalname});const w=o.join(j,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);y.push(w),await n.promises.mkdir(w,{recursive:!0}),await J(s.path,w);const $=await I(w,"skills");if($){const le=(await n.promises.readdir($,{withFileTypes:!0})).filter(Y=>Y.isDirectory());await ne(le,ie,async Y=>{if(!M(Y.name)){O.add(Y.name);return}await te(o.join($,Y.name),T,Y.name,{asDynamic:!1}),R.add(Y.name)})}K=await I(w,"agents")}const Z=Object.entries(W);if(Z.length>0){const L=[];for(const[w,$]of Z){if(!M(w)){O.add(w),r(h,"INFO","Skip download skill zip (already in agent store)",{userId:t,cId:i,agentId:a,skillName:w,skillUrl:$});continue}L.push({skillName:w,skillUrl:$})}await ne(L,ie,async(w,$)=>{await G(w.skillUrl,w.skillName,$)})}else{const L=[];for(let w=0;w<_.length;w++){const{name:$,url:le}=_[w];if($&&!M($)){O.add($);continue}L.push({skillUrl:le,hintedName:$,index:w})}await ne(L,ie,async w=>{await G(w.skillUrl,w.hintedName,w.index)})}await $e(K,ee),await Oe(T,A,h),r(h,"INFO","Agent store resources prepared",{userId:t,cId:i,agentId:a,selectiveInstall:k,updatedSkills:Array.from(R),skippedSkills:Array.from(O),updateSkillNames:Array.from(z),keepSkillNames:A,skillUrlMapCount:Z.length,prepareElapsedMs:Date.now()-B})}const P=await we(S,T,ee,h);return r(h,"INFO","Workspace created successfully (agent store)",{userId:t,cId:i,agentId:a,agentStorePath:N,updatedSkills:Array.from(R),skippedSkills:Array.from(O),skippedStoreUpdate:v,linkElapsedMs:P?.elapsedMs,elapsedMs:Date.now()-u}),{message:v?"Workspace linked to agent store (store update skipped, lock held)":"Workspace created successfully, linked to agent store",workspaceRoot:C,agentStorePath:N,updatedSkills:Array.from(R),skippedSkills:Array.from(O),skippedStoreUpdate:v}}catch(F){throw r(h,"ERROR","Failed to create workspace with agent store",{userId:t,cId:i,agentId:a,error:F.message,elapsedMs:Date.now()-u}),F instanceof m||F instanceof Q||F instanceof U?F:new U(`Failed to create workspace: ${F.message}`,{userId:t,cId:i,agentId:a})}finally{await ge(E,h);const F=Date.now();for(const P of y)try{n.existsSync(P)&&await n.promises.rm(P,{recursive:!0,force:!0})}catch(B){r(h,"WARN","Failed to clean up temporary extracted zip",{error:B.message})}for(const P of b)try{n.existsSync(P)&&await n.promises.unlink(P)}catch(B){r(h,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:P,error:B.message})}try{s&&s.path&&n.existsSync(s.path)&&await n.promises.unlink(s.path)}catch(P){r(h,"WARN","Failed to clean up uploaded zip file",{tempZipPath:s?.path,error:P.message})}r(h,"INFO","Workspace temp cleanup completed",{userId:t,cId:i,agentId:a,elapsedMs:Date.now()-F,totalElapsedMs:Date.now()-u})}}async function Ce(e,t,i,a,s,l,c,d,p,D,x,f){if(p!=null&&String(p).trim()!=="")return be({userId:e,cId:t,agentId:p,file:i,skillUrls:a,skillUrlMap:f,skillNames:D,updateSkillNames:x,mcpServersConfig:s,permissionsConfig:l,hooksConfig:c,hookScripts:d});const g=Date.now(),u=`computer:${e}:${t}`,h=se(a),W=[],_=[],A=new Set;if(!e)throw new m("userId cannot be empty",{field:"userId"});if(!t)throw new m("cId cannot be empty",{field:"cId"});const k=await q(u),z=o.join(k,String(e),String(t),".tmp"),b=o.join(k,String(e),String(t)),{skillsDir:y,agentsDir:R,agentTypes:O}=await fe(b);n.existsSync(b)||await n.promises.mkdir(b,{recursive:!0});const E=o.join(z,`preserved_skills_${Date.now()}_${Math.round(Math.random()*1e6)}`);if(n.existsSync(y)){const j=(await n.promises.readdir(y,{withFileTypes:!0})).filter(N=>N.isDirectory()&&We(o.join(y,N.name)));if(j.length>0){await n.promises.mkdir(E,{recursive:!0});for(const N of j){const T=o.join(y,N.name),ee=o.join(E,N.name);await V(T,ee)}r(u,"INFO","\u4FDD\u7559\u542B .dynamic_add.lock \u7684 skill",{preserved:j.map(N=>N.name)})}}if(await H(y),await H(R),await n.promises.mkdir(y,{recursive:!0}),await n.promises.mkdir(R,{recursive:!0}),n.existsSync(E)){const S=await n.promises.readdir(E,{withFileTypes:!0});for(const j of S)if(j.isDirectory()){const N=o.join(E,j.name),T=o.join(y,j.name);await V(N,T)}await H(E)}const v=n.existsSync(y),C=n.existsSync(R);if(r(u,"INFO","Deleted old skills and agents directories completed",{userId:e,cId:t,targetSkillsDir:y,targetAgentsDir:R,agentTypes:O,skillsExists:v,agentsExists:C}),await ke(b,{mcpServersConfig:s,hooksConfig:c,permissionsConfig:l,hookScripts:d},u),!i&&h.length===0){const S=await ce(b,u);return r(u,"INFO","Created workspace (no uploaded file, no skills and agents)",{userId:e,cId:t,workspaceRoot:k,skillsDir:null,agentsDir:null,agentTypes:O,syncAgentsElapsedMs:S?.elapsedMs,elapsedMs:Date.now()-g}),{message:"Workspace created (no uploaded file, no skills and agents)",workspaceRoot:k}}if(i){if(!i.path)throw new m("Uploaded file has no valid path",{field:"file.path"});if(o.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new m("Only zip files are supported",{field:"file",originalName:i.originalname})}r(u,"DEBUG","Start processing workspace resources",{userId:e,cId:t,workspaceRoot:k,hasUploadedZip:!!i,skillUrlsCount:h.length});try{n.existsSync(z)||await n.promises.mkdir(z,{recursive:!0});const S=[];if(i){const M=o.join(z,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);_.push(M),await n.promises.mkdir(M,{recursive:!0}),r(u,"DEBUG","Start extracting uploaded zip file",{extractRoot:M}),await J(i.path,M),r(u,"DEBUG","Uploaded zip file extracted successfully",{extractRoot:M});const G=await I(M,"skills"),F=await I(M,"agents");if(G){await n.promises.mkdir(y,{recursive:!0});const P=await n.promises.readdir(G,{withFileTypes:!0});for(const B of P){if(!B.isDirectory())continue;const K=o.join(G,B.name),Z=o.join(y,B.name);n.existsSync(Z)&&await H(Z),await V(K,Z),A.add(B.name)}S.push("skills"),r(u,"INFO","skills updated to workspace",{userId:e,cId:t,workspaceRoot:k,targetSkillsDir:y,agentTypes:O})}else r(u,"INFO","skills directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:M});F?(await H(R),await V(F,R),S.push("agents"),r(u,"INFO","agents updated to workspace",{userId:e,cId:t,workspaceRoot:k,targetAgentsDir:R,agentTypes:O})):r(u,"INFO","agents directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:M})}for(let M=0;M<h.length;M+=1){const G=h[M],F=o.join(z,`skill_url_${Date.now()}_${M}_${Math.round(Math.random()*1e6)}.zip`);W.push(F);const P=o.join(z,`skill_url_extract_${Date.now()}_${M}_${Math.round(Math.random()*1e6)}`);_.push(P),await n.promises.mkdir(P,{recursive:!0}),r(u,"INFO","Start download skill zip from url",{userId:e,cId:t,skillUrl:G}),await ae(G,F,u),r(u,"INFO","Skill zip downloaded, start extracting",{userId:e,cId:t,skillUrl:G,downloadedZipPath:F}),await J(F,P);const K=(await n.promises.readdir(P,{withFileTypes:!0})).filter(w=>w.isDirectory()&&!w.name.startsWith(".")),L=K.find(w=>w.name==="skills")?(await n.promises.readdir(o.join(P,"skills"),{withFileTypes:!0})).filter(w=>w.isDirectory()&&!w.name.startsWith(".")).map(w=>({name:w.name,sourcePath:o.join(P,"skills",w.name)})):K.map(w=>({name:w.name,sourcePath:o.join(P,w.name)}));if(L.length===0){r(u,"WARN","No skill directory found after extracting skill url zip",{userId:e,cId:t,skillUrl:G,extractRoot:P});continue}await n.promises.mkdir(y,{recursive:!0});for(const w of L){const $=o.join(y,w.name);n.existsSync($)&&await H($),await V(w.sourcePath,$),A.add(w.name),r(u,"INFO","Skill from url updated to workspace",{userId:e,cId:t,skillUrl:G,skillName:w.name,destSkillPath:$})}S.includes("skills")||S.push("skills")}S.length===0&&r(u,"WARN","No valid skills or agents directories found",{userId:e,cId:t,hasUploadedZip:!!i,skillUrlsCount:h.length});const j=Array.from(A),N=S.length>0?`Workspace created successfully, ${S.join(" and ")} updated`:"Workspace created successfully (skills and agents directories not found)",T=Date.now()-g;r(u,"INFO","Workspace resources prepared",{userId:e,cId:t,updatedDirs:S,updatedSkills:j,agentTypes:O,prepareElapsedMs:T});const ee=await ce(b,u);return r(u,"INFO",N,{userId:e,cId:t,updatedDirs:S,updatedSkills:j,agentTypes:O,prepareElapsedMs:T,syncAgentsElapsedMs:ee?.elapsedMs,elapsedMs:Date.now()-g}),{message:N,workspaceRoot:k,updatedSkills:j}}catch(S){throw r(u,"ERROR","Failed to process workspace resources",{userId:e,cId:t,error:S.message,elapsedMs:Date.now()-g}),S instanceof m||S instanceof Q||S instanceof U?S:new U(`Failed to create workspace: ${S.message}`,{userId:e,cId:t})}finally{const S=Date.now();try{for(const j of _)n.existsSync(j)&&await n.promises.rm(j,{recursive:!0,force:!0})}catch(j){r(u,"WARN","Failed to clean up temporary extracted zip",{error:j.message})}for(const j of W)try{n.existsSync(j)&&await n.promises.unlink(j)}catch(N){r(u,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:j,error:N.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(j){r(u,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:j.message})}r(u,"INFO","Workspace temp cleanup completed",{userId:e,cId:t,elapsedMs:Date.now()-S,totalElapsedMs:Date.now()-g})}}async function Te(e,t,i,a,s){const l=Date.now(),c=`computer:${e}:${t}`,d=se(s),p=[],D=[],x=new Set;let f=null;if(!e)throw new m("userId cannot be empty",{field:"userId"});if(!t)throw new m("cId cannot be empty",{field:"cId"});if(!i)throw new m("agentId cannot be empty",{field:"agentId"});if(!a&&d.length===0)throw new m("file or skillUrls cannot both be empty",{field:"file|skillUrls"});const g=await q(c),u=o.join(g,String(e),String(t)),h=o.join(u,".tmp");await n.promises.mkdir(u,{recursive:!0}),await n.promises.mkdir(h,{recursive:!0});const{agentStorePath:W,skillsDir:_,agentsDir:A}=await he(e,i,c);try{let k=await pe(W,c);if(!k.acquired)for(let R=1;R<=5&&!k.acquired;R++)await new Promise(O=>setTimeout(O,200*R)),k=await pe(W,c);if(!k.acquired)throw new U("Agent store is busy, please retry",{userId:e,cId:t,agentId:i});if(f=k.lockPath,a){if(!a.path)throw new m("Uploaded file has no valid path",{field:"file.path"});const R=o.join(h,`push_skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);D.push(R),await n.promises.mkdir(R,{recursive:!0}),await J(a.path,R);const O=await de(R),E=await I(R,"skills"),v=E?(await n.promises.readdir(E,{withFileTypes:!0})).filter(C=>C.isDirectory()).map(C=>({name:C.name,sourcePath:o.join(E,C.name)})):O;await ne(v,ie,async C=>{await te(C.sourcePath,_,C.name,{asDynamic:!0}),x.add(C.name)})}await ne(d,ie,async(R,O)=>{const E=o.join(h,`push_skill_url_${Date.now()}_${O}_${Math.round(Math.random()*1e6)}.zip`);p.push(E);const v=o.join(h,`push_skill_url_extract_${Date.now()}_${O}_${Math.round(Math.random()*1e6)}`);D.push(v),await n.promises.mkdir(v,{recursive:!0}),await ae(R,E,c),await J(E,v);const C=await de(v);for(const S of C)await te(S.sourcePath,_,S.name,{asDynamic:!0}),x.add(S.name)});const z=await we(u,_,A,c),b=Array.from(x),y=b.length>0?`Pushed ${b.length} skills to agent store: ${b.join(", ")}`:"No valid skill directories found in file or skillUrls";return r(c,"INFO",y,{userId:e,cId:t,agentId:i,updatedSkills:b,linkElapsedMs:z?.elapsedMs,elapsedMs:Date.now()-l}),{message:y,workspaceRoot:g,agentStorePath:W,updatedSkills:b}}catch(k){throw r(c,"ERROR","Failed to push skills to agent store",{userId:e,cId:t,agentId:i,error:k.message,elapsedMs:Date.now()-l}),k instanceof m||k instanceof Q||k instanceof U?k:new U(`Failed to push skill to workspace: ${k.message}`,{userId:e,cId:t,agentId:i})}finally{await ge(f,c);for(const k of D)try{n.existsSync(k)&&await n.promises.rm(k,{recursive:!0,force:!0})}catch(z){r(c,"WARN","Failed to clean up temporary extracted zip",{error:z.message})}for(const k of p)try{n.existsSync(k)&&await n.promises.unlink(k)}catch(z){r(c,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:k,error:z.message})}try{a&&a.path&&n.existsSync(a.path)&&await n.promises.unlink(a.path)}catch(k){r(c,"WARN","Failed to clean up uploaded zip file",{tempZipPath:a?.path,error:k.message})}}}async function Le(e,t,i,a,s){if(s!=null&&String(s).trim()!==""){const _=X.COMPUTER_WORKSPACE_DIR,A=_?o.join(_,String(e),String(t)):null;if(Ae(A))return Te(e,t,s,i,a);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 c=Date.now(),d=`computer:${e}:${t}`,p=se(a),D=[],x=[];if(!e)throw new m("userId cannot be empty",{field:"userId"});if(!t)throw new m("cId cannot be empty",{field:"cId"});if(!i&&p.length===0)throw new m("file or skillUrls cannot both be empty",{field:"file|skillUrls"});if(i){if(!i.path)throw new m("Uploaded file has no valid path",{field:"file.path"});if(o.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new m("Only zip files are supported",{field:"file",originalName:i?.originalname})}const f=await q(d),g=o.join(f,String(e),String(t),".tmp"),u=o.join(f,String(e),String(t)),{skillsDir:h,agentTypes:W}=await fe(u);try{n.existsSync(u)||await n.promises.mkdir(u,{recursive:!0}),n.existsSync(h)||await n.promises.mkdir(h,{recursive:!0}),n.existsSync(g)||await n.promises.mkdir(g,{recursive:!0});const _=[],A=new Set;if(i){const y=o.join(g,`skill_push_${Date.now()}_${Math.round(Math.random()*1e6)}`);x.push(y),await n.promises.mkdir(y,{recursive:!0}),r(d,"DEBUG","Start extracting skill zip file",{extractRoot:y}),await J(i.path,y),r(d,"DEBUG","Skill zip file extracted successfully",{extractRoot:y});const R=await I(y,"skills");if(!R)r(d,"WARN","skills directory not found in uploaded zip",{userId:e,cId:t,extractRoot:y});else{const E=(await n.promises.readdir(R,{withFileTypes:!0})).filter(v=>v.isDirectory()&&!v.name.startsWith("."));if(E.length===0)r(d,"WARN","skills directory in uploaded zip has no skill subdirectories",{userId:e,cId:t,skillsDir:R});else for(const v of E){const C=o.join(R,v.name),S=o.join(h,v.name);n.existsSync(S)&&await H(S),await V(C,S),A.add(v.name),r(d,"INFO","skill pushed to workspace from uploaded zip",{userId:e,cId:t,skillName:v.name,destSkillPath:S})}}}for(let y=0;y<p.length;y+=1){const R=p[y],O=o.join(g,`skill_push_url_${Date.now()}_${y}_${Math.round(Math.random()*1e6)}.zip`);D.push(O);const E=o.join(g,`skill_push_url_extract_${Date.now()}_${y}_${Math.round(Math.random()*1e6)}`);x.push(E),await n.promises.mkdir(E,{recursive:!0}),r(d,"INFO","Start download skill zip for push from url",{userId:e,cId:t,skillUrl:R}),await ae(R,O,d),await J(O,E);const C=(await n.promises.readdir(E,{withFileTypes:!0})).filter(N=>N.isDirectory()&&!N.name.startsWith(".")),j=C.find(N=>N.name==="skills")?(await n.promises.readdir(o.join(E,"skills"),{withFileTypes:!0})).filter(N=>N.isDirectory()&&!N.name.startsWith(".")).map(N=>({name:N.name,sourcePath:o.join(E,"skills",N.name)})):C.map(N=>({name:N.name,sourcePath:o.join(E,N.name)}));if(j.length===0){r(d,"WARN","No skill directory found after extracting push skill url zip",{userId:e,cId:t,skillUrl:R,extractRoot:E});continue}for(const N of j){const T=o.join(h,N.name);n.existsSync(T)&&await H(T),await V(N.sourcePath,T),A.add(N.name),r(d,"INFO","skill pushed to workspace from url zip",{userId:e,cId:t,skillUrl:R,skillName:N.name,destSkillPath:T})}}for(const y of A)_.push(y);const k=_.length>0?`Pushed ${_.length} skills: ${_.join(", ")}`:"No valid skill directories found in file or skillUrls",z=Date.now()-c;r(d,"INFO","Push skills resources prepared",{userId:e,cId:t,updatedSkills:_,agentTypes:W,prepareElapsedMs:z});const b=await ce(u,d);return r(d,"INFO",k,{userId:e,cId:t,updatedSkills:_,agentTypes:W,prepareElapsedMs:z,syncAgentsElapsedMs:b?.elapsedMs,elapsedMs:Date.now()-c}),{message:k,workspaceRoot:f,updatedSkills:_}}catch(_){throw r(d,"ERROR","Failed to push skill to workspace",{userId:e,cId:t,error:_.message,elapsedMs:Date.now()-c}),_ instanceof m||_ instanceof Q||_ instanceof U?_:new U(`Failed to push skill to workspace: ${_.message}`,{userId:e,cId:t})}finally{const _=Date.now();try{for(const A of x)n.existsSync(A)&&await n.promises.rm(A,{recursive:!0,force:!0})}catch(A){r(d,"WARN","Failed to clean up temporary extracted zip",{error:A.message})}for(const A of D)try{n.existsSync(A)&&await n.promises.unlink(A)}catch(k){r(d,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:A,error:k.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(A){r(d,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:A.message})}r(d,"INFO","Push skills temp cleanup completed",{userId:e,cId:t,elapsedMs:Date.now()-_,totalElapsedMs:Date.now()-c})}}async function Be(e,t,i,a){const s=Date.now(),l=`computer:${e}:${t}`;if(!e)throw new m("userId cannot be empty",{field:"userId"});if(!t)throw new m("cId cannot be empty",{field:"cId"});if(!i||!i.path)throw new m("file is required",{field:"file"});if(o.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new m("Only zip files are supported",{field:"file",originalName:i.originalname});const d=await q(l),p=o.join(d,String(e),String(t)),D=o.join(p,".tmp"),x=o.join(D,`template_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{n.existsSync(p)||await n.promises.mkdir(p,{recursive:!0}),n.existsSync(D)||await n.promises.mkdir(D,{recursive:!0}),await n.promises.mkdir(x,{recursive:!0}),r(l,"DEBUG","Start extracting template zip file",{extractRoot:x}),await J(i.path,x),r(l,"DEBUG","Template zip file extracted successfully",{extractRoot:x});const f=await n.promises.readdir(x,{withFileTypes:!0});for(const g of f){const u=o.join(x,g.name),h=o.join(p,g.name);if(g.isDirectory())await V(u,h);else{const W=o.dirname(h);n.existsSync(W)||await n.promises.mkdir(W,{recursive:!0}),await n.promises.copyFile(u,h)}}if(r(l,"INFO","Template files extracted to workspace",{userId:e,cId:t,targetDir:p,fileCount:f.length}),X.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(l,"INFO","Git init and initial commit done",{userId:e,cId:t})}return r(l,"INFO","Init project template completed",{userId:e,cId:t,targetDir:p,elapsedMs:Date.now()-s}),{message:"Project template initialized successfully",workspaceRoot:p}}catch(f){throw r(l,"ERROR","Failed to init project template",{userId:e,cId:t,error:f.message,elapsedMs:Date.now()-s}),f instanceof m||f instanceof Q||f instanceof U?f:new U(`Failed to init project template: ${f.message}`,{userId:e,cId:t})}finally{try{n.existsSync(x)&&await n.promises.rm(x,{recursive:!0,force:!0})}catch(f){r(l,"WARN","Failed to clean up temporary extracted zip",{error:f.message})}try{i&&i.path&&n.existsSync(i.path)&&await n.promises.unlink(i.path)}catch(f){r(l,"WARN","Failed to clean up uploaded zip file",{tempZipPath:i?.path,error:f.message})}}}async function Ue(e,t,i){if(!e)throw new m("userId cannot be empty",{field:"userId"});if(!t)throw new m("cId cannot be empty",{field:"cId"});if(!i||typeof i!="string"||!i.trim())throw new m("command cannot be empty",{field:"command"});const a=await q("computer"),s=o.join(a,String(e),String(t));if(!n.existsSync(s))throw new m("workspace directory does not exist",{field:"workDir",workDir:s});const l=`computer:${e}:${t}`;r(l,"INFO","Execute command in workspace",{userId:e,cId:t,workDir:s,command:i});const c=10*60*1e3,d={cwd:s,timeout:c,maxBuffer:50*1024*1024};return X.BASH_PATH&&(d.shell=X.BASH_PATH),new Promise((p,D)=>{Pe(i,d,(x,f,g)=>{const u=x?x.killed?-1:x.code||1:0,h=x&&x.killed?(g||"")+`
|
|
2
|
+
Command timed out after ${c/1e3}s`:g||"";r(l,"INFO","Execute command completed",{userId:e,cId:t,exitCode:u,killed:x?x.killed:!1,stdoutLength:f?f.length:0,stderrLength:h.length}),p({stdout:f||"",stderr:h,exitCode:u})})})}async function Ze(e,t,i){const a=`computer:${e}:${t}`;if(!e)throw new m("userId cannot be empty",{field:"userId"});if(!t)throw new m("cId cannot be empty",{field:"cId"});const s=await q(a),l=o.join(s,String(e),String(t));if(!n.existsSync(l))throw new m("workspace directory does not exist",{field:"workDir",workDir:l});const c=X.ZIP_WORKSPACE_EXCLUDE||[],d=Array.isArray(i)?i:[],p=new Set([...c,...d]),D=`${e}_${t}.zip`,x=Date.now();r(a,"INFO","Zip workspace request",{userId:e,cId:t,targetDir:l,excludeDirs:[...p]});const f=Ee("zip",{zlib:{level:9}});return f.directory(l,!1,g=>{const u=g.name||"";if(u.split(/[\/\\]/).filter(Boolean).some(W=>p.has(W)))return!1;try{const W=o.join(l,u);if(n.lstatSync(W).isSymbolicLink())return!1}catch{return!1}return g}),f.on("warning",g=>{g.code==="ENOENT"?r(a,"WARN","Encountered file problem when creating zip",{message:g.message,code:g.code}):r(a,"ERROR","Encountered warning when creating zip",{message:g.message,code:g.code})}),f.on("error",g=>{r(a,"ERROR","Failed to create zip",{message:g.message,elapsedMs:Date.now()-x})}),f.on("end",()=>{r(a,"INFO","Workspace zip created successfully",{targetDir:l,zipFileName:D,elapsedMs:Date.now()-x})}),{archive:f,zipFileName:D}}const ue=new Set([...X.ZIP_WORKSPACE_EXCLUDE||[],"dist-packages"]);function re(e){let t;try{t=n.readdirSync(e,{withFileTypes:!0})}catch{return null}const i=o.join(e,"scripts","package-platforms.mjs");if(n.existsSync(i))return e;for(const a of t)if(a.isDirectory()&&!ue.has(a.name)){const s=re(o.join(e,a.name));if(s)return s}return null}function oe(e,t,i,a=10*60*1e3){return new Promise(s=>{const l=je(e,t,{...i,timeout:a});let c="",d="";l.stdout?.on("data",p=>{c+=p.toString()}),l.stderr?.on("data",p=>{d+=p.toString()}),l.on("error",p=>{s({stdout:c,stderr:d+`
|
|
3
|
+
Failed to spawn ${e}: ${p.message}`,exitCode:1})}),l.on("close",(p,D)=>{s(p===null&&D?{stdout:c,stderr:d+`
|
|
4
|
+
Process killed by signal ${D} (timeout after ${a/1e3}s)`,exitCode:-1}:{stdout:c,stderr:d,exitCode:p??1})})})}function Ge(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 Ke(e,t,i,a){const s=`computer:${e}:${t}`;if(!e)throw new m("userId cannot be empty",{field:"userId"});if(!t)throw new m("cId cannot be empty",{field:"cId"});if(!i)throw new m("agentId cannot be empty",{field:"agentId"});if(!a)throw new m("version cannot be empty",{field:"version"});const l=await q(s),c=o.join(l,String(e),String(t));if(!n.existsSync(c))throw new m("workspace directory does not exist",{field:"workDir",workDir:c});r(s,"INFO","Build agent package request",{userId:e,cId:t,agentId:i,version:a});const d=re(c);if(!d)throw new m("package-platforms.mjs not found in workspace",{field:"script"});r(s,"INFO","Found package script",{projectDir:d});const p=await Ne(d,s);if(r(s,"INFO","pnpm install completed",{exitCode:p.exitCode,stderrLength:p.stderr.length}),p.exitCode!==0)throw r(s,"ERROR","pnpm install failed",{stderr:p.stderr}),new U(`pnpm install failed: ${p.stderr}`);const D=o.join(d,"dist-packages"),x=["scripts/package-platforms.mjs",`agent-${i}`,a,D,"--print-artifacts"],f=await oe("node",x,{cwd:d,env:me()});if(r(s,"INFO","Build completed",{exitCode:f.exitCode,stdoutLength:f.stdout.length}),f.exitCode!==0)throw r(s,"ERROR","Build failed",{stderr:f.stderr}),new U(`Build failed: ${f.stderr}`);const g=[".tar.gz",".tar.bz2",".zip",".tgz"],u=[],h=f.stdout.split(`
|
|
5
|
+
`);for(const W of h){const _=W.trim();if(!_)continue;const A=_.toLowerCase();if(!g.some(R=>A.endsWith(R)))continue;const k=o.resolve(d,_);let z=o.relative(c,k);z=z.split(o.sep).join("/");const b=o.basename(_),y=Ge(b);u.push({path:z,fileName:b,platform:y||""})}return r(s,"INFO","Build agent package completed",{artifactsCount:u.length,artifacts:u}),{artifacts:u}}async function qe(e,t){const i=`computer:${e}:${t}`;if(!e)throw new m("userId cannot be empty",{field:"userId"});if(!t)throw new m("cId cannot be empty",{field:"cId"});const a=await q(i),s=o.join(a,String(e),String(t));if(!n.existsSync(s))return r(i,"WARN","Workspace not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const l=re(s);if(!l)return r(i,"WARN","package-platforms.mjs not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const c=o.join(l,"dist-packages");return n.existsSync(c)?(n.rmSync(c,{recursive:!0,force:!0}),r(i,"INFO","Cleaned dist-packages",{distPackagesDir:c}),{cleaned:!0}):(r(i,"INFO","dist-packages not found, nothing to clean",{distPackagesDir:c}),{cleaned:!1})}async function Je(e,t){const i=`computer:${e}:${t}`;if(!e)throw new m("userId cannot be empty",{field:"userId"});if(!t)throw new m("cId cannot be empty",{field:"cId"});const a=await q(i),s=o.join(a,String(e),String(t));return n.existsSync(s)?(await n.promises.rm(s,{recursive:!0,force:!0}),r(i,"INFO","Workspace deleted",{userId:e,cId:t,targetDir:s})):r(i,"WARN","Workspace not found, skip delete",{userId:e,cId:t,targetDir:s}),{deleted:!0}}function De(e){const t=o.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()&&!ue.has(a.name)&&a.name!=="node_modules"){const s=De(o.join(e,a.name));if(s)return s}return null}const xe=["neverBuiltDependencies","onlyBuiltDependencies","ignoredBuiltDependencies"],He=["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 Ve(e,t){const i=o.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 l=xe.filter(c=>c in s.pnpm);if(l.length!==0){for(const c of l)delete s.pnpm[c];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:l})}}async function Ye(e,t){const i=o.join(e,"pnpm-workspace.yaml");if(!n.existsSync(i))return;const s=(await n.promises.readFile(i,"utf8")).split(`
|
|
7
|
+
`),l=[];let c=-1,d=[];for(const p of s){const D=p.trimStart(),x=p.length-D.length,f=xe.find(g=>new RegExp(`^${g}:\\s*(.*)$`).test(D));if(f){d.push(f);const g=D.slice(f.length+1).trim();(!g||g==="|"||g===">")&&(c=x);continue}if(c>=0){if(D===""||x>c)continue;c=-1}l.push(p)}d.length!==0&&(await n.promises.writeFile(i,l.join(`
|
|
8
|
+
`),"utf8"),r(t,"INFO","Removed conflicting pnpm built dependency settings from pnpm-workspace.yaml",{projectDir:e,removedKeys:d}))}async function Re(e){if(!n.existsSync(e))return"";const t=await n.promises.readFile(e,"utf8"),i=t.split(`
|
|
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 Xe(e,t){await Ve(e,t),await Ye(e,t),await Re(o.join(e,".npmrc"))}async function Qe(e,t){await Fe(e,t),await Xe(e,t);const i=o.join(e,".npmrc");let a=await Re(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 l=a&&!a.endsWith(`
|
|
11
11
|
`)?`
|
|
12
|
-
`:"";await n.promises.writeFile(i,`${
|
|
12
|
+
`:"";await n.promises.writeFile(i,`${a}${l}${s.join(`
|
|
13
13
|
`)}
|
|
14
|
-
`,"utf8"),r(t,"INFO","Updated .npmrc for pnpm install",{projectDir:e,additions:s})}}function
|
|
14
|
+
`,"utf8"),r(t,"INFO","Updated .npmrc for pnpm install",{projectDir:e,additions:s})}}function me(){const e={...process.env};delete e.CI,delete e.NPM_CONFIG_PRODUCTION,delete e.npm_config_production;for(const t of He)delete e[t];return e.NODE_ENV="development",e.NPM_CONFIG_PRODUCTION="false",e}async function Ne(e,t){await Qe(e,t);const i=me();return oe("pnpm",["install","--prefer-offline","--config.production=false","--config.confirmModulesPurge=false","--config.dangerouslyAllowAllBuilds=true"],{cwd:e,env:i})}function _e(e){const t=o.join(e,"pyproject.toml"),i=o.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()&&!ue.has(s.name)&&s.name!==".venv"&&s.name!=="venv"&&s.name!=="__pycache__"){const l=_e(o.join(e,s.name));if(l)return l}return null}async function Ie(e,t,i){const a=`computer:${e}:${t}`,s=String(i||"").trim().toLowerCase();if(!e)throw new m("userId cannot be empty",{field:"userId"});if(!t)throw new m("cId cannot be empty",{field:"cId"});if(!s)throw new m("programmingLanguage cannot be empty",{field:"programmingLanguage"});const l=await q(a),c=o.join(l,String(e),String(t));if(!n.existsSync(c))throw new m("workspace directory does not exist",{field:"workDir",workDir:c});r(a,"INFO","Install project dependencies request",{userId:e,cId:t,programmingLanguage:s});const d=me();let p=null,D=null;if(s==="typescript"||s==="ts"){if(p=re(c)||De(c),!p)throw new m("TypeScript project not found in workspace",{field:"projectDir"});D=await Ne(p,a)}else if(s==="python"||s==="py"){if(p=_e(c),!p)throw new m("Python project not found in workspace",{field:"projectDir"});const x=o.join(p,"pyproject.toml"),f=o.join(p,"requirements.txt");if(n.existsSync(x))D=await oe("pip",["install","-e","."],{cwd:p,env:d});else if(n.existsSync(f))D=await oe("pip",["install","-r","requirements.txt"],{cwd:p,env:d});else throw new m("No pyproject.toml or requirements.txt found in workspace",{field:"projectDir"})}else throw new m("Unsupported programmingLanguage",{field:"programmingLanguage",programmingLanguage:s});if(r(a,"INFO","Project dependencies install completed",{projectDir:p,programmingLanguage:s,exitCode:D.exitCode,stderrLength:D.stderr.length}),D.exitCode!==0)throw r(a,"ERROR","Project dependencies install failed",{projectDir:p,programmingLanguage:s,stderr:D.stderr}),new U(`Project dependencies install failed: ${D.stderr||D.stdout}`);return{message:"Project dependencies installed successfully",projectDir:p,programmingLanguage:s}}export{Ce as createWorkspace,Le as pushSkillsToWorkspace,Be as initProjectTemplate,Ue as executeCommand,Je as deleteWorkspace,Ze as zipWorkspace,Ke as buildAgentPackage,qe as cleanupBuildArtifacts,Ie as installProjectDependencies};
|
package/package.json
CHANGED