nuwax-file-server 1.3.0-beta.21 → 1.3.0-beta.23
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/utils/computer/computerUtils.js +10 -5
- 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.0-beta.21",d=new ee,w={reset:"\x1B[0m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",cyan:"\x1B[36m"};function j(e){console.error(`${w.red}ERROR: ${e}${w.reset}`)}function b(e){console.log(`${w.green}${e}${w.reset}`)}function oe(e){console.log(`${w.blue}${e}${w.reset}`)}function y(e){return async(...t)=>{try{let o=await e(...t);o&&o.success===!1?(j(o.message||"Command failed"),process.exitCode=1):process.exitCode=0}catch(o){j(o?.message||"Command failed with unexpected error"),process.exitCode=1}}}async function se(e){let t=await N({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(`Service started (PID: ${t.pid})`),t}async function re(e){let t=await T({force:e.force,timeout:Number(e.timeout)});return t.success&&b(t.message||"Service stopped"),t}async function ne(e){let t=await O({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(t.message||"Service restarted"),t}function ie(){let e=_();return oe(`${n.name} service status:`),console.log(""),console.log(` Service name: ${n.name}`),console.log(` Running status: ${e.running?"Running":"Stopped"}`),console.log(` Message: ${e.message}`),console.log(` PID file: ${$()}`),e.pidInfo&&(console.log(` Process ID: ${e.pidInfo.pid}`),console.log(` Environment: ${e.pidInfo.env||"Unknown"}`),console.log(` Port: ${e.pidInfo.port||"Unknown"}`),console.log(` Version: ${e.pidInfo.version||A}`),console.log(` Platform: ${e.pidInfo.platform||process.platform}`),console.log(` Started at: ${e.pidInfo.startedAt||"Unknown"}`),console.log(` Uptime: ${L(e.pidInfo.startedAt)}`)),console.log(""),{success:e.running,message:e.message}}function ce(){d.name("nuwax-file-server").description("Cross-platform file service deployment tool, supporting start/stop/restart/status").version(A,"-v, --version","Display version number").helpOption("-h, --help","Display help information"),d.command("start").allowUnknownOption().description("Start service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(se)),d.command("stop").description("Stop service").option("--force","Force stop").option("--timeout <ms>","\u505C\u6B62\u670D\u52A1\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).action(y(re)),d.command("restart").allowUnknownOption().description("Restart service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(ne)),d.command("status").description("View service status").action(y(ie)),d.command("help").description("Display help information").action(()=>{d.outputHelp()}),d.parse(process.argv),process.argv.slice(2).length||(d.outputHelp(),process.exitCode=0)}ce();
|
|
2
|
+
import{Command as ee}from"commander";import{createRequire as te}from"module";import f from"path";import U from"os";import c from"fs-extra";import{spawn as E}from"cross-spawn";import V from"tree-kill";import{fileURLToPath as q}from"url";import{execFileSync as R}from"child_process";import M from"http";import{createRequire as G}from"module";var I=f.dirname(q(import.meta.url)),H=G(import.meta.url),n={name:"nuwax-file-server",pidDir:f.join(U.tmpdir(),"nuwax-file-server"),pidFileName:"server.pid",lockFileName:"start.lock",defaultStopTimeout:3e4,defaultStartTimeout:3e4,staleLockTimeout:12e4,checkInterval:500};function $(){return f.join(n.pidDir,n.pidFileName)}function F(){return f.join(n.pidDir,n.lockFileName)}function J(){let e=[f.join(I,"..","server.js"),f.join(I,"server.js")];for(let t of e)if(c.existsSync(t))return t;return e[0]}function W(){let e=["../../package.json","../package.json"];for(let t of e)try{let o=H(t);if(o?.version)return o.version}catch{}return"unknown"}function x(){try{let e=$();if(!c.existsSync(e))return null;let t=c.readFileSync(e,"utf8"),o=JSON.parse(t);return!o||typeof o.pid!="number"?null:o}catch(e){return e.code!=="ENOENT"&&console.error(`Read PID file failed: ${e.message}`),null}}function B(e){try{let t=$();c.ensureDirSync(n.pidDir),c.writeFileSync(t,JSON.stringify(e,null,2)),console.debug(`PID file written: ${t}`)}catch(t){throw console.error(`Write PID file failed: ${t.message}`),t}}function v(){try{let e=$();c.existsSync(e)&&(c.removeSync(e),console.debug(`PID file deleted: ${e}`))}catch(e){console.error(`Delete PID file failed: ${e.message}`)}}function g(e){try{return process.kill(e,0),!0}catch(t){return t.code!=="ESRCH"}}function K(e){try{return k()?R("powershell",["-NoProfile","-Command",`(Get-CimInstance Win32_Process -Filter "ProcessId = ${e}").CommandLine`],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim():R("ps",["-p",String(e),"-o","command="],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return""}}function X(e){if(!g(e))return!1;let t=K(e);if(!t)return!1;let o=t.toLowerCase(),s=f.basename(f.join(I,"..","server.js")).toLowerCase(),i=n.name.toLowerCase();return o.includes(s)||o.includes(i)}function z(){c.ensureDirSync(n.pidDir);let e=F(),t=`${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2,10)}`,o=JSON.stringify({pid:process.pid,token:t,createdAt:new Date().toISOString()},null,2);try{let s=c.openSync(e,"wx");return c.writeSync(s,o),{fd:s,token:t}}catch(s){if(s.code!=="EEXIST")throw s;try{let i=c.readFileSync(e,"utf8"),r=JSON.parse(i),a=Number(r?.pid),u=r?.createdAt?new Date(r.createdAt).getTime():0,l=Date.now()-u,C=Number.isFinite(a)&&a>0&&g(a),p=!u||Number.isNaN(l)||l>n.staleLockTimeout;if(!C||p){c.removeSync(e);let S=c.openSync(e,"wx");return c.writeSync(S,o),console.warn("Detected stale start lock, auto cleaned"),{fd:S,token:t}}}catch{c.removeSync(e);let r=c.openSync(e,"wx");return c.writeSync(r,o),console.warn("Detected invalid start lock, auto cleaned"),{fd:r,token:t}}throw s}}function Q(e){let t=e?.fd,o=e?.token;try{t!=null&&c.closeSync(t)}catch{}try{let s=F();if(c.existsSync(s)){let i=!1;if(!o)i=!0;else try{let r=c.readFileSync(s,"utf8");i=JSON.parse(r)?.token===o}catch{i=!0}i&&c.removeSync(s)}}catch{}}function k(){return process.platform==="win32"}async function Y(e=n.defaultStopTimeout){let t=x();return t?X(t.pid)?(console.log(`Existing service process detected (PID: ${t.pid}), stopping before start...`),await h(t.pid,!1)&&await D(t.pid,e)?(v(),{success:!0,message:`Existing process ${t.pid} stopped gracefully`}):(console.warn(`Graceful stop timeout or failed for PID ${t.pid}, force stop...`),await h(t.pid,!0)?await D(t.pid,e)?(v(),{success:!0,message:`Existing process ${t.pid} stopped forcibly`}):{success:!1,message:`Existing process ${t.pid} did not exit after force stop`}:{success:!1,message:`Failed to stop existing process ${t.pid}`})):(console.log(`Found stale PID file (PID: ${t.pid}), clean it...`),v(),{success:!0,message:"Stale PID file cleaned"}):{success:!0,message:"No existing service process"}}async function h(e,t=!1){return new Promise(o=>{if(!g(e)){console.debug(`Process ${e} does not exist, already stopped`),o(!0);return}let s=t?"SIGKILL":"SIGTERM",i=k()?"taskkill":"tree-kill";if(console.debug(`Use ${i} to stop process ${e} (signal: ${s})`),k()){let r=t?["/F","/PID",String(e)]:["/PID",String(e)],a=E("taskkill",r,{stdio:["ignore","pipe","pipe"],windowsHide:!0}),u="";a.stdout.on("data",l=>{u+=l.toString()}),a.stderr.on("data",l=>{u+=l.toString()}),a.on("error",l=>{console.error(`Stop process failed: ${l.message}`),o(!1)}),a.on("close",l=>{l===0?(console.debug(`Process ${e} stopped`),o(!0)):(console.warn(`taskkill exit code: ${l}, output: ${u}`),t?o(!1):h(e,!0).then(o))})}else V(e,s,r=>{r?r.code==="ESRCH"?(console.debug(`Process ${e} does not exist`),o(!0)):(console.error(`Stop process failed: ${r.message}`),o(!1)):(console.debug(`Process ${e} stopped`),o(!0))})})}async function D(e,t=n.defaultStopTimeout){let o=Date.now();for(;g(e);){if(Date.now()-o>t)return console.warn(`Wait for process ${e} to stop timeout (${t}ms)`),!1;await new Promise(i=>setTimeout(i,n.checkInterval))}let s=Date.now()-o;return console.debug(`Process ${e} stopped after ${s}ms`),!0}async function Z(e,t=n.defaultStartTimeout){let o=Number(e);if(!Number.isFinite(o)||o<=0)return!1;let s=Date.now();for(;Date.now()-s<=t;){if(await new Promise(r=>{let a=M.get({host:"127.0.0.1",port:o,path:"/health",timeout:Math.min(2e3,n.checkInterval*4)},u=>{r(u.statusCode>=200&&u.statusCode<300),u.resume()});a.on("timeout",()=>{a.destroy(),r(!1)}),a.on("error",()=>r(!1))}))return!0;await new Promise(r=>setTimeout(r,n.checkInterval))}return!1}async function N(e={}){let{env:t,port:o,config:s}=e,i=null;console.log(`Start service ${n.name}...`);try{i=z()}catch(r){return r.code==="EEXIST"?{success:!1,pid:null,message:"Another start operation is in progress, please retry later"}:{success:!1,pid:null,message:`Acquire start lock failed: ${r.message}`}}try{let r=Number(e.timeout)||n.defaultStopTimeout,a=await Y(r);if(!a.success)return{success:!1,pid:null,message:`Service start blocked: ${a.message}`};let u={...process.env};t&&(u.NODE_ENV=t,console.log(`Environment: ${t}`)),o&&(u.PORT=o,console.log(`Port: ${o}`)),s&&(u.CONFIG_FILE=s,console.log(`Configuration file: ${s}`));let l=J(),p=E("node",[l,...[]],{env:u,stdio:["pipe","pipe","pipe"],detached:!0,cwd:process.cwd()});if(p.stdout.on("data",m=>{process.stdout.write(m)}),p.stderr.on("data",m=>{process.stderr.write(m)}),p.on("error",m=>{console.error(`Start service failed: ${m.message}`)}),await new Promise(m=>setTimeout(m,2e3)),!g(p.pid))return{success:!1,pid:null,message:"Service start failed"};let S={pid:p.pid,startedAt:new Date().toISOString(),env:t||process.env.NODE_ENV||"production",port:o||process.env.PORT||"60000",version:W(),platform:process.platform};B(S);let P=Number(e.startTimeout)||n.defaultStartTimeout;return await Z(S.port,P)?(p.unref(),console.log(`Service started (PID: ${p.pid})`),console.log(`Service address: http://localhost:${S.port}`),{success:!0,pid:p.pid,message:"Service started successfully"}):(console.error(`Service health check timeout (${P}ms), stop failed instance...`),await h(p.pid,!0),v(),{success:!1,pid:null,message:`Service health check timeout (${P}ms)`})}finally{Q(i)}}async function T(e={}){let{force:t=!1,timeout:o=n.defaultStopTimeout}=e;console.log(`Stop service ${n.name}...`);let s=x();if(!s)return{success:!1,message:"Service not found"};if(!g(s.pid))return console.log("Service process has stopped, clean PID file..."),v(),{success:!0,message:"Service has stopped (process has exited)"};if(!await h(s.pid,t))return{success:!1,message:"Stop service failed"};let r=await D(s.pid,o);return v(),r?{success:!0,message:"Service has stopped"}:{success:!1,message:"Service stop timeout"}}async function O(e={}){console.log(`Restart service ${n.name}...`);let t=await T(e);!t.success&&t.message!=="Service not found"&&console.warn(`Stop service failed: ${t.message}`),await new Promise(s=>setTimeout(s,2e3));let o=await N(e);return o.success?{success:!0,pid:o.pid,message:"Service has restarted"}:{success:!1,pid:null,message:`Restart failed: ${o.message}`}}function _(){let e=x();return e?g(e.pid)?{running:!0,pidInfo:e,message:"Service running"}:{running:!1,pidInfo:e,message:"Service process does not exist"}:{running:!1,pidInfo:null,message:"Service not running"}}function L(e){try{let t=new Date(e),o=new Date;if(isNaN(t.getTime()))return"unknown";let s=Math.floor((o-t)/1e3),i=Math.floor(s/3600),r=Math.floor(s%3600/60),a=s%60;return i>0?`${i} hours ${r} minutes ${a} seconds`:r>0?`${r} minutes ${a} seconds`:`${a} seconds`}catch{return"unknown"}}var Pe=te(import.meta.url),A="1.3.0-beta.23",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,5 +1,10 @@
|
|
|
1
|
-
import i from"fs";import r from"path";import{exec as re}from"child_process";import oe from"cross-spawn";import ae from"archiver";import L from"../../appConfig/index.js";import{extractZip as Z}from"../common/zipUtils.js";import{ValidationError as h,SystemError as $,FileError as q}from"../error/errorHandler.js";import{log as o}from"../log/logUtils.js";import{ensurePrimaryAgentDirs as ee,syncAgents as Y}from"../common/AgentWorkspaceUtils.js";function te(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 s=JSON.parse(t);if(Array.isArray(s))return s.map(c=>typeof c=="string"?c.trim():"").filter(Boolean)}catch{}return[t]}return[]}async function ie(e,t,s){let c;try{c=await fetch(e)}catch(y){throw new q(`Failed to download skill zip from url: ${e}`,{url:e,reason:y.message})}if(!c.ok)throw new q(`Failed to download skill zip from url: ${e}`,{url:e,status:c.status,statusText:c.statusText});const n=c.headers.get("content-type")||"";n&&!n.includes("zip")&&!n.includes("octet-stream")&&o(s,"WARN","Downloaded skill url content-type is not typical zip",{url:e,contentType:n});const a=await c.arrayBuffer(),w=Buffer.from(a);await i.promises.writeFile(t,w)}async function W(e="computer"){const t=L.COMPUTER_WORKSPACE_DIR;if(!t)throw new h("COMPUTER_WORKSPACE_DIR configuration does not exist",{field:"COMPUTER_WORKSPACE_DIR"});return i.existsSync(t)||(await i.promises.mkdir(t,{recursive:!0}),o(e,"INFO","Created user workspace root directory",{workspaceRoot:t})),t}async function X(e,t){const s=r.join(e,t);if(i.existsSync(s)&&(await i.promises.lstat(s)).isDirectory())return s;const c=await i.promises.readdir(e,{withFileTypes:!0});for(const n of c){if(!n.isDirectory())continue;const a=r.join(e,n.name),w=r.join(a,t);if(i.existsSync(w)&&(await i.promises.lstat(w)).isDirectory())return w}return null}async function ve(e){return X(e,"skills")}const ce=".dynamic_add.lock";function le(e){const t=r.join(e,ce);return i.existsSync(t)&&i.statSync(t).isFile()}async function b(e){i.existsSync(e)&&await i.promises.rm(e,{recursive:!0,force:!0})}async function C(e,t){try{await i.promises.rename(e,t)}catch(s){if(s.code==="EXDEV"){async function c(n,a){if((await i.promises.lstat(n)).isDirectory()){await i.promises.mkdir(a,{recursive:!0});const y=await i.promises.readdir(n);for(const p of y)await c(r.join(n,p),r.join(a,p))}else await i.promises.copyFile(n,a)}await c(e,t),await i.promises.rm(e,{recursive:!0,force:!0})}else throw s}}async function pe(e,t,s,c,n,a){const w=t&&t.trim(),y=s&&s.trim(),p=c&&c.trim(),l=Array.isArray(n)&&n.length>0;if(!w&&!y&&!p&&!l)return;const g=r.join(e,".claude");try{if(i.existsSync(g)||await i.promises.mkdir(g,{recursive:!0}),w)try{const m={mcpServers:JSON.parse(t)},S=r.join(e,".mcp.json");await i.promises.writeFile(S,JSON.stringify(m,null,2),"utf-8"),o(a,"INFO","Written .mcp.json to workspace root")}catch(m){o(a,"WARN","Failed to parse/write mcpServersConfig, skipping",{error:m.message})}const u={};if(y)try{u.hooks=JSON.parse(s)}catch(m){o(a,"WARN","Failed to parse hooksConfig, skipping",{error:m.message})}if(p)try{u.permissions=JSON.parse(c)}catch(m){o(a,"WARN","Failed to parse permissionsConfig, skipping",{error:m.message})}if(Object.keys(u).length>0){const m=r.join(g,"settings.json");await i.promises.writeFile(m,JSON.stringify(u,null,2),"utf-8"),o(a,"INFO","Written .claude/settings.json",{keys:Object.keys(u)})}if(l){const m=r.join(g,"hooks");i.existsSync(m)||await i.promises.mkdir(m,{recursive:!0});for(const S of n){if(!S||!S.path||!S.content)continue;const d=r.normalize(S.path);if(d.startsWith("..")||r.isAbsolute(d)){o(a,"WARN","Hook script path contains traversal, skipping",{path:S.path});continue}const x=r.join(g,d),P=r.dirname(x);i.existsSync(P)||await i.promises.mkdir(P,{recursive:!0}),await i.promises.writeFile(x,S.content,"utf-8"),await i.promises.chmod(x,493),o(a,"INFO","Written hook script",{path:S.path})}}}catch(u){o(a,"WARN","Failed to write .claude/settings or .mcp.json or hook scripts, not blocking workspace creation",{error:u.message})}}async function de(e,t,s,c,n,a,w,y){const p=Date.now(),l=`computer:${e}:${t}`,g=te(c),u=[],m=[],S=new Set;if(!e)throw new h("userId cannot be empty",{field:"userId"});if(!t)throw new h("cId cannot be empty",{field:"cId"});const d=await W(l),x=r.join(d,String(e),String(t),".tmp"),P=r.join(d,String(e),String(t)),{skillsDir:k,agentsDir:D,agentTypes:O}=await ee(P);i.existsSync(P)||await i.promises.mkdir(P,{recursive:!0});const j=r.join(x,`preserved_skills_${Date.now()}_${Math.round(Math.random()*1e6)}`);if(i.existsSync(k)){const N=(await i.promises.readdir(k,{withFileTypes:!0})).filter(E=>E.isDirectory()&&le(r.join(k,E.name)));if(N.length>0){await i.promises.mkdir(j,{recursive:!0});for(const E of N){const R=r.join(k,E.name),v=r.join(j,E.name);await C(R,v)}o(l,"INFO","\u4FDD\u7559\u542B .dynamic_add.lock \u7684 skill",{preserved:N.map(E=>E.name)})}}if(await b(k),await b(D),await i.promises.mkdir(k,{recursive:!0}),await i.promises.mkdir(D,{recursive:!0}),i.existsSync(j)){const f=await i.promises.readdir(j,{withFileTypes:!0});for(const N of f)if(N.isDirectory()){const E=r.join(j,N.name),R=r.join(k,N.name);await C(E,R)}await b(j)}const z=i.existsSync(k),T=i.existsSync(D);o(l,"INFO","Deleted old skills and agents directories completed",{userId:e,cId:t,targetSkillsDir:k,targetAgentsDir:D,agentTypes:O,skillsExists:z,agentsExists:T});const A=r.join(P,".mcp.json");i.existsSync(A)&&await i.promises.unlink(A);const U=r.join(P,".claude","settings.json");if(i.existsSync(U)&&await i.promises.unlink(U),await pe(P,n,w,a,y,l),!s&&g.length===0)return await Y(P),o(l,"INFO","Created workspace (no uploaded file, no skills and agents)",{userId:e,cId:t,workspaceRoot:d,skillsDir:null,agentsDir:null,agentTypes:O,elapsedMs:Date.now()-p}),{message:"Workspace created (no uploaded file, no skills and agents)",workspaceRoot:d};if(s){if(!s.path)throw new h("Uploaded file has no valid path",{field:"file.path"});if(r.extname(s.originalname||s.filename||"").toLowerCase()!==".zip")throw new h("Only zip files are supported",{field:"file",originalName:s.originalname})}o(l,"DEBUG","Start processing workspace resources",{userId:e,cId:t,workspaceRoot:d,hasUploadedZip:!!s,skillUrlsCount:g.length});try{i.existsSync(x)||await i.promises.mkdir(x,{recursive:!0});const f=[];if(s){const R=r.join(x,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);m.push(R),await i.promises.mkdir(R,{recursive:!0}),o(l,"DEBUG","Start extracting uploaded zip file",{extractRoot:R}),await Z(s.path,R),o(l,"DEBUG","Uploaded zip file extracted successfully",{extractRoot:R});const v=await X(R,"skills"),M=await X(R,"agents");if(v){await i.promises.mkdir(k,{recursive:!0});const _=await i.promises.readdir(v,{withFileTypes:!0});for(const B of _){if(!B.isDirectory())continue;const J=r.join(v,B.name),K=r.join(k,B.name);i.existsSync(K)&&await b(K),await C(J,K),S.add(B.name)}f.push("skills"),o(l,"INFO","skills updated to workspace",{userId:e,cId:t,workspaceRoot:d,targetSkillsDir:k,agentTypes:O})}else o(l,"INFO","skills directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:R});M?(await b(D),await C(M,D),f.push("agents"),o(l,"INFO","agents updated to workspace",{userId:e,cId:t,workspaceRoot:d,targetAgentsDir:D,agentTypes:O})):o(l,"INFO","agents directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:R})}for(let R=0;R<g.length;R+=1){const v=g[R],M=r.join(x,`skill_url_${Date.now()}_${R}_${Math.round(Math.random()*1e6)}.zip`);u.push(M);const _=r.join(x,`skill_url_extract_${Date.now()}_${R}_${Math.round(Math.random()*1e6)}`);m.push(_),await i.promises.mkdir(_,{recursive:!0}),o(l,"INFO","Start download skill zip from url",{userId:e,cId:t,skillUrl:v}),await ie(v,M,l),o(l,"INFO","Skill zip downloaded, start extracting",{userId:e,cId:t,skillUrl:v,downloadedZipPath:M}),await Z(M,_);const J=(await i.promises.readdir(_,{withFileTypes:!0})).filter(F=>F.isDirectory()&&!F.name.startsWith(".")),I=J.find(F=>F.name==="skills")?(await i.promises.readdir(r.join(_,"skills"),{withFileTypes:!0})).filter(F=>F.isDirectory()&&!F.name.startsWith(".")).map(F=>({name:F.name,sourcePath:r.join(_,"skills",F.name)})):J.map(F=>({name:F.name,sourcePath:r.join(_,F.name)}));if(I.length===0){o(l,"WARN","No skill directory found after extracting skill url zip",{userId:e,cId:t,skillUrl:v,extractRoot:_});continue}await i.promises.mkdir(k,{recursive:!0});for(const F of I){const H=r.join(k,F.name);i.existsSync(H)&&await b(H),await C(F.sourcePath,H),S.add(F.name),o(l,"INFO","Skill from url updated to workspace",{userId:e,cId:t,skillUrl:v,skillName:F.name,destSkillPath:H})}f.includes("skills")||f.push("skills")}f.length===0&&o(l,"WARN","No valid skills or agents directories found",{userId:e,cId:t,hasUploadedZip:!!s,skillUrlsCount:g.length});const N=Array.from(S),E=f.length>0?`Workspace created successfully, ${f.join(" and ")} updated`:"Workspace created successfully (skills and agents directories not found)";return o(l,"INFO",E,{userId:e,cId:t,updatedDirs:f,updatedSkills:N,agentTypes:O,elapsedMs:Date.now()-p}),await Y(P),{message:E,workspaceRoot:d,updatedSkills:N}}catch(f){throw o(l,"ERROR","Failed to process workspace resources",{userId:e,cId:t,error:f.message,elapsedMs:Date.now()-p}),f instanceof h||f instanceof q||f instanceof $?f:new $(`Failed to create workspace: ${f.message}`,{userId:e,cId:t})}finally{try{for(const f of m)i.existsSync(f)&&await i.promises.rm(f,{recursive:!0,force:!0})}catch(f){o(l,"WARN","Failed to clean up temporary extracted zip",{error:f.message})}for(const f of u)try{i.existsSync(f)&&await i.promises.unlink(f)}catch(N){o(l,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:f,error:N.message})}try{s&&s.path&&i.existsSync(s.path)&&await i.promises.unlink(s.path)}catch(f){o(l,"WARN","Failed to clean up uploaded zip file",{tempZipPath:s?.path,error:f.message})}}}async function me(e,t,s,c){const n=Date.now(),a=`computer:${e}:${t}`,w=te(c),y=[],p=[];if(!e)throw new h("userId cannot be empty",{field:"userId"});if(!t)throw new h("cId cannot be empty",{field:"cId"});if(!s&&w.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(r.extname(s.originalname||s.filename||"").toLowerCase()!==".zip")throw new h("Only zip files are supported",{field:"file",originalName:s?.originalname})}const l=await W(a),g=r.join(l,String(e),String(t),".tmp"),u=r.join(l,String(e),String(t)),{skillsDir:m,agentTypes:S}=await ee(u);try{i.existsSync(u)||await i.promises.mkdir(u,{recursive:!0}),i.existsSync(m)||await i.promises.mkdir(m,{recursive:!0}),i.existsSync(g)||await i.promises.mkdir(g,{recursive:!0});const d=[],x=new Set;if(s){const k=r.join(g,`skill_push_${Date.now()}_${Math.round(Math.random()*1e6)}`);p.push(k),await i.promises.mkdir(k,{recursive:!0}),o(a,"DEBUG","Start extracting skill zip file",{extractRoot:k}),await Z(s.path,k),o(a,"DEBUG","Skill zip file extracted successfully",{extractRoot:k});const D=await X(k,"skills");if(!D)o(a,"WARN","skills directory not found in uploaded zip",{userId:e,cId:t,extractRoot:k});else{const j=(await i.promises.readdir(D,{withFileTypes:!0})).filter(z=>z.isDirectory()&&!z.name.startsWith("."));if(j.length===0)o(a,"WARN","skills directory in uploaded zip has no skill subdirectories",{userId:e,cId:t,skillsDir:D});else for(const z of j){const T=r.join(D,z.name),A=r.join(m,z.name);i.existsSync(A)&&await b(A),await C(T,A),x.add(z.name),o(a,"INFO","skill pushed to workspace from uploaded zip",{userId:e,cId:t,skillName:z.name,destSkillPath:A})}}}for(let k=0;k<w.length;k+=1){const D=w[k],O=r.join(g,`skill_push_url_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}.zip`);y.push(O);const j=r.join(g,`skill_push_url_extract_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}`);p.push(j),await i.promises.mkdir(j,{recursive:!0}),o(a,"INFO","Start download skill zip for push from url",{userId:e,cId:t,skillUrl:D}),await ie(D,O,a),await Z(O,j);const T=(await i.promises.readdir(j,{withFileTypes:!0})).filter(f=>f.isDirectory()&&!f.name.startsWith(".")),U=T.find(f=>f.name==="skills")?(await i.promises.readdir(r.join(j,"skills"),{withFileTypes:!0})).filter(f=>f.isDirectory()&&!f.name.startsWith(".")).map(f=>({name:f.name,sourcePath:r.join(j,"skills",f.name)})):T.map(f=>({name:f.name,sourcePath:r.join(j,f.name)}));if(U.length===0){o(a,"WARN","No skill directory found after extracting push skill url zip",{userId:e,cId:t,skillUrl:D,extractRoot:j});continue}for(const f of U){const N=r.join(m,f.name);i.existsSync(N)&&await b(N),await C(f.sourcePath,N),x.add(f.name),o(a,"INFO","skill pushed to workspace from url zip",{userId:e,cId:t,skillUrl:D,skillName:f.name,destSkillPath:N})}}for(const k of x)d.push(k);const P=d.length>0?`Pushed ${d.length} skills: ${d.join(", ")}`:"No valid skill directories found in file or skillUrls";return o(a,"INFO",P,{userId:e,cId:t,updatedSkills:d,agentTypes:S,elapsedMs:Date.now()-n}),await Y(u),{message:P,workspaceRoot:l,updatedSkills:d}}catch(d){throw o(a,"ERROR","Failed to push skill to workspace",{userId:e,cId:t,error:d.message,elapsedMs:Date.now()-n}),d instanceof h||d instanceof q||d instanceof $?d:new $(`Failed to push skill to workspace: ${d.message}`,{userId:e,cId:t})}finally{try{for(const d of p)i.existsSync(d)&&await i.promises.rm(d,{recursive:!0,force:!0})}catch(d){o(a,"WARN","Failed to clean up temporary extracted zip",{error:d.message})}for(const d of y)try{i.existsSync(d)&&await i.promises.unlink(d)}catch(x){o(a,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:d,error:x.message})}try{s&&s.path&&i.existsSync(s.path)&&await i.promises.unlink(s.path)}catch(d){o(a,"WARN","Failed to clean up uploaded zip file",{tempZipPath:s?.path,error:d.message})}}}async function fe(e,t,s,c){const n=Date.now(),a=`computer:${e}:${t}`;if(!e)throw new h("userId cannot be empty",{field:"userId"});if(!t)throw new h("cId cannot be empty",{field:"cId"});if(!s||!s.path)throw new h("file is required",{field:"file"});if(r.extname(s.originalname||s.filename||"").toLowerCase()!==".zip")throw new h("Only zip files are supported",{field:"file",originalName:s.originalname});const y=await W(a),p=r.join(y,String(e),String(t)),l=r.join(p,".tmp"),g=r.join(l,`template_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{i.existsSync(p)||await i.promises.mkdir(p,{recursive:!0}),i.existsSync(l)||await i.promises.mkdir(l,{recursive:!0}),await i.promises.mkdir(g,{recursive:!0}),o(a,"DEBUG","Start extracting template zip file",{extractRoot:g}),await Z(s.path,g),o(a,"DEBUG","Template zip file extracted successfully",{extractRoot:g});const u=await i.promises.readdir(g,{withFileTypes:!0});for(const m of u){const S=r.join(g,m.name),d=r.join(p,m.name);if(m.isDirectory())await C(S,d);else{const x=r.dirname(d);i.existsSync(x)||await i.promises.mkdir(x,{recursive:!0}),await i.promises.copyFile(S,d)}}if(o(a,"INFO","Template files extracted to workspace",{userId:e,cId:t,targetDir:p,fileCount:u.length}),L.GIT_ENABLED&&(c==="true"||c===!0)){const m=await import("../../service/gitService.js");await m.default.init({workspaceType:"taskAgent",userId:e,cId:t}),await m.default.commit({workspaceType:"taskAgent",userId:e,cId:t,message:"Initial commit"}),o(a,"INFO","Git init and initial commit done",{userId:e,cId:t})}return o(a,"INFO","Init project template completed",{userId:e,cId:t,targetDir:p,elapsedMs:Date.now()-n}),{message:"Project template initialized successfully",workspaceRoot:p}}catch(u){throw o(a,"ERROR","Failed to init project template",{userId:e,cId:t,error:u.message,elapsedMs:Date.now()-n}),u instanceof h||u instanceof q||u instanceof $?u:new $(`Failed to init project template: ${u.message}`,{userId:e,cId:t})}finally{try{i.existsSync(g)&&await i.promises.rm(g,{recursive:!0,force:!0})}catch(u){o(a,"WARN","Failed to clean up temporary extracted zip",{error:u.message})}try{s&&s.path&&i.existsSync(s.path)&&await i.promises.unlink(s.path)}catch(u){o(a,"WARN","Failed to clean up uploaded zip file",{tempZipPath:s?.path,error:u.message})}}}async function ue(e,t,s){if(!e)throw new h("userId cannot be empty",{field:"userId"});if(!t)throw new h("cId cannot be empty",{field:"cId"});if(!s||typeof s!="string"||!s.trim())throw new h("command cannot be empty",{field:"command"});const c=await W("computer"),n=r.join(c,String(e),String(t));if(!i.existsSync(n))throw new h("workspace directory does not exist",{field:"workDir",workDir:n});const a=`computer:${e}:${t}`;o(a,"INFO","Execute command in workspace",{userId:e,cId:t,workDir:n,command:s});const w=10*60*1e3,y={cwd:n,timeout:w,maxBuffer:50*1024*1024};return L.BASH_PATH&&(y.shell=L.BASH_PATH),new Promise((p,l)=>{re(s,y,(g,u,m)=>{const S=g?g.killed?-1:g.code||1:0,d=g&&g.killed?(m||"")+`
|
|
2
|
-
Command timed out after ${w/1e3}s`:m||"";o(
|
|
3
|
-
Failed to spawn ${e}: ${p.message}`,exitCode:1})}),
|
|
4
|
-
Process killed by signal ${l} (timeout after ${
|
|
5
|
-
`);for(const P of
|
|
1
|
+
import i from"fs";import a from"path";import{exec as oe}from"child_process";import ae from"cross-spawn";import ce from"archiver";import U from"../../appConfig/index.js";import{extractZip as q}from"../common/zipUtils.js";import{ValidationError as h,SystemError as _,FileError as G}from"../error/errorHandler.js";import{log as o}from"../log/logUtils.js";import{createPnpmNpmrc as le}from"../common/npmrcUtils.js";import{ensurePrimaryAgentDirs as ee,syncAgents as Y}from"../common/AgentWorkspaceUtils.js";function te(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 s=JSON.parse(t);if(Array.isArray(s))return s.map(r=>typeof r=="string"?r.trim():"").filter(Boolean)}catch{}return[t]}return[]}async function ie(e,t,s){let r;try{r=await fetch(e)}catch(y){throw new G(`Failed to download skill zip from url: ${e}`,{url:e,reason:y.message})}if(!r.ok)throw new G(`Failed to download skill zip from url: ${e}`,{url:e,status:r.status,statusText:r.statusText});const n=r.headers.get("content-type")||"";n&&!n.includes("zip")&&!n.includes("octet-stream")&&o(s,"WARN","Downloaded skill url content-type is not typical zip",{url:e,contentType:n});const c=await r.arrayBuffer(),w=Buffer.from(c);await i.promises.writeFile(t,w)}async function W(e="computer"){const t=U.COMPUTER_WORKSPACE_DIR;if(!t)throw new h("COMPUTER_WORKSPACE_DIR configuration does not exist",{field:"COMPUTER_WORKSPACE_DIR"});return i.existsSync(t)||(await i.promises.mkdir(t,{recursive:!0}),o(e,"INFO","Created user workspace root directory",{workspaceRoot:t})),t}async function V(e,t){const s=a.join(e,t);if(i.existsSync(s)&&(await i.promises.lstat(s)).isDirectory())return s;const r=await i.promises.readdir(e,{withFileTypes:!0});for(const n of r){if(!n.isDirectory())continue;const c=a.join(e,n.name),w=a.join(c,t);if(i.existsSync(w)&&(await i.promises.lstat(w)).isDirectory())return w}return null}async function be(e){return V(e,"skills")}const pe=".dynamic_add.lock";function de(e){const t=a.join(e,pe);return i.existsSync(t)&&i.statSync(t).isFile()}async function T(e){i.existsSync(e)&&await i.promises.rm(e,{recursive:!0,force:!0})}async function C(e,t){try{await i.promises.rename(e,t)}catch(s){if(s.code==="EXDEV"){async function r(n,c){if((await i.promises.lstat(n)).isDirectory()){await i.promises.mkdir(c,{recursive:!0});const y=await i.promises.readdir(n);for(const p of y)await r(a.join(n,p),a.join(c,p))}else await i.promises.copyFile(n,c)}await r(e,t),await i.promises.rm(e,{recursive:!0,force:!0})}else throw s}}async function me(e,t,s,r,n,c){const w=t&&t.trim(),y=s&&s.trim(),p=r&&r.trim(),l=Array.isArray(n)&&n.length>0;if(!w&&!y&&!p&&!l)return;const g=a.join(e,".claude");try{if(i.existsSync(g)||await i.promises.mkdir(g,{recursive:!0}),w)try{const m={mcpServers:JSON.parse(t)},x=a.join(e,".mcp.json");await i.promises.writeFile(x,JSON.stringify(m,null,2),"utf-8"),o(c,"INFO","Written .mcp.json to workspace root")}catch(m){o(c,"WARN","Failed to parse/write mcpServersConfig, skipping",{error:m.message})}const u={};if(y)try{u.hooks=JSON.parse(s)}catch(m){o(c,"WARN","Failed to parse hooksConfig, skipping",{error:m.message})}if(p)try{u.permissions=JSON.parse(r)}catch(m){o(c,"WARN","Failed to parse permissionsConfig, skipping",{error:m.message})}if(Object.keys(u).length>0){const m=a.join(g,"settings.json");await i.promises.writeFile(m,JSON.stringify(u,null,2),"utf-8"),o(c,"INFO","Written .claude/settings.json",{keys:Object.keys(u)})}if(l){const m=a.join(g,"hooks");i.existsSync(m)||await i.promises.mkdir(m,{recursive:!0});for(const x of n){if(!x||!x.path||!x.content)continue;const d=a.normalize(x.path);if(d.startsWith("..")||a.isAbsolute(d)){o(c,"WARN","Hook script path contains traversal, skipping",{path:x.path});continue}const S=a.join(g,d),P=a.dirname(S);i.existsSync(P)||await i.promises.mkdir(P,{recursive:!0}),await i.promises.writeFile(S,x.content,"utf-8"),await i.promises.chmod(S,493),o(c,"INFO","Written hook script",{path:x.path})}}}catch(u){o(c,"WARN","Failed to write .claude/settings or .mcp.json or hook scripts, not blocking workspace creation",{error:u.message})}}async function fe(e,t,s,r,n,c,w,y){const p=Date.now(),l=`computer:${e}:${t}`,g=te(r),u=[],m=[],x=new Set;if(!e)throw new h("userId cannot be empty",{field:"userId"});if(!t)throw new h("cId cannot be empty",{field:"cId"});const d=await W(l),S=a.join(d,String(e),String(t),".tmp"),P=a.join(d,String(e),String(t)),{skillsDir:k,agentsDir:R,agentTypes:O}=await ee(P);i.existsSync(P)||await i.promises.mkdir(P,{recursive:!0});const j=a.join(S,`preserved_skills_${Date.now()}_${Math.round(Math.random()*1e6)}`);if(i.existsSync(k)){const N=(await i.promises.readdir(k,{withFileTypes:!0})).filter(E=>E.isDirectory()&&de(a.join(k,E.name)));if(N.length>0){await i.promises.mkdir(j,{recursive:!0});for(const E of N){const D=a.join(k,E.name),z=a.join(j,E.name);await C(D,z)}o(l,"INFO","\u4FDD\u7559\u542B .dynamic_add.lock \u7684 skill",{preserved:N.map(E=>E.name)})}}if(await T(k),await T(R),await i.promises.mkdir(k,{recursive:!0}),await i.promises.mkdir(R,{recursive:!0}),i.existsSync(j)){const f=await i.promises.readdir(j,{withFileTypes:!0});for(const N of f)if(N.isDirectory()){const E=a.join(j,N.name),D=a.join(k,N.name);await C(E,D)}await T(j)}const v=i.existsSync(k),b=i.existsSync(R);o(l,"INFO","Deleted old skills and agents directories completed",{userId:e,cId:t,targetSkillsDir:k,targetAgentsDir:R,agentTypes:O,skillsExists:v,agentsExists:b});const A=a.join(P,".mcp.json");i.existsSync(A)&&await i.promises.unlink(A);const B=a.join(P,".claude","settings.json");if(i.existsSync(B)&&await i.promises.unlink(B),await me(P,n,w,c,y,l),!s&&g.length===0)return await Y(P),o(l,"INFO","Created workspace (no uploaded file, no skills and agents)",{userId:e,cId:t,workspaceRoot:d,skillsDir:null,agentsDir:null,agentTypes:O,elapsedMs:Date.now()-p}),{message:"Workspace created (no uploaded file, no skills and agents)",workspaceRoot:d};if(s){if(!s.path)throw new h("Uploaded file has no valid path",{field:"file.path"});if(a.extname(s.originalname||s.filename||"").toLowerCase()!==".zip")throw new h("Only zip files are supported",{field:"file",originalName:s.originalname})}o(l,"DEBUG","Start processing workspace resources",{userId:e,cId:t,workspaceRoot:d,hasUploadedZip:!!s,skillUrlsCount:g.length});try{i.existsSync(S)||await i.promises.mkdir(S,{recursive:!0});const f=[];if(s){const D=a.join(S,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);m.push(D),await i.promises.mkdir(D,{recursive:!0}),o(l,"DEBUG","Start extracting uploaded zip file",{extractRoot:D}),await q(s.path,D),o(l,"DEBUG","Uploaded zip file extracted successfully",{extractRoot:D});const z=await V(D,"skills"),M=await V(D,"agents");if(z){await i.promises.mkdir(k,{recursive:!0});const $=await i.promises.readdir(z,{withFileTypes:!0});for(const Z of $){if(!Z.isDirectory())continue;const J=a.join(z,Z.name),K=a.join(k,Z.name);i.existsSync(K)&&await T(K),await C(J,K),x.add(Z.name)}f.push("skills"),o(l,"INFO","skills updated to workspace",{userId:e,cId:t,workspaceRoot:d,targetSkillsDir:k,agentTypes:O})}else o(l,"INFO","skills directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:D});M?(await T(R),await C(M,R),f.push("agents"),o(l,"INFO","agents updated to workspace",{userId:e,cId:t,workspaceRoot:d,targetAgentsDir:R,agentTypes:O})):o(l,"INFO","agents directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:D})}for(let D=0;D<g.length;D+=1){const z=g[D],M=a.join(S,`skill_url_${Date.now()}_${D}_${Math.round(Math.random()*1e6)}.zip`);u.push(M);const $=a.join(S,`skill_url_extract_${Date.now()}_${D}_${Math.round(Math.random()*1e6)}`);m.push($),await i.promises.mkdir($,{recursive:!0}),o(l,"INFO","Start download skill zip from url",{userId:e,cId:t,skillUrl:z}),await ie(z,M,l),o(l,"INFO","Skill zip downloaded, start extracting",{userId:e,cId:t,skillUrl:z,downloadedZipPath:M}),await q(M,$);const J=(await i.promises.readdir($,{withFileTypes:!0})).filter(F=>F.isDirectory()&&!F.name.startsWith(".")),I=J.find(F=>F.name==="skills")?(await i.promises.readdir(a.join($,"skills"),{withFileTypes:!0})).filter(F=>F.isDirectory()&&!F.name.startsWith(".")).map(F=>({name:F.name,sourcePath:a.join($,"skills",F.name)})):J.map(F=>({name:F.name,sourcePath:a.join($,F.name)}));if(I.length===0){o(l,"WARN","No skill directory found after extracting skill url zip",{userId:e,cId:t,skillUrl:z,extractRoot:$});continue}await i.promises.mkdir(k,{recursive:!0});for(const F of I){const H=a.join(k,F.name);i.existsSync(H)&&await T(H),await C(F.sourcePath,H),x.add(F.name),o(l,"INFO","Skill from url updated to workspace",{userId:e,cId:t,skillUrl:z,skillName:F.name,destSkillPath:H})}f.includes("skills")||f.push("skills")}f.length===0&&o(l,"WARN","No valid skills or agents directories found",{userId:e,cId:t,hasUploadedZip:!!s,skillUrlsCount:g.length});const N=Array.from(x),E=f.length>0?`Workspace created successfully, ${f.join(" and ")} updated`:"Workspace created successfully (skills and agents directories not found)";return o(l,"INFO",E,{userId:e,cId:t,updatedDirs:f,updatedSkills:N,agentTypes:O,elapsedMs:Date.now()-p}),await Y(P),{message:E,workspaceRoot:d,updatedSkills:N}}catch(f){throw o(l,"ERROR","Failed to process workspace resources",{userId:e,cId:t,error:f.message,elapsedMs:Date.now()-p}),f instanceof h||f instanceof G||f instanceof _?f:new _(`Failed to create workspace: ${f.message}`,{userId:e,cId:t})}finally{try{for(const f of m)i.existsSync(f)&&await i.promises.rm(f,{recursive:!0,force:!0})}catch(f){o(l,"WARN","Failed to clean up temporary extracted zip",{error:f.message})}for(const f of u)try{i.existsSync(f)&&await i.promises.unlink(f)}catch(N){o(l,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:f,error:N.message})}try{s&&s.path&&i.existsSync(s.path)&&await i.promises.unlink(s.path)}catch(f){o(l,"WARN","Failed to clean up uploaded zip file",{tempZipPath:s?.path,error:f.message})}}}async function ue(e,t,s,r){const n=Date.now(),c=`computer:${e}:${t}`,w=te(r),y=[],p=[];if(!e)throw new h("userId cannot be empty",{field:"userId"});if(!t)throw new h("cId cannot be empty",{field:"cId"});if(!s&&w.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(a.extname(s.originalname||s.filename||"").toLowerCase()!==".zip")throw new h("Only zip files are supported",{field:"file",originalName:s?.originalname})}const l=await W(c),g=a.join(l,String(e),String(t),".tmp"),u=a.join(l,String(e),String(t)),{skillsDir:m,agentTypes:x}=await ee(u);try{i.existsSync(u)||await i.promises.mkdir(u,{recursive:!0}),i.existsSync(m)||await i.promises.mkdir(m,{recursive:!0}),i.existsSync(g)||await i.promises.mkdir(g,{recursive:!0});const d=[],S=new Set;if(s){const k=a.join(g,`skill_push_${Date.now()}_${Math.round(Math.random()*1e6)}`);p.push(k),await i.promises.mkdir(k,{recursive:!0}),o(c,"DEBUG","Start extracting skill zip file",{extractRoot:k}),await q(s.path,k),o(c,"DEBUG","Skill zip file extracted successfully",{extractRoot:k});const R=await V(k,"skills");if(!R)o(c,"WARN","skills directory not found in uploaded zip",{userId:e,cId:t,extractRoot:k});else{const j=(await i.promises.readdir(R,{withFileTypes:!0})).filter(v=>v.isDirectory()&&!v.name.startsWith("."));if(j.length===0)o(c,"WARN","skills directory in uploaded zip has no skill subdirectories",{userId:e,cId:t,skillsDir:R});else for(const v of j){const b=a.join(R,v.name),A=a.join(m,v.name);i.existsSync(A)&&await T(A),await C(b,A),S.add(v.name),o(c,"INFO","skill pushed to workspace from uploaded zip",{userId:e,cId:t,skillName:v.name,destSkillPath:A})}}}for(let k=0;k<w.length;k+=1){const R=w[k],O=a.join(g,`skill_push_url_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}.zip`);y.push(O);const j=a.join(g,`skill_push_url_extract_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}`);p.push(j),await i.promises.mkdir(j,{recursive:!0}),o(c,"INFO","Start download skill zip for push from url",{userId:e,cId:t,skillUrl:R}),await ie(R,O,c),await q(O,j);const b=(await i.promises.readdir(j,{withFileTypes:!0})).filter(f=>f.isDirectory()&&!f.name.startsWith(".")),B=b.find(f=>f.name==="skills")?(await i.promises.readdir(a.join(j,"skills"),{withFileTypes:!0})).filter(f=>f.isDirectory()&&!f.name.startsWith(".")).map(f=>({name:f.name,sourcePath:a.join(j,"skills",f.name)})):b.map(f=>({name:f.name,sourcePath:a.join(j,f.name)}));if(B.length===0){o(c,"WARN","No skill directory found after extracting push skill url zip",{userId:e,cId:t,skillUrl:R,extractRoot:j});continue}for(const f of B){const N=a.join(m,f.name);i.existsSync(N)&&await T(N),await C(f.sourcePath,N),S.add(f.name),o(c,"INFO","skill pushed to workspace from url zip",{userId:e,cId:t,skillUrl:R,skillName:f.name,destSkillPath:N})}}for(const k of S)d.push(k);const P=d.length>0?`Pushed ${d.length} skills: ${d.join(", ")}`:"No valid skill directories found in file or skillUrls";return o(c,"INFO",P,{userId:e,cId:t,updatedSkills:d,agentTypes:x,elapsedMs:Date.now()-n}),await Y(u),{message:P,workspaceRoot:l,updatedSkills:d}}catch(d){throw o(c,"ERROR","Failed to push skill to workspace",{userId:e,cId:t,error:d.message,elapsedMs:Date.now()-n}),d instanceof h||d instanceof G||d instanceof _?d:new _(`Failed to push skill to workspace: ${d.message}`,{userId:e,cId:t})}finally{try{for(const d of p)i.existsSync(d)&&await i.promises.rm(d,{recursive:!0,force:!0})}catch(d){o(c,"WARN","Failed to clean up temporary extracted zip",{error:d.message})}for(const d of y)try{i.existsSync(d)&&await i.promises.unlink(d)}catch(S){o(c,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:d,error:S.message})}try{s&&s.path&&i.existsSync(s.path)&&await i.promises.unlink(s.path)}catch(d){o(c,"WARN","Failed to clean up uploaded zip file",{tempZipPath:s?.path,error:d.message})}}}async function we(e,t,s,r){const n=Date.now(),c=`computer:${e}:${t}`;if(!e)throw new h("userId cannot be empty",{field:"userId"});if(!t)throw new h("cId cannot be empty",{field:"cId"});if(!s||!s.path)throw new h("file is required",{field:"file"});if(a.extname(s.originalname||s.filename||"").toLowerCase()!==".zip")throw new h("Only zip files are supported",{field:"file",originalName:s.originalname});const y=await W(c),p=a.join(y,String(e),String(t)),l=a.join(p,".tmp"),g=a.join(l,`template_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{i.existsSync(p)||await i.promises.mkdir(p,{recursive:!0}),i.existsSync(l)||await i.promises.mkdir(l,{recursive:!0}),await i.promises.mkdir(g,{recursive:!0}),o(c,"DEBUG","Start extracting template zip file",{extractRoot:g}),await q(s.path,g),o(c,"DEBUG","Template zip file extracted successfully",{extractRoot:g});const u=await i.promises.readdir(g,{withFileTypes:!0});for(const m of u){const x=a.join(g,m.name),d=a.join(p,m.name);if(m.isDirectory())await C(x,d);else{const S=a.dirname(d);i.existsSync(S)||await i.promises.mkdir(S,{recursive:!0}),await i.promises.copyFile(x,d)}}if(o(c,"INFO","Template files extracted to workspace",{userId:e,cId:t,targetDir:p,fileCount:u.length}),U.GIT_ENABLED&&(r==="true"||r===!0)){const m=await import("../../service/gitService.js");await m.default.init({workspaceType:"taskAgent",userId:e,cId:t}),await m.default.commit({workspaceType:"taskAgent",userId:e,cId:t,message:"Initial commit"}),o(c,"INFO","Git init and initial commit done",{userId:e,cId:t})}return o(c,"INFO","Init project template completed",{userId:e,cId:t,targetDir:p,elapsedMs:Date.now()-n}),{message:"Project template initialized successfully",workspaceRoot:p}}catch(u){throw o(c,"ERROR","Failed to init project template",{userId:e,cId:t,error:u.message,elapsedMs:Date.now()-n}),u instanceof h||u instanceof G||u instanceof _?u:new _(`Failed to init project template: ${u.message}`,{userId:e,cId:t})}finally{try{i.existsSync(g)&&await i.promises.rm(g,{recursive:!0,force:!0})}catch(u){o(c,"WARN","Failed to clean up temporary extracted zip",{error:u.message})}try{s&&s.path&&i.existsSync(s.path)&&await i.promises.unlink(s.path)}catch(u){o(c,"WARN","Failed to clean up uploaded zip file",{tempZipPath:s?.path,error:u.message})}}}async function he(e,t,s){if(!e)throw new h("userId cannot be empty",{field:"userId"});if(!t)throw new h("cId cannot be empty",{field:"cId"});if(!s||typeof s!="string"||!s.trim())throw new h("command cannot be empty",{field:"command"});const r=await W("computer"),n=a.join(r,String(e),String(t));if(!i.existsSync(n))throw new h("workspace directory does not exist",{field:"workDir",workDir:n});const c=`computer:${e}:${t}`;o(c,"INFO","Execute command in workspace",{userId:e,cId:t,workDir:n,command:s});const w=10*60*1e3,y={cwd:n,timeout:w,maxBuffer:50*1024*1024};return U.BASH_PATH&&(y.shell=U.BASH_PATH),new Promise((p,l)=>{oe(s,y,(g,u,m)=>{const x=g?g.killed?-1:g.code||1:0,d=g&&g.killed?(m||"")+`
|
|
2
|
+
Command timed out after ${w/1e3}s`:m||"";o(c,"INFO","Execute command completed",{userId:e,cId:t,exitCode:x,killed:g?g.killed:!1,stdoutLength:u?u.length:0,stderrLength:d.length}),p({stdout:u||"",stderr:d,exitCode:x})})})}async function ge(e,t,s){const r=`computer:${e}:${t}`;if(!e)throw new h("userId cannot be empty",{field:"userId"});if(!t)throw new h("cId cannot be empty",{field:"cId"});const n=await W(r),c=a.join(n,String(e),String(t));if(!i.existsSync(c))throw new h("workspace directory does not exist",{field:"workDir",workDir:c});const w=U.ZIP_WORKSPACE_EXCLUDE_DIRS||[],y=Array.isArray(s)?s:[],p=new Set([...w,...y]),l=`${e}_${t}.zip`,g=Date.now();o(r,"INFO","Zip workspace request",{userId:e,cId:t,targetDir:c,excludeDirs:[...p]});const u=ce("zip",{zlib:{level:9}});return u.directory(c,!1,m=>{const x=m.name||"";if(x.split(/[\/\\]/).filter(Boolean).some(S=>p.has(S)))return!1;try{const S=a.join(c,x);if(i.lstatSync(S).isSymbolicLink())return!1}catch{return!1}return m}),u.on("warning",m=>{m.code==="ENOENT"?o(r,"WARN","Encountered file problem when creating zip",{message:m.message,code:m.code}):o(r,"ERROR","Encountered warning when creating zip",{message:m.message,code:m.code})}),u.on("error",m=>{o(r,"ERROR","Failed to create zip",{message:m.message,elapsedMs:Date.now()-g})}),u.on("end",()=>{o(r,"INFO","Workspace zip created successfully",{targetDir:c,zipFileName:l,elapsedMs:Date.now()-g})}),{archive:u,zipFileName:l}}const Q=new Set([...U.ZIP_WORKSPACE_EXCLUDE_DIRS||[],"dist-packages"]);function X(e){let t;try{t=i.readdirSync(e,{withFileTypes:!0})}catch{return null}const s=a.join(e,"scripts","package-platforms.mjs");if(i.existsSync(s))return e;for(const r of t)if(r.isDirectory()&&!Q.has(r.name)){const n=X(a.join(e,r.name));if(n)return n}return null}function L(e,t,s,r=10*60*1e3){return new Promise(n=>{const c=ae(e,t,{...s,timeout:r});let w="",y="";c.stdout?.on("data",p=>{w+=p.toString()}),c.stderr?.on("data",p=>{y+=p.toString()}),c.on("error",p=>{n({stdout:w,stderr:y+`
|
|
3
|
+
Failed to spawn ${e}: ${p.message}`,exitCode:1})}),c.on("close",(p,l)=>{n(p===null&&l?{stdout:w,stderr:y+`
|
|
4
|
+
Process killed by signal ${l} (timeout after ${r/1e3}s)`,exitCode:-1}:{stdout:w,stderr:y,exitCode:p??1})})})}function ke(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 s=t.split("-");if(s.length<4)return null;let r=-1;for(let n=s.length-1;n>=2;n--)if(/^\d+\.\d+\.\d+$/.test(s[n])){r=n;break}return r<3?null:s.slice(2,r).join("-")}async function ye(e,t,s,r){const n=`computer:${e}:${t}`;if(!e)throw new h("userId cannot be empty",{field:"userId"});if(!t)throw new h("cId cannot be empty",{field:"cId"});if(!s)throw new h("agentId cannot be empty",{field:"agentId"});if(!r)throw new h("version cannot be empty",{field:"version"});const c=await W(n),w=a.join(c,String(e),String(t));if(!i.existsSync(w))throw new h("workspace directory does not exist",{field:"workDir",workDir:w});o(n,"INFO","Build agent package request",{userId:e,cId:t,agentId:s,version:r});const y=X(w);if(!y)throw new h("package-platforms.mjs not found in workspace",{field:"script"});o(n,"INFO","Found package script",{projectDir:y});const p={...process.env,CI:"true"},l=await L("pnpm",["install"],{cwd:y,env:p});if(o(n,"INFO","pnpm install completed",{exitCode:l.exitCode,stderrLength:l.stderr.length}),l.exitCode!==0)throw o(n,"ERROR","pnpm install failed",{stderr:l.stderr}),new _(`pnpm install failed: ${l.stderr}`);const g=a.join(y,"dist-packages"),u=["scripts/package-platforms.mjs",`agent-${s}`,r,g,"--print-artifacts"],m=await L("node",u,{cwd:y,env:p});if(o(n,"INFO","Build completed",{exitCode:m.exitCode,stdoutLength:m.stdout.length}),m.exitCode!==0)throw o(n,"ERROR","Build failed",{stderr:m.stderr}),new _(`Build failed: ${m.stderr}`);const x=[".tar.gz",".tar.bz2",".zip",".tgz"],d=[],S=m.stdout.split(`
|
|
5
|
+
`);for(const P of S){const k=P.trim();if(!k)continue;const R=k.toLowerCase();if(!x.some(A=>R.endsWith(A)))continue;const O=a.resolve(y,k);let j=a.relative(w,O);j=j.split(a.sep).join("/");const v=a.basename(k),b=ke(v);d.push({path:j,fileName:v,platform:b||""})}return o(n,"INFO","Build agent package completed",{artifactsCount:d.length,artifacts:d}),{artifacts:d}}async function xe(e,t){const s=`computer:${e}:${t}`;if(!e)throw new h("userId cannot be empty",{field:"userId"});if(!t)throw new h("cId cannot be empty",{field:"cId"});const r=await W(s),n=a.join(r,String(e),String(t));if(!i.existsSync(n))return o(s,"WARN","Workspace not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const c=X(n);if(!c)return o(s,"WARN","package-platforms.mjs not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const w=a.join(c,"dist-packages");return i.existsSync(w)?(i.rmSync(w,{recursive:!0,force:!0}),o(s,"INFO","Cleaned dist-packages",{distPackagesDir:w}),{cleaned:!0}):(o(s,"INFO","dist-packages not found, nothing to clean",{distPackagesDir:w}),{cleaned:!1})}async function Se(e,t){const s=`computer:${e}:${t}`;if(!e)throw new h("userId cannot be empty",{field:"userId"});if(!t)throw new h("cId cannot be empty",{field:"cId"});const r=await W(s),n=a.join(r,String(e),String(t));return i.existsSync(n)?(await i.promises.rm(n,{recursive:!0,force:!0}),o(s,"INFO","Workspace deleted",{userId:e,cId:t,targetDir:n})):o(s,"WARN","Workspace not found, skip delete",{userId:e,cId:t,targetDir:n}),{deleted:!0}}function se(e){const t=a.join(e,"package.json");if(i.existsSync(t))return e;let s;try{s=i.readdirSync(e,{withFileTypes:!0})}catch{return null}for(const r of s)if(r.isDirectory()&&!Q.has(r.name)&&r.name!=="node_modules"){const n=se(a.join(e,r.name));if(n)return n}return null}async function je(e,t){await le(e,t);const s=a.join(e,".npmrc");let r="";i.existsSync(s)&&(r=await i.promises.readFile(s,"utf8"));const n=[];if(/dangerously-allow-all-builds\s*=/.test(r)||n.push("dangerously-allow-all-builds=true"),n.length>0){const c=r&&!r.endsWith(`
|
|
6
|
+
`)?`
|
|
7
|
+
`:"";await i.promises.writeFile(s,`${r}${c}${n.join(`
|
|
8
|
+
`)}
|
|
9
|
+
`,"utf8"),o(t,"INFO","Updated .npmrc for pnpm install",{projectDir:e,additions:n})}}function ne(){const e={...process.env};return delete e.CI,e.NODE_ENV="development",e}async function Re(e,t){await je(e,t);const s=ne();let r=await L("pnpm",["install","--prefer-offline"],{cwd:e,env:s});if(`${r.stdout}
|
|
10
|
+
${r.stderr}`.includes("Ignored build scripts")){o(t,"WARN","Detected ignored pnpm build scripts, approving and reinstalling",{projectDir:e});const c=await L("pnpm",["approve-builds","--all"],{cwd:e,env:s});o(t,"INFO","pnpm approve-builds completed",{exitCode:c.exitCode,stderrLength:c.stderr.length}),r=await L("pnpm",["install","--prefer-offline"],{cwd:e,env:s})}return r}function re(e){const t=a.join(e,"pyproject.toml"),s=a.join(e,"requirements.txt");if(i.existsSync(t)||i.existsSync(s))return e;let r;try{r=i.readdirSync(e,{withFileTypes:!0})}catch{return null}for(const n of r)if(n.isDirectory()&&!Q.has(n.name)&&n.name!==".venv"&&n.name!=="venv"&&n.name!=="__pycache__"){const c=re(a.join(e,n.name));if(c)return c}return null}async function De(e,t,s){const r=`computer:${e}:${t}`,n=String(s||"").trim().toLowerCase();if(!e)throw new h("userId cannot be empty",{field:"userId"});if(!t)throw new h("cId cannot be empty",{field:"cId"});if(!n)throw new h("programmingLanguage cannot be empty",{field:"programmingLanguage"});const c=await W(r),w=a.join(c,String(e),String(t));if(!i.existsSync(w))throw new h("workspace directory does not exist",{field:"workDir",workDir:w});o(r,"INFO","Install project dependencies request",{userId:e,cId:t,programmingLanguage:n});const y=ne();let p=null,l=null;if(n==="typescript"||n==="ts"){if(p=X(w)||se(w),!p)throw new h("TypeScript project not found in workspace",{field:"projectDir"});l=await Re(p,r)}else if(n==="python"||n==="py"){if(p=re(w),!p)throw new h("Python project not found in workspace",{field:"projectDir"});const g=a.join(p,"pyproject.toml"),u=a.join(p,"requirements.txt");if(i.existsSync(g))l=await L("pip",["install","-e","."],{cwd:p,env:y});else if(i.existsSync(u))l=await L("pip",["install","-r","requirements.txt"],{cwd:p,env:y});else throw new h("No pyproject.toml or requirements.txt found in workspace",{field:"projectDir"})}else throw new h("Unsupported programmingLanguage",{field:"programmingLanguage",programmingLanguage:n});if(o(r,"INFO","Project dependencies install completed",{projectDir:p,programmingLanguage:n,exitCode:l.exitCode,stderrLength:l.stderr.length}),l.exitCode!==0)throw o(r,"ERROR","Project dependencies install failed",{projectDir:p,programmingLanguage:n,stderr:l.stderr}),new _(`Project dependencies install failed: ${l.stderr||l.stdout}`);return{message:"Project dependencies installed successfully",projectDir:p,programmingLanguage:n}}export{fe as createWorkspace,ue as pushSkillsToWorkspace,we as initProjectTemplate,he as executeCommand,Se as deleteWorkspace,ge as zipWorkspace,ye as buildAgentPackage,xe as cleanupBuildArtifacts,De as installProjectDependencies};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuwax-file-server",
|
|
3
3
|
"displayName": "nuwax-file-server",
|
|
4
|
-
"version": "1.3.0-beta.
|
|
4
|
+
"version": "1.3.0-beta.23",
|
|
5
5
|
"description": "Cross-platform file service deployment tool with start/stop/restart CLI commands",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "index.js",
|