nuwax-file-server 1.3.0-beta.9 → 1.3.1
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/appConfig/index.js +1 -1
- package/dist/cli.js +1 -1
- package/dist/env.development +14 -1
- package/dist/env.production +14 -1
- package/dist/routes/codeRoutes.js +1 -1
- package/dist/routes/computerRoutes.js +1 -1
- package/dist/routes/gitRoutes.js +1 -0
- package/dist/routes/projectRoutes.js +1 -1
- package/dist/routes/router.js +1 -1
- package/dist/server.js +1 -1
- package/dist/service/codeService.js +2 -2
- package/dist/service/gitService.js +8 -0
- package/dist/service/projectService.js +1 -1
- package/dist/utils/build/restartDevUtils.js +1 -1
- package/dist/utils/build/startDevUtils.js +1 -1
- package/dist/utils/computer/computerFileUtils.js +2 -1
- package/dist/utils/computer/computerUtils.js +14 -1
- package/dist/utils/git/gitUtils.js +6 -0
- package/dist/utils/project/copyProjectUtils.js +1 -1
- package/dist/utils/project/getContentUtils.js +1 -1
- package/package.json +3 -1
package/dist/appConfig/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import E from"path";import n from"fs";import I from"os";import{fileURLToPath as T}from"url";import o from"dotenv";const L=E.dirname(T(import.meta.url));o.config();function p(){const e=process.env.LOG_BASE_DIR,_=E.join(I.tmpdir(),"nuwax-file-server","project_logs");if(!e)return _;try{return n.mkdirSync(e,{recursive:!0}),e}catch{try{n.mkdirSync(_,{recursive:!0})}catch{}return _}}const r=process.env.NODE_ENV||"development";function O(e){const _=E.join(L,"..",`env.${e}`);if(n.existsSync(_))o.config({path:_}),console.log(`Environment configuration file env.${e} loaded`);else{const s=`Environment configuration file env.${e} does not exist, please create the corresponding environment configuration file and try again`;throw console.error(s),new Error(s)}}O(r);const R={NODE_ENV:r,PORT:parseInt(process.env.PORT),DEPLOYMENT_MODE:process.env.DEPLOYMENT_MODE||"docker-compose",INIT_PROJECT_NAME_REACT:process.env.INIT_PROJECT_NAME_REACT||"react-vite-template",INIT_PROJECT_NAME_VUE3:process.env.INIT_PROJECT_NAME_VUE3||"vue3-vite-template",INIT_PROJECT_DIR:process.env.INIT_PROJECT_DIR,PROJECT_SOURCE_DIR:process.env.PROJECT_SOURCE_DIR,DIST_TARGET_DIR:process.env.DIST_TARGET_DIR,UPLOAD_PROJECT_DIR:process.env.UPLOAD_PROJECT_DIR,MAX_BUILD_CONCURRENCY:process.env.MAX_BUILD_CONCURRENCY?parseInt(process.env.MAX_BUILD_CONCURRENCY,10):void 0,MAX_INLINE_FILE_SIZE_BYTES:process.env.MAX_INLINE_FILE_SIZE_BYTES?parseInt(process.env.MAX_INLINE_FILE_SIZE_BYTES,10):void 0,UPLOAD_MAX_FILE_SIZE_BYTES:process.env.UPLOAD_MAX_FILE_SIZE_BYTES?parseInt(process.env.UPLOAD_MAX_FILE_SIZE_BYTES,10):void 0,UPLOAD_ALLOWED_EXTENSIONS:process.env.UPLOAD_ALLOWED_EXTENSIONS?process.env.UPLOAD_ALLOWED_EXTENSIONS.split(",").map(e=>e.trim().toLowerCase()).filter(Boolean):[],UPLOAD_SINGLE_FILE_SIZE_BYTES:process.env.UPLOAD_SINGLE_FILE_SIZE_BYTES?parseInt(process.env.UPLOAD_SINGLE_FILE_SIZE_BYTES,10):void 0,DOWNLOAD_MAX_FILE_SIZE_BYTES:process.env.DOWNLOAD_MAX_FILE_SIZE_BYTES?parseInt(process.env.DOWNLOAD_MAX_FILE_SIZE_BYTES,10):void 0,REQUEST_BODY_LIMIT:process.env.REQUEST_BODY_LIMIT,TRAVERSE_EXCLUDE_DIRS:process.env.TRAVERSE_EXCLUDE_DIRS?process.env.TRAVERSE_EXCLUDE_DIRS.split(",").map(e=>e.trim()).filter(Boolean):[],BACKUP_TRAVERSE_EXCLUDE_FILES:process.env.BACKUP_TRAVERSE_EXCLUDE_FILES?process.env.BACKUP_TRAVERSE_EXCLUDE_FILES.split(",").map(e=>e.trim()).filter(Boolean):[],CONTENT_TRAVERSE_EXCLUDE_FILES:process.env.CONTENT_TRAVERSE_EXCLUDE_FILES?process.env.CONTENT_TRAVERSE_EXCLUDE_FILES.split(",").map(e=>e.trim()).filter(Boolean):[],INLINE_IMAGE_EXTENSIONS:process.env.INLINE_IMAGE_EXTENSIONS?process.env.INLINE_IMAGE_EXTENSIONS.split(",").map(e=>e.trim().toLowerCase()).filter(Boolean):[],TOP_LEVEL_NOISE_PATTERNS:process.env.TOP_LEVEL_NOISE_PATTERNS?process.env.TOP_LEVEL_NOISE_PATTERNS.split(",").map(e=>e.trim()).filter(Boolean):[],LOG_BASE_DIR:p(),LOG_LEVEL:process.env.LOG_LEVEL?process.env.LOG_LEVEL.toLowerCase():void 0,LOG_PREFIX_API:process.env.LOG_PREFIX_API,LOG_PREFIX_BUILD:process.env.LOG_PREFIX_BUILD,LOG_CONSOLE_ENABLED:typeof process.env.LOG_CONSOLE_ENABLED=="string"?process.env.LOG_CONSOLE_ENABLED.toLowerCase()==="true":void 0,LOG_CACHE_ENABLED:typeof process.env.LOG_CACHE_ENABLED=="string"?process.env.LOG_CACHE_ENABLED.toLowerCase()==="true":void 0,LOG_CACHE_DURATION:process.env.LOG_CACHE_DURATION?parseInt(process.env.LOG_CACHE_DURATION,10):void 0,LOG_CACHE_MAX_ENTRIES:process.env.LOG_CACHE_MAX_ENTRIES?parseInt(process.env.LOG_CACHE_MAX_ENTRIES,10):void 0,LOG_CACHE_MAX_FILE_SIZE:process.env.LOG_CACHE_MAX_FILE_SIZE?parseInt(process.env.LOG_CACHE_MAX_FILE_SIZE,10):void 0,DEV_SERVER_PORT_TIMEOUT:process.env.DEV_SERVER_PORT_TIMEOUT?parseInt(process.env.DEV_SERVER_PORT_TIMEOUT,10):void 0,DEV_SERVER_STOP_TIMEOUT:process.env.DEV_SERVER_STOP_TIMEOUT?parseInt(process.env.DEV_SERVER_STOP_TIMEOUT,10):void 0,DEV_SERVER_STOP_CHECK_INTERVAL:process.env.DEV_SERVER_STOP_CHECK_INTERVAL?parseInt(process.env.DEV_SERVER_STOP_CHECK_INTERVAL,10):void 0,DEV_SERVER_STOP_MAX_ATTEMPTS:process.env.DEV_SERVER_STOP_MAX_ATTEMPTS?parseInt(process.env.DEV_SERVER_STOP_MAX_ATTEMPTS,10):void 0,COMPUTER_WORKSPACE_DIR:process.env.COMPUTER_WORKSPACE_DIR,COMPUTER_LOG_DIR:process.env.COMPUTER_LOG_DIR,CLI_SERVICE_NAME:"nuwax-file-server",CLI_PID_DIR:process.env.CLI_PID_DIR||(process.platform==="win32"?E.join(process.env.TEMP||"","nuwax-file-server"):E.join("/tmp","nuwax-file-server")),CLI_PID_FILE:"server.pid",CLI_STOP_TIMEOUT:process.env.CLI_STOP_TIMEOUT?parseInt(process.env.CLI_STOP_TIMEOUT,10):3e4,CLI_CHECK_INTERVAL:process.env.CLI_CHECK_INTERVAL?parseInt(process.env.CLI_CHECK_INTERVAL,10):500,CLI_LOG_DIR:process.env.CLI_LOG_DIR||(process.platform==="win32"?E.join(process.env.TEMP||"","nuwax-file-server","logs"):E.join("/tmp","nuwax-file-server","logs")),CLI_IS_WINDOWS:process.platform==="win32",GIT_DEFAULT_AUTHOR_NAME:process.env.GIT_DEFAULT_AUTHOR_NAME||"Nuwax File Server",GIT_DEFAULT_AUTHOR_EMAIL:process.env.GIT_DEFAULT_AUTHOR_EMAIL||"git@nuwax.local",GIT_AUTO_GITIGNORE:process.env.GIT_AUTO_GITIGNORE?.toLowerCase()!=="false",GIT_GITIGNORE_ENTRIES:process.env.GIT_GITIGNORE_ENTRIES?process.env.GIT_GITIGNORE_ENTRIES.split("|").map(e=>e.trim()).filter(Boolean):["node_modules/",".pnpm-store/","dist/","build/",".idea/",".vscode/",".DS_Store",".npmrc",".agents/",".claude/",".opencode/",".codex/",".tmp/",".logs/","pnpm-lock.yaml","yarn.lock","package-lock.json"],GIT_ENABLED:process.env.GIT_ENABLED?.toLowerCase()==="true",BASH_PATH:process.env.BASH_PATH||"",ZIP_WORKSPACE_EXCLUDE:process.env.ZIP_WORKSPACE_EXCLUDE?process.env.ZIP_WORKSPACE_EXCLUDE.split(",").map(e=>e.trim()).filter(Boolean):[".git",".tmp",".claude",".agents",".codex",".opencode",".logs",".npmrc","__pycache__","node_modules","dist","pnpm-lock.yaml","yarn.lock","package-lock.json"]};export default R;
|
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.9",d=new ee,w={reset:"\x1B[0m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",cyan:"\x1B[36m"};function j(e){console.error(`${w.red}ERROR: ${e}${w.reset}`)}function b(e){console.log(`${w.green}${e}${w.reset}`)}function oe(e){console.log(`${w.blue}${e}${w.reset}`)}function y(e){return async(...t)=>{try{let o=await e(...t);o&&o.success===!1?(j(o.message||"Command failed"),process.exitCode=1):process.exitCode=0}catch(o){j(o?.message||"Command failed with unexpected error"),process.exitCode=1}}}async function se(e){let t=await N({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(`Service started (PID: ${t.pid})`),t}async function re(e){let t=await T({force:e.force,timeout:Number(e.timeout)});return t.success&&b(t.message||"Service stopped"),t}async function ne(e){let t=await O({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(t.message||"Service restarted"),t}function ie(){let e=_();return oe(`${n.name} service status:`),console.log(""),console.log(` Service name: ${n.name}`),console.log(` Running status: ${e.running?"Running":"Stopped"}`),console.log(` Message: ${e.message}`),console.log(` PID file: ${$()}`),e.pidInfo&&(console.log(` Process ID: ${e.pidInfo.pid}`),console.log(` Environment: ${e.pidInfo.env||"Unknown"}`),console.log(` Port: ${e.pidInfo.port||"Unknown"}`),console.log(` Version: ${e.pidInfo.version||A}`),console.log(` Platform: ${e.pidInfo.platform||process.platform}`),console.log(` Started at: ${e.pidInfo.startedAt||"Unknown"}`),console.log(` Uptime: ${L(e.pidInfo.startedAt)}`)),console.log(""),{success:e.running,message:e.message}}function ce(){d.name("nuwax-file-server").description("Cross-platform file service deployment tool, supporting start/stop/restart/status").version(A,"-v, --version","Display version number").helpOption("-h, --help","Display help information"),d.command("start").allowUnknownOption().description("Start service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(se)),d.command("stop").description("Stop service").option("--force","Force stop").option("--timeout <ms>","\u505C\u6B62\u670D\u52A1\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).action(y(re)),d.command("restart").allowUnknownOption().description("Restart service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(ne)),d.command("status").description("View service status").action(y(ie)),d.command("help").description("Display help information").action(()=>{d.outputHelp()}),d.parse(process.argv),process.argv.slice(2).length||(d.outputHelp(),process.exitCode=0)}ce();
|
|
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.1",d=new ee,w={reset:"\x1B[0m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",cyan:"\x1B[36m"};function j(e){console.error(`${w.red}ERROR: ${e}${w.reset}`)}function b(e){console.log(`${w.green}${e}${w.reset}`)}function oe(e){console.log(`${w.blue}${e}${w.reset}`)}function y(e){return async(...t)=>{try{let o=await e(...t);o&&o.success===!1?(j(o.message||"Command failed"),process.exitCode=1):process.exitCode=0}catch(o){j(o?.message||"Command failed with unexpected error"),process.exitCode=1}}}async function se(e){let t=await N({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(`Service started (PID: ${t.pid})`),t}async function re(e){let t=await T({force:e.force,timeout:Number(e.timeout)});return t.success&&b(t.message||"Service stopped"),t}async function ne(e){let t=await O({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(t.message||"Service restarted"),t}function ie(){let e=_();return oe(`${n.name} service status:`),console.log(""),console.log(` Service name: ${n.name}`),console.log(` Running status: ${e.running?"Running":"Stopped"}`),console.log(` Message: ${e.message}`),console.log(` PID file: ${$()}`),e.pidInfo&&(console.log(` Process ID: ${e.pidInfo.pid}`),console.log(` Environment: ${e.pidInfo.env||"Unknown"}`),console.log(` Port: ${e.pidInfo.port||"Unknown"}`),console.log(` Version: ${e.pidInfo.version||A}`),console.log(` Platform: ${e.pidInfo.platform||process.platform}`),console.log(` Started at: ${e.pidInfo.startedAt||"Unknown"}`),console.log(` Uptime: ${L(e.pidInfo.startedAt)}`)),console.log(""),{success:e.running,message:e.message}}function ce(){d.name("nuwax-file-server").description("Cross-platform file service deployment tool, supporting start/stop/restart/status").version(A,"-v, --version","Display version number").helpOption("-h, --help","Display help information"),d.command("start").allowUnknownOption().description("Start service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(se)),d.command("stop").description("Stop service").option("--force","Force stop").option("--timeout <ms>","\u505C\u6B62\u670D\u52A1\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).action(y(re)),d.command("restart").allowUnknownOption().description("Restart service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(ne)),d.command("status").description("View service status").action(y(ie)),d.command("help").description("Display help information").action(()=>{d.outputHelp()}),d.parse(process.argv),process.argv.slice(2).length||(d.outputHelp(),process.exitCode=0)}ce();
|
package/dist/env.development
CHANGED
|
@@ -48,7 +48,7 @@ DOWNLOAD_MAX_FILE_SIZE_BYTES=104857600
|
|
|
48
48
|
REQUEST_BODY_LIMIT=2000mb
|
|
49
49
|
|
|
50
50
|
# 目录遍历排除(返回内容、归档等)
|
|
51
|
-
TRAVERSE_EXCLUDE_DIRS=dist,node_modules,.pnpm-store,__MACOSX,.attachments
|
|
51
|
+
TRAVERSE_EXCLUDE_DIRS=dist,node_modules,.pnpm-store,__MACOSX,.attachments,.git,.agents,.codex,.opencode,.logs
|
|
52
52
|
|
|
53
53
|
# 归档遍历排除文件
|
|
54
54
|
BACKUP_TRAVERSE_EXCLUDE_FILES=pnpm-lock.yaml,yarn.lock,package-lock.json
|
|
@@ -88,3 +88,16 @@ LOG_CACHE_MAX_FILE_SIZE=2097152
|
|
|
88
88
|
COMPUTER_WORKSPACE_DIR=/app/computer-project-workspace
|
|
89
89
|
# computer 日志目录
|
|
90
90
|
COMPUTER_LOG_DIR=/app/logs/computer_logs
|
|
91
|
+
|
|
92
|
+
# Git 版本管理配置
|
|
93
|
+
GIT_DEFAULT_AUTHOR_NAME=Nuwax File Server
|
|
94
|
+
GIT_DEFAULT_AUTHOR_EMAIL=git@nuwax.com
|
|
95
|
+
GIT_AUTO_GITIGNORE=true
|
|
96
|
+
GIT_GITIGNORE_ENTRIES=node_modules/|.pnpm-store/|dist/|dist-packages/|build/|.idea/|.vscode/|.DS_Store|.npmrc|.agents/|.claude/|.opencode/|.codex/|.tmp/|.logs/|pnpm-lock.yaml|yarn.lock|package-lock.json
|
|
97
|
+
GIT_ENABLED=true
|
|
98
|
+
|
|
99
|
+
# Windows 专用路径(Linux/macOS 留空)
|
|
100
|
+
BASH_PATH=
|
|
101
|
+
|
|
102
|
+
# zip-workspace 打包时排除的路径片段(逗号分隔,目录或文件名均可)
|
|
103
|
+
ZIP_WORKSPACE_EXCLUDE=.git,.tmp,.claude,.agents,.codex,.opencode,.logs,.npmrc,__pycache__,node_modules,dist,pnpm-lock.yaml,yarn.lock,package-lock.json
|
package/dist/env.production
CHANGED
|
@@ -48,7 +48,7 @@ DOWNLOAD_MAX_FILE_SIZE_BYTES=104857600
|
|
|
48
48
|
REQUEST_BODY_LIMIT=2000mb
|
|
49
49
|
|
|
50
50
|
# 目录遍历排除(返回内容、归档等)
|
|
51
|
-
TRAVERSE_EXCLUDE_DIRS=dist,node_modules,.pnpm-store,__MACOSX,.attachments
|
|
51
|
+
TRAVERSE_EXCLUDE_DIRS=dist,node_modules,.pnpm-store,__MACOSX,.attachments,.git,.agents,.codex,.opencode,.logs
|
|
52
52
|
|
|
53
53
|
# 归档遍历排除文件
|
|
54
54
|
BACKUP_TRAVERSE_EXCLUDE_FILES=pnpm-lock.yaml,yarn.lock,package-lock.json
|
|
@@ -88,3 +88,16 @@ LOG_CACHE_MAX_FILE_SIZE=2097152
|
|
|
88
88
|
COMPUTER_WORKSPACE_DIR=/app/computer-project-workspace
|
|
89
89
|
# computer 日志目录
|
|
90
90
|
COMPUTER_LOG_DIR=/app/logs/computer_logs
|
|
91
|
+
|
|
92
|
+
# Git 版本管理配置
|
|
93
|
+
GIT_DEFAULT_AUTHOR_NAME=Nuwax File Server
|
|
94
|
+
GIT_DEFAULT_AUTHOR_EMAIL=git@nuwax.com
|
|
95
|
+
GIT_AUTO_GITIGNORE=true
|
|
96
|
+
GIT_GITIGNORE_ENTRIES=node_modules/|.pnpm-store/|dist/|dist-packages/|build/|.idea/|.vscode/|.DS_Store|.npmrc|.agents/|.claude/|.opencode/|.codex/|.tmp/|.logs/|pnpm-lock.yaml|yarn.lock|package-lock.json
|
|
97
|
+
GIT_ENABLED=true
|
|
98
|
+
|
|
99
|
+
# Windows 专用路径(Linux/macOS 留空)
|
|
100
|
+
BASH_PATH=
|
|
101
|
+
|
|
102
|
+
# zip-workspace 打包时排除的路径片段(逗号分隔,目录或文件名均可)
|
|
103
|
+
ZIP_WORKSPACE_EXCLUDE=.git,.tmp,.claude,.agents,.codex,.opencode,.logs,.npmrc,__pycache__,node_modules,dist,pnpm-lock.yaml,yarn.lock,package-lock.json
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import I from"express";import b from"multer";import{asyncHandler as p,ValidationError as r}from"../utils/error/errorHandler.js";import h from"../service/codeService.js";import{log as f}from"../utils/log/logUtils.js";import g from"../appConfig/index.js";import{extractIsolationContext as u}from"../utils/common/projectPathUtils.js";const y=I.Router(),w=b({storage:b.memoryStorage(),limits:{fileSize:g.UPLOAD_SINGLE_FILE_SIZE_BYTES}}),j=[{path:"/specified-files-update",method:"post",handler:p(async(e,a)=>{const{projectId:t,codeVersion:i,files:n}=e.body||{},l=u(e.body||{});f(t,"INFO","Partial files update",{projectId:t,codeVersion:i,filesCount:n?n.length:0}),n&&Array.isArray(n)&&n.forEach(s=>{if(s&&typeof s.contents=="string"&&s.contents)try{s.contents=decodeURIComponent(s.contents)}catch(d){f(t,"WARN","Decode file content failed",{fileName:s.path,error:d.message})}});const o=await h.specifiedFilesUpdate(String(t),String(i),n,e,l);a.status(200).json(o)})},{path:"/all-files-update",method:"post",handler:p(async(e,a)=>{const{projectId:t,codeVersion:i,files:n,basePath:l,pid:o}=e.body||{},s=u(e.body||{});if(f(t,"INFO","Submit files",{projectId:t,codeVersion:i,basePath:l,pid:o}),!t)throw new r("Project ID cannot be empty",{field:"projectId"});if(i==null)throw new r("codeVersion cannot be empty",{field:"codeVersion"});if(!Array.isArray(n))throw new r("files must be an array",{field:"files"});n&&Array.isArray(n)&&n.forEach(c=>{if(c&&typeof c.contents=="string"&&c.contents)try{c.contents=decodeURIComponent(c.contents)}catch(m){f(t,"WARN","Decode file content failed",{fileName:c.name,error:m.message})}});const d=await h.allFilesUpdate(String(t),String(i),n,e,s);a.status(200).json(d)})},{path:"/upload-single-file",method:"post",middleware:w.single("file"),handler:p(async(e,a)=>{const{projectId:t,codeVersion:i,filePath:n}=e.body||{},l=u(e.body||{}),o=e.file;if(f(t,"INFO","\u4E0A\u4F20\u5355\u4E2A\u6587\u4EF6",{projectId:t,codeVersion:i,filePath:n}),!t)throw new r("Project ID cannot be empty",{field:"projectId"});if(i==null)throw new r("codeVersion cannot be empty",{field:"codeVersion"});if(!o)throw new r("File cannot be empty",{field:"file"});if(!n||typeof n!="string")throw new r("File path cannot be empty",{field:"filePath"});f(t,"INFO","Received file information",{originalname:o.originalname,mimetype:o.mimetype,size:o.size,bufferLength:o.buffer?o.buffer.length:0,bufferIsBuffer:Buffer.isBuffer(o.buffer)});const s={buffer:o.buffer,originalname:o.originalname,mimetype:o.mimetype,size:o.size},d=await h.uploadSingleFile(String(t),String(i),s,n,e,l);a.status(200).json(d)})},{path:"/upload-batch-files",method:"post",middleware:w.array("files"),handler:p(async(e,a)=>{const{projectId:t,codeVersion:i,filePaths:n}=e.body||{},l=u(e.body||{}),o=e.files;if(f(t,"INFO","\u6279\u91CF\u4E0A\u4F20\u6587\u4EF6",{projectId:t,codeVersion:i,fileCount:o?o.length:0}),!t)throw new r("Project ID cannot be empty",{field:"projectId"});if(i==null)throw new r("codeVersion cannot be empty",{field:"codeVersion"});if(!o||o.length===0)throw new r("Files cannot be empty",{field:"files"});let s=n;if(typeof s=="string"&&(s=[s]),!s||s.length!==o.length)throw new r("filePaths and files count mismatch",{field:"filePaths",filePathsCount:s?s.length:0,filesCount:o.length});const d=o.map(m=>({buffer:m.buffer,originalname:m.originalname,mimetype:m.mimetype,size:m.size})),c=await h.uploadBatchFiles(String(t),String(i),d,s,e,l);a.status(200).json(c)})},{path:"/rollback-version",method:"post",handler:p(async(e,a)=>{const{projectId:t,codeVersion:i,rollbackTo:n}=e.body||{},l=u(e.body||{});if(f(t,"INFO","Rollback version",{projectId:t,codeVersion:i,rollbackTo:n}),!t)throw new r("Project ID cannot be empty",{field:"projectId"});if(i==null)throw new r("codeVersion cannot be empty",{field:"codeVersion"});if(n==null)throw new r("rollbackTo cannot be empty",{field:"rollbackTo"});if(g.GIT_ENABLED)return a.status(200).json({success:!1,deprecated:!0,message:"\u6B64\u63A5\u53E3\u5DF2\u5E9F\u5F03\uFF0C\u8BF7\u4F7F\u7528 /api/git/rollback \u8FDB\u884C\u7248\u672C\u56DE\u6EDA"});const o=await h.rollbackVersion(String(t),String(i),String(n),e,l);a.status(200).json(o)})}];j.forEach(e=>{e.middleware?y[e.method](e.path,e.middleware,e.handler):y[e.method](e.path,e.handler)});export default y;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import z from"express";import k from"multer";import f from"fs";import w from"path";import{ValidationError as C,asyncHandler as d}from"../utils/error/errorHandler.js";import{log as u}from"../utils/log/logUtils.js";import F from"../appConfig/index.js";import{createWorkspace as N,pushSkillsToWorkspace as S,initProjectTemplate as b,installProjectDependencies as A,executeCommand as j,deleteWorkspace as O,zipWorkspace as D,buildAgentPackage as E,cleanupBuildArtifacts as P}from"../utils/computer/computerUtils.js";import{getFileList as U,updateFiles as T,uploadFile as R,uploadFiles as x,downloadAllFiles as v,getLatestLogs as H}from"../utils/computer/computerFileUtils.js";const g=z.Router(),h=k({storage:k.diskStorage({destination:(t,a,s)=>{try{const e=F.COMPUTER_WORKSPACE_DIR;if(!e)return s(new Error("COMPUTER_WORKSPACE_DIR is not configured, cannot determine upload temporary directory"));const o=t.body?.userId||"unknown",n=t.body?.cId||"unknown",i=w.join(e,String(o),String(n),".tmp");f.existsSync(i)||f.mkdirSync(i,{recursive:!0}),s(null,i)}catch(e){s(e)}},filename:(t,a,s)=>{const e=w.extname(a.originalname)||".zip",o=w.basename(a.originalname,e),n=`${Date.now()}_${Math.round(Math.random()*1e6)}`;s(null,`${o}_${n}${e}`)}}),limits:{fileSize:F.UPLOAD_MAX_FILE_SIZE_BYTES}});function _(t,a,s,e){if(t&&t.name==="MulterError"||t&&(t.name==="ValidationError"||t instanceof C))return e(t);e(t)}const q=[{path:"/create-workspace",method:"post",middleware:h.single("file"),handler:d(async(t,a)=>{const{userId:s,cId:e}=t.body||{},o=t.file||null,n=`computer:${s}:${e}`;u(n,"INFO","Create workspace request",{userId:s,cId:e,hasFile:!!o,fileName:o?.originalname,fileSize:o?.size});const i=await N(s,e,o);a.status(200).json({success:!0,...i})})},{path:"/create-workspace-v2",method:"post",middleware:h.single("file"),handler:d(async(t,a)=>{const{userId:s,cId:e,skillUrls:o,mcpServersConfig:n,hooksConfig:i,permissionsConfig:r,hookScripts:l}=t.body||{},c=t.file||null,y=`computer:${s}:${e}`;let m=o;if(typeof o=="string")try{const $=JSON.parse(o);m=Array.isArray($)?$:[o]}catch{m=[o]}let p=l;if(typeof l=="string")try{p=JSON.parse(l)}catch{p=null}u(y,"INFO","Create workspace v2 request",{userId:s,cId:e,hasFile:!!c,fileName:c?.originalname,fileSize:c?.size,skillUrlsCount:Array.isArray(m)?m.length:0,hasMcpServersConfig:!!n,hasHooksConfig:!!i,hasPermissionsConfig:!!r,hookScriptsCount:Array.isArray(p)?p.length:0});const I=await N(s,e,c,m,n,r,i,p);a.status(200).json({success:!0,...I})})},{path:"/push-skills-to-workspace",method:"post",middleware:h.single("file"),handler:d(async(t,a)=>{const{userId:s,cId:e}=t.body||{},o=t.file||null,n=`computer:${s}:${e}`;u(n,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42",{userId:s,cId:e,hasFile:!!o,fileName:o?.originalname,fileSize:o?.size});const i=await S(s,e,o);a.status(200).json({success:!0,...i})})},{path:"/push-skills-to-workspace-v2",method:"post",middleware:h.single("file"),handler:d(async(t,a)=>{const{userId:s,cId:e,skillUrls:o}=t.body||{},n=t.file||null,i=`computer:${s}:${e}`;let r=o;if(typeof o=="string")try{const c=JSON.parse(o);r=Array.isArray(c)?c:[o]}catch{r=[o]}u(i,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42(v2)",{userId:s,cId:e,hasFile:!!n,fileName:n?.originalname,fileSize:n?.size,skillUrlsCount:Array.isArray(r)?r.length:0});const l=await S(s,e,n,r);a.status(200).json({success:!0,...l})})},{path:"/get-file-list",method:"get",handler:d(async(t,a)=>{const{userId:s,cId:e,proxyPath:o,customTargetDir:n}=t.query,i=await U(s,e,o,n);a.status(200).json({success:!0,...i})})},{path:"/files-update",method:"post",handler:d(async(t,a)=>{const{userId:s,cId:e,files:o,customTargetDir:n}=t.body||{},i=`computer:${s}:${e}`;u(i,"INFO","Files update",{userId:s,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(c){u(i,"WARN","Decode file content failed",{fileName:l.name,error:c.message})}});const r=await T(s,e,o,n);a.status(200).json(r)})},{path:"/upload-file",method:"post",middleware:h.single("file"),handler:d(async(t,a)=>{const{userId:s,cId:e,filePath:o,customTargetDir:n}=t.body||{},i=t.file,r=`computer:${s}:${e}`;u(r,"INFO","Upload single file",{userId:s,cId:e,filePath:o});const c={buffer:await f.promises.readFile(i.path),originalname:i.originalname,mimetype:i.mimetype,size:i.size};try{const y=await R(s,e,c,o,n);a.status(200).json(y)}finally{f.existsSync(i.path)&&await f.promises.unlink(i.path)}})},{path:"/upload-files",method:"post",middleware:h.array("files"),handler:d(async(t,a)=>{const{userId:s,cId:e,filePaths:o,customTargetDir:n}=t.body||{},i=t.files||[],r=`computer:${s}:${e}`,l=Array.isArray(o)?o:typeof o=="string"?[o]:o;u(r,"INFO","Batch upload files request",{userId:s,cId:e,filesCount:i.length,filePathsCount:Array.isArray(l)?l.length:0});const c=[],y=[];try{for(const p of i){y.push(p.path);const I=await f.promises.readFile(p.path);c.push({buffer:I,originalname:p.originalname,mimetype:p.mimetype,size:p.size})}const m=await x(s,e,c,l,n);a.status(200).json(m)}finally{for(const m of y)if(f.existsSync(m))try{await f.promises.unlink(m)}catch(p){u(r,"WARN","Clean temporary file failed",{tempPath:m,error:p.message})}}})},{path:"/install-project",method:"post",handler:d(async(t,a)=>{const{userId:s,cId:e,programmingLanguage:o}=t.body||{},n=`computer:${s}:${e}`;u(n,"INFO","Install project request",{userId:s,cId:e,programmingLanguage:o});const i=await A(s,e,o);a.status(200).json({success:!0,...i})})},{path:"/init-project-template",method:"post",middleware:h.single("file"),handler:d(async(t,a)=>{const{userId:s,cId:e,enableGit:o}=t.body||{},n=t.file||null,i=`computer:${s}:${e}`;u(i,"INFO","Init project template request",{userId:s,cId:e,hasFile:!!n,fileName:n?.originalname,fileSize:n?.size,enableGit:o});const r=await b(s,e,n,o);a.status(200).json({success:!0,...r})})},{path:"/download-all-files",method:"get",handler:d(async(t,a)=>{const{userId:s,cId:e,customTargetDir:o}=t.query||{},n=`computer:${s}:${e}`;u(n,"INFO","Download all files request",{userId:s,cId:e});const{archive:i,zipFileName:r}=await v(s,e,o);a.setHeader("Content-Type","application/zip");const l=encodeURIComponent(r);a.setHeader("Content-Disposition",`attachment; filename="${l}"; filename*=UTF-8''${l}`),i.on("error",c=>{a.destroy(c)}),i.pipe(a),i.finalize()})},{path:"/execute-command",method:"post",handler:d(async(t,a)=>{const{userId:s,cId:e,command:o}=t.body||{},n=`computer:${s}:${e}`;u(n,"INFO","Execute command request",{userId:s,cId:e,command:o});const i=await j(s,e,o);a.status(200).json({success:!0,...i})})},{path:"/delete-workspace",method:"post",handler:d(async(t,a)=>{const{userId:s,cId:e}=t.body||{},o=`computer:${s}:${e}`;u(o,"INFO","Delete workspace request",{userId:s,cId:e});const n=await O(s,e);a.status(200).json({success:!0,...n})})},{path:"/get-logs",method:"get",handler:d(async(t,a)=>{const{userId:s,cId:e,tailLines:o}=t.query,n=o?parseInt(o,10):200,i=Number.isFinite(n)&&n>0?n:200,r=await H(s,e,i);a.status(200).json(r)})},{path:"/zip-workspace",method:"post",handler:d(async(t,a)=>{const{userId:s,cId:e,excludeDirs:o}=t.body||{},n=`computer:${s}:${e}`;u(n,"INFO","Zip workspace request",{userId:s,cId:e,hasExcludeDirs:!!o});const{archive:i,zipFileName:r}=await D(s,e,o||null);a.setHeader("Content-Type","application/zip");const l=encodeURIComponent(r);a.setHeader("Content-Disposition",`attachment; filename="${l}"; filename*=UTF-8''${l}`),i.on("error",c=>{a.destroy(c)}),i.pipe(a),i.finalize()})},{path:"/build-agent-package",method:"post",handler:d(async(t,a)=>{const{userId:s,cId:e,agentId:o,version:n}=t.body||{},i=`computer:${s}:${e}`;u(i,"INFO","Build agent package request",{userId:s,cId:e,agentId:o,version:n});const r=await E(s,e,o,n);a.status(200).json({success:!0,...r})})},{path:"/cleanup-build-artifacts",method:"post",handler:d(async(t,a)=>{const{userId:s,cId:e}=t.body||{},o=`computer:${s}:${e}`;u(o,"INFO","Cleanup build artifacts request",{userId:s,cId:e});const n=await P(s,e);a.status(200).json({success:!0,...n})})}];q.forEach(t=>{if(t.middleware)g[t.method](t.path,t.middleware,t.handler);else if(t.customHandler){const a=[];t.handler&&a.push(t.handler),t.decodeMiddleware&&a.push(t.decodeMiddleware),a.push(_),a.push(t.customHandler),g[t.method](t.path,...a)}else g[t.method](t.path,t.handler)});export default g;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import l from"express";import{asyncHandler as c,ValidationError as p}from"../utils/error/errorHandler.js";import d from"../service/gitService.js";import{log as i}from"../utils/log/logUtils.js";import{extractIsolationContext as I}from"../utils/common/projectPathUtils.js";const u=l.Router();function m(a){const{workspaceType:o,projectId:t,userId:e,cId:s}=a||{},r=I(a||{});return{workspaceType:o?String(o):void 0,projectId:t?String(t):void 0,userId:e?String(e):void 0,cId:s?String(s):void 0,isolationContext:r}}const y=[{path:"/init",method:"post",handler:c(async(a,o)=>{const t=m(a.body);i(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git init",t);const e=await d.init(t);o.status(200).json(e)})},{path:"/status",method:"get",handler:c(async(a,o)=>{const t=m(a.query);i(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git status",t);const e=await d.status(t);o.status(200).json(e)})},{path:"/commit",method:"post",handler:c(async(a,o)=>{const{message:t,files:e,authorName:s,authorEmail:r}=a.body||{},n={...m(a.body),message:t,files:e,authorName:s,authorEmail:r};if(!t)throw new p("Commit message cannot be empty",{field:"message"});i(n.projectId||`computer:${n.userId}:${n.cId}`,"INFO","Git commit",{...n,filesCount:e?e.length:"all"});const h=await d.commit(n);o.status(200).json(h)})},{path:"/add",method:"post",handler:c(async(a,o)=>{const{files:t}=a.body||{},e={...m(a.body),files:t};i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git add",{...e,filesCount:t?t.length:"all"});const s=await d.add(e);o.status(200).json(s)})},{path:"/unstage",method:"post",handler:c(async(a,o)=>{const{files:t}=a.body||{},e={...m(a.body),files:t};i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git unstage",{...e,filesCount:t?t.length:"all"});const s=await d.unstage(e);o.status(200).json(s)})},{path:"/discard",method:"post",handler:c(async(a,o)=>{const{files:t}=a.body||{},e={...m(a.body),files:t};i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git discard",{...e,filesCount:t?t.length:"all"});const s=await d.discard(e);o.status(200).json(s)})},{path:"/log",method:"get",handler:c(async(a,o)=>{const{maxCount:t,branch:e,skip:s,filePath:r}=a.query||{},n={...m(a.query),maxCount:t?parseInt(t,10):50,skip:s?parseInt(s,10):0,branch:e,filePath:r};i(n.projectId||`computer:${n.userId}:${n.cId}`,"INFO","Git log",n);const h=await d.logHistory(n);o.status(200).json(h)})},{path:"/diff",method:"post",handler:c(async(a,o)=>{const{from:t,to:e,paths:s,source:r}=a.body||{},n={...m(a.body),from:t,to:e,paths:s,source:r};i(n.projectId||`computer:${n.userId}:${n.cId}`,"INFO","Git diff",{from:t,to:e});const h=await d.diff(n);o.status(200).json(h)})},{path:"/file-content",method:"post",handler:c(async(a,o)=>{const{ref:t,filePath:e}=a.body||{},s={...m(a.body),ref:t,filePath:e};i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git file content",{ref:t,filePath:e});const r=await d.fileContent(s);o.status(200).json(r)})},{path:"/reset",method:"post",handler:c(async(a,o)=>{const{target:t,mode:e}=a.body||{},s={...m(a.body),target:t,mode:e||"mixed"};if(!t)throw new p("Reset target cannot be empty",{field:"target"});i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git reset",{target:t,mode:e});const r=await d.reset(s);o.status(200).json(r)})},{path:"/revert",method:"post",handler:c(async(a,o)=>{const{target:t,message:e,authorName:s,authorEmail:r}=a.body||{},n={...m(a.body),target:t,message:e,authorName:s,authorEmail:r};if(!t)throw new p("Revert target cannot be empty",{field:"target"});i(n.projectId||`computer:${n.userId}:${n.cId}`,"INFO","Git revert",{target:t});const h=await d.revert(n);o.status(200).json(h)})},{path:"/checkout",method:"post",handler:c(async(a,o)=>{const{target:t}=a.body||{},e={...m(a.body),target:t};if(!t)throw new p("Checkout target cannot be empty",{field:"target"});i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git checkout files",{target:t});const s=await d.checkout(e);o.status(200).json(s)})},{path:"/tags",method:"get",handler:c(async(a,o)=>{const t=m(a.query);i(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git list tags",t);const e=await d.listTags(t);o.status(200).json(e)})},{path:"/tag-create",method:"post",handler:c(async(a,o)=>{const{tagName:t,message:e}=a.body||{},s={...m(a.body),tagName:t,message:e};if(!t)throw new p("Tag name cannot be empty",{field:"tagName"});i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git create tag",{tagName:t});const r=await d.createTag(s);o.status(200).json(r)})},{path:"/tag-delete",method:"post",handler:c(async(a,o)=>{const{tagName:t}=a.body||{},e={...m(a.body),tagName:t};if(!t)throw new p("Tag name cannot be empty",{field:"tagName"});i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git delete tag",{tagName:t});const s=await d.deleteTag(e);o.status(200).json(s)})},{path:"/branches",method:"get",handler:c(async(a,o)=>{const t=m(a.query);i(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git list branches",t);const e=await d.listBranches(t);o.status(200).json(e)})},{path:"/branch-create",method:"post",handler:c(async(a,o)=>{const{branchName:t,startPoint:e}=a.body||{},s={...m(a.body),branchName:t,startPoint:e};if(!t)throw new p("Branch name cannot be empty",{field:"branchName"});i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git create branch",{branchName:t,startPoint:e});const r=await d.createBranch(s);o.status(200).json(r)})},{path:"/branch-switch",method:"post",handler:c(async(a,o)=>{const{branchName:t}=a.body||{},e={...m(a.body),branchName:t};if(!t)throw new p("Branch name cannot be empty",{field:"branchName"});i(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git switch branch",{branchName:t});const s=await d.switchBranch(e);o.status(200).json(s)})},{path:"/branch-delete",method:"post",handler:c(async(a,o)=>{const{branchName:t,force:e}=a.body||{},s={...m(a.body),branchName:t,force:e===!0};if(!t)throw new p("Branch name cannot be empty",{field:"branchName"});i(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git delete branch",{branchName:t,force:e});const r=await d.deleteBranch(s);o.status(200).json(r)})}];y.forEach(a=>{a.middleware?u[a.method](a.path,a.middleware,a.handler):u[a.method](a.path,a.handler)});export default u;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import x from"express";import
|
|
1
|
+
import x from"express";import j from"multer";import h from"path";import d from"fs";import P from"iconv-lite";import u from"../service/projectService.js";import{getProjectContent as b,getProjectContentByVersion as E}from"../utils/project/getContentUtils.js";import{uploadAttachmentFile as D}from"../utils/project/uploadAttachmentFileUtils.js";import{copyProject as C}from"../utils/project/copyProjectUtils.js";import m from"../appConfig/index.js";import{log as y}from"../utils/log/logUtils.js";import{extractIsolationContext as l,resolveProjectPath as T}from"../utils/common/projectPathUtils.js";import{ValidationError as i,SystemError as R,asyncHandler as p}from"../utils/error/errorHandler.js";const w=x.Router(),O=j.diskStorage({destination:function(e,n,t){const o=h.join(m.UPLOAD_PROJECT_DIR,"temp");d.existsSync(o)||d.mkdirSync(o,{recursive:!0}),t(null,o)},filename:function(e,n,t){const o=Date.now()+"-"+Math.round(Math.random()*1e6);t(null,n.fieldname+"-"+o+h.extname(n.originalname))}}),I=j({storage:O,fileFilter:function(e,n,t){const o=h.extname(n.originalname).toLowerCase();m.UPLOAD_ALLOWED_EXTENSIONS.includes(o)?t(null,!0):t(new i("File type not allowed",{fileExtension:o,allowedExtensions:m.UPLOAD_ALLOWED_EXTENSIONS}),!1)},limits:{fileSize:m.UPLOAD_MAX_FILE_SIZE_BYTES}}),_=j.diskStorage({destination:function(e,n,t){const o=h.join(m.UPLOAD_PROJECT_DIR,"temp");d.existsSync(o)||d.mkdirSync(o,{recursive:!0}),t(null,o)},filename:function(e,n,t){const o=Date.now()+"-"+Math.round(Math.random()*1e6);t(null,n.fieldname+"-"+o+h.extname(n.originalname))}}),S=[".pdf",".doc",".docx",".xls",".xlsx",".ppt",".pptx",".txt",".md",".csv",".json",".xml",".png",".jpg",".jpeg",".gif",".bmp",".svg",".ico",".webp",".avif",".zip",".rar",".7z",".tar",".gz",".mp4",".avi",".mov",".wmv",".flv",".mp3",".wav",".ogg",".m4a"],A=j({storage:_,fileFilter:function(e,n,t){const o=h.extname(n.originalname).toLowerCase();S.includes(o)?t(null,!0):t(new i("\u9644\u4EF6\u6587\u4EF6\u7C7B\u578B\u4E0D\u88AB\u5141\u8BB8",{fileExtension:o,allowedExtensions:S}),!1)},limits:{fileSize:m.UPLOAD_MAX_FILE_SIZE_BYTES}});function L(e,n,t){if(e.file&&e.file.originalname)try{const o=e.file.originalname,a=P.decode(Buffer.from(e.file.originalname,"latin1"),"utf8");e.file.originalname=a,y("system","INFO","File name decoded successfully",{before:o,after:a})}catch(o){y("system","WARN","File name decoded failed",{originalName:e.file.originalname,error:o.message})}t()}const v=[{path:"/push-skills-to-workspace",method:"post",handler:I.single("file"),customHandler:p(async(e,n)=>{const{projectId:t,skillUrls:o}=e.body||{},a=e.file||null;if(!t)throw new i("Project ID cannot be empty",{field:"projectId"});const s=l(e.body||{}),r=await u.pushSkillsToWorkspace(String(t),a,o,s);n.status(200).json({success:!0,...r})})},{path:"/create-project",method:"post",handler:p(async(e,n)=>{const{projectId:t,templateType:o}=e.body,a=l(e.body||{});if(!t)throw new i("Project ID cannot be empty",{field:"projectId"});const s=await u.createProject(String(t),o,a);n.status(200).json(s)})},{path:"/upload-project",method:"post",handler:I.single("file"),customHandler:p(async(e,n)=>{const{projectId:t,codeVersion:o,pid:a,basePath:s}=e.body,r=l(e.body||{});if(!t)throw new i("Project ID cannot be empty",{field:"projectId"});if(!o)throw new i("Code version cannot be empty",{field:"codeVersion"});if(!e.file)throw new i("Please upload a zip file",{field:"zipFile"});const c=await u.handleFileUpload(String(t),o,e.file);e.file.path=c.filePath;try{const f=await u.uploadProject(String(t),e.file.path,e,o,a,s,r);n.status(200).json(f)}catch(f){try{await u.cleanupProjectDirectory(String(t),r)}catch(g){y(t,"ERROR","Route layer cleanup project directory failed",{projectId:t,error:g.message})}if(e.file&&d.existsSync(e.file.path))try{d.unlinkSync(e.file.path)}catch(g){y(t,"ERROR","Clean upload file failed",{projectId:t,error:g.message})}throw f}})},{path:"/get-project-content",method:"get",handler:p(async(e,n)=>{const{projectId:t,command:o,proxyPath:a}=e.query,s=l(e.query||{});if(!t)throw new i("Project ID cannot be empty",{field:"projectId"});const r=T(t,s);if(!d.existsSync(r))throw new i("Project does not exist",{field:"projectId"});try{const c=await b(r,o,a);n.status(200).json({success:!0,...c})}catch(c){const f=c?.message||"Query failed";n.status(500).json({success:!1,message:f})}})},{path:"/get-project-content-by-version",method:"get",handler:p(async(e,n)=>{const{projectId:t,codeVersion:o,command:a,proxyPath:s}=e.query,r=l(e.query||{});if(!t)throw new i("Project ID cannot be empty",{field:"projectId"});if(!o)throw new i("Code version cannot be empty",{field:"codeVersion"});if(m.GIT_ENABLED)return n.status(200).json({success:!1,deprecated:!0,message:"\u6B64\u63A5\u53E3\u5DF2\u5E9F\u5F03\uFF0C\u8BF7\u4F7F\u7528 /api/git/log + /api/git/diff \u67E5\u770B\u5386\u53F2\u7248\u672C\u5185\u5BB9"});try{const c=await E(String(t),o,a,s,r);n.status(200).json({success:!0,...c})}catch(c){const f=c?.message||"Query failed";n.status(500).json({success:!1,message:f})}})},{path:"/backup-current-version",method:"post",handler:p(async(e,n)=>{const{projectId:t,codeVersion:o}=e.body,a=l(e.body||{});if(m.GIT_ENABLED)return n.status(200).json({success:!1,deprecated:!0,message:"\u6B64\u63A5\u53E3\u5DF2\u5E9F\u5F03\uFF0C\u8BF7\u4F7F\u7528 Git \u7248\u672C\u7BA1\u7406 API\uFF08/api/git/*\uFF09"});const s=await u.backupCurrentVersion(String(t),o,a);n.status(200).json({success:!0,...s})})},{path:"/export-project",method:"post",handler:p(async(e,n)=>{const{projectId:t,codeVersion:o,exportType:a,config:s}=e.body,r=l(e.body||{}),c=await u.exportProject(String(t),o,a,s,r);if(!d.existsSync(c.zipPath))throw new R("Exported zip file does not exist",{zipPath:c.zipPath});const f=h.basename(c.zipPath);n.setHeader("Content-Type","application/zip"),n.setHeader("Content-Disposition",`attachment; filename="${f}"`),n.sendFile(c.zipPath,g=>{g?(y(t,"ERROR","Send zip file failed",{projectId:t,error:g.message}),n.headersSent||n.status(500).json({success:!1,message:"File send failed"})):y(t,"INFO","Zip file send successfully",{projectId:t,zipPath:c.zipPath})})})},{path:"/delete-project",method:"get",handler:p(async(e,n)=>{const{projectId:t,pid:o}=e.query,a=l(e.query||{});if(!t)throw new i("Project ID cannot be empty",{field:"projectId"});const s=await u.deleteProject(String(t),o,e,a);n.status(200).json(s)})},{path:"/upload-attachment-file",method:"post",handler:A.single("file"),decodeMiddleware:L,customHandler:p(async(e,n)=>{const{projectId:t,fileName:o}=e.body,a=l(e.body||{});if(!t)throw new i("Project ID cannot be empty",{field:"projectId"});if(!e.file)throw new i("Please upload a file",{field:"file"});try{const s=await D(String(t),e.file,o,a);n.status(200).json({success:!0,...s})}catch(s){if(e.file&&d.existsSync(e.file.path))try{d.unlinkSync(e.file.path)}catch(r){y(t,"ERROR","Clean upload file failed",{projectId:t,error:r.message})}throw s}})},{path:"/copy-project",method:"post",handler:p(async(e,n)=>{const{sourceProjectId:t,targetProjectId:o}=e.body,a=l({tenantId:e.body?.sourceTenantId||e.body?.tenantId,spaceId:e.body?.sourceSpaceId||e.body?.spaceId,isolationType:e.body?.sourceIsolationType||e.body?.isolationType}),s=l({tenantId:e.body?.targetTenantId||e.body?.tenantId,spaceId:e.body?.targetSpaceId||e.body?.spaceId,isolationType:e.body?.targetIsolationType||e.body?.isolationType});if(!t)throw new i("Source project ID cannot be empty",{field:"sourceProjectId"});if(!o)throw new i("Target project ID cannot be empty",{field:"targetProjectId"});const r=await C(String(t),String(o),{sourceIsolationContext:a,targetIsolationContext:s});n.status(200).json(r)})}];function z(e,n,t,o){if(e instanceof j.MulterError||e.name==="ValidationError"||e instanceof i)return o(e);o(e)}v.forEach(e=>{if(e.customHandler){const n=[];e.handler&&n.push(e.handler),e.decodeMiddleware&&n.push(e.decodeMiddleware),n.push(z),n.push(e.customHandler),w[e.method](e.path,...n)}else w[e.method](e.path,e.handler)});export default w;
|
package/dist/routes/router.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import i from"express";import c from"./buildRoutes.js";import u from"./projectRoutes.js";import l from"./codeRoutes.js";import d from"./computerRoutes.js";import s from"path";import
|
|
1
|
+
import i from"express";import c from"./buildRoutes.js";import u from"./projectRoutes.js";import l from"./codeRoutes.js";import d from"./computerRoutes.js";import h from"./gitRoutes.js";import s from"path";import f from"fs";import{fileURLToPath as g}from"url";const v=s.dirname(g(import.meta.url));let a="1.0.0";try{const o=s.resolve(v,"..","..","package.json");a=JSON.parse(f.readFileSync(o,"utf8")).version||"1.0.0"}catch{}const e=i.Router();e.get("/",(o,r)=>{r.send("Hello")}),e.get("/health",(o,r)=>{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:a,platform:process.platform,nodeVersion:process.version,pid:process.pid,memory:m,env:process.env.NODE_ENV||"unknown"};r.json(n)}),e.use("/api/build",c),e.use("/api/project",u),e.use("/api/project",l),e.use("/api/computer",d),e.use("/api/git",h);export default e;
|
package/dist/server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import A from"express";import"json-bigint";import
|
|
1
|
+
import A from"express";import"json-bigint";import C from"swagger-ui-express";import R from"./config/swagger.js";import n from"./appConfig/index.js";import{log as c,logger as I}from"./utils/log/logUtils.js";import S from"./utils/log/logCacheManager.js";import{errorHandler as P,notFoundHandler as w}from"./utils/error/errorHandler.js";import y from"./routes/router.js";import{cleanupInitProjectOnStartup as N}from"./utils/project/initProjectCleanupUtils.js";import{startScheduler as D,stopScheduler as H}from"./scheduler/pnpmPruneScheduler.js";import f from"path";const o=A();o.use(A.json({limit:n.REQUEST_BODY_LIMIT,reviver:(t,e)=>typeof e=="number"&&!Number.isSafeInteger(e)?e.toString():e})),o.use(A.urlencoded({extended:!0,limit:n.REQUEST_BODY_LIMIT})),o.use(I);const O=t=>{let e=t;try{for(;;){const s=decodeURIComponent(e);if(s===e)break;e=s}}catch{}return e};o.use("/api/page/static/:projectId",(t,e,s)=>{const{projectId:d}=t.params;let a=t.path||"/";if(!d||a==="/")return e.status(404).send("Not Found");const r=t.headers.origin,l=r||"*";if(e.header("Access-Control-Allow-Origin",l),e.header("Access-Control-Allow-Methods","HEAD,GET,POST,PUT,DELETE,OPTIONS"),e.header("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Fragment"),e.header("Access-Control-Expose-Headers","Content-Type"),r&&(e.header("Access-Control-Allow-Credentials","true"),e.header("Vary","Origin")),t.method==="OPTIONS")return e.sendStatus(200);a=a.replace(/^\/+/,"");const u=O(a),p=f.join(n.PROJECT_SOURCE_DIR,d,u),i={"Access-Control-Allow-Origin":l,"Access-Control-Allow-Methods":"HEAD,GET,POST,PUT,DELETE,OPTIONS","Access-Control-Allow-Headers":"Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Fragment"};return r&&(i["Access-Control-Allow-Credentials"]="true",i.Vary="Origin"),e.sendFile(p,{dotfiles:"allow",headers:i},h=>{if(h)return s()})}),o.use("/api/computer/static/:userId/:cId",(t,e,s)=>{const{userId:d,cId:a}=t.params;let r=t.path||"/";if(!d||!a||r==="/")return e.status(404).send("Not Found");const l=t.headers.origin,u=l||"*";if(e.header("Access-Control-Allow-Origin",u),e.header("Access-Control-Allow-Methods","HEAD,GET,POST,PUT,DELETE,OPTIONS"),e.header("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Range, If-Range"),e.header("Access-Control-Expose-Headers","Content-Type, Content-Length, Content-Range, Accept-Ranges, ETag, Last-Modified"),l&&(e.header("Access-Control-Allow-Credentials","true"),e.header("Vary","Origin")),t.method==="OPTIONS")return e.sendStatus(200);r=r.replace(/^\/+/,"");const p=O(r),{customTargetDir:i}=t.query,h=i&&i.trim()?f.join(i,p):f.join(n.COMPUTER_WORKSPACE_DIR,d,a,p),m={"Access-Control-Allow-Origin":u,"Access-Control-Allow-Methods":"HEAD,GET,POST,PUT,DELETE,OPTIONS","Access-Control-Allow-Headers":"Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Range, If-Range","Access-Control-Expose-Headers":"Content-Type, Content-Length, Content-Range, Accept-Ranges, ETag, Last-Modified"};return l&&(m["Access-Control-Allow-Credentials"]="true",m.Vary="Origin"),e.sendFile(h,{dotfiles:"allow",headers:m},E=>{if(E)return s()})}),o.use("/api-docs",C.serve,C.setup(R,{customCss:".swagger-ui .topbar { display: none }",customSiteTitle:"nuwax-file-server API Documentation"})),o.use(y),o.use(w),o.use(P);const g=o.listen(n.PORT,async()=>{c("default","INFO",`Server is running on port ${n.PORT} (${n.NODE_ENV} mode)`),await N(n);try{D()}catch(t){c("default","ERROR",`pnpm prune scheduled task failed to start: ${t.message}`)}});g.timeout=6e5,g.keepAliveTimeout=61e4,g.headersTimeout=62e4;const T=t=>{c("default","INFO",`Received ${t} signal, preparing graceful exit...`),H();try{S.destroy(),c("default","INFO","Log cache manager cleared")}catch(e){c("default","ERROR",`Failed to clear log cache manager: ${e.message}`)}g.close(()=>{c("default","INFO","Server closed"),process.exit(0)}),setTimeout(()=>{c("default","ERROR","Force exit (timeout)"),process.exit(1)},3e4)};process.on("SIGTERM",()=>T("SIGTERM")),process.on("SIGINT",()=>T("SIGINT"));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import o from"path";import r from"fs";import R from"../appConfig/index.js";import{restartDevServer as U}from"../utils/build/restartDevUtils.js";import{log as t}from"../utils/log/logUtils.js";import{ValidationError as h,SystemError as x,ResourceError as D}from"../utils/error/errorHandler.js";import"../utils/common/sensitiveUtils.js";import{backupProjectToZip as O,restoreProjectFromZip as z,pruneMissingFiles as L}from"../utils/project/backupUtils.js";import"../utils/common/zipUtils.js";import"../utils/buildJudge/restartJudgeUtils.js";import{resolveProjectPath as S}from"../utils/common/projectPathUtils.js";function V(e,d){const n=e.split(/\r?\n/),g=d.split(/\r?\n/),F=n.length,b=g.length,y=Math.min(F,b);let l=0;for(let a=0;a<y;a++)n[a]!==g[a]&&(n[a]=g[a],l++);if(F>b)for(let a=F-1;a>=b;a--)n.splice(a,1),l++;if(b>F)for(let a=F;a<b;a++)n.push(g[a]),l++;const m=e.includes(`\r
|
|
2
2
|
`)?`\r
|
|
3
3
|
`:`
|
|
4
|
-
`;return{finalContent:n.join(u),changesCount:r}}function ie(e,F){return e===F?{finalContent:e,changesCount:0}:{finalContent:F,changesCount:-1}}async function T(e,F,n,d,P={}){const w=Date.now();if(!e)throw new m("Project ID cannot be empty",{field:"projectId"});if(F==null)throw new m("codeVersion cannot be empty",{field:"codeVersion"});const b=Number(F);if(!Number.isFinite(b))throw new m("codeVersion must be a number",{field:"codeVersion"});if(!Array.isArray(n))throw new m("files must be an array",{field:"files"});for(let s=0;s<n.length;s++){const o=n[s];if(!o||typeof o.operation!="string")throw new m(`files[${s}].operation cannot be empty`,{field:`files[${s}].operation`});if(!o.name||typeof o.name!="string")throw new m(`files[${s}].name cannot be empty`,{field:`files[${s}].name`});const t=o.operation.toLowerCase();if(!["create","delete","rename","modify"].includes(t))throw new m(`files[${s}].operation must be one of create, delete, rename or modify`,{field:`files[${s}].operation`});if(t==="rename"&&!o.renameFrom)throw new m(`files[${s}].renameFrom cannot be empty (rename operation requires)`,{field:`files[${s}].renameFrom`});if(t==="modify"&&typeof o.contents!="string")throw new m(`files[${s}].contents must be a string (modify operation requires)`,{field:`files[${s}].contents`})}const r=x(e,P);if(!a.existsSync(r))throw i(e,"ERROR","Project does not exist",{projectId:e,projectPath:r}),new O("Project does not exist",{projectId:e});let u="";try{const s=l.join(R.UPLOAD_PROJECT_DIR,e);a.existsSync(s)||a.mkdirSync(s,{recursive:!0});const o=`${e}-v${b}.zip`;u=l.join(s,o),i(e,"DEBUG","Start backing up project",{projectId:e,backupZipPath:u}),await D(e,r,u),i(e,"INFO","Project backed up successfully",{projectId:e,zipPath:u});try{i(e,"DEBUG","Start processing file operations",{projectId:e,filesCount:n.length});for(const t of n){const f=t.operation.toLowerCase(),h=t.name,c=l.normalize(h).replace(/^[\/\\]+/,""),y=l.join(r,c),k=l.resolve(y),g=l.resolve(r);if(!k.startsWith(g+l.sep)&&k!==g){i(e,"WARN","Unsafe file path, skipping",{filePath:c,resolvedPath:k});continue}switch(f){case"create":{await a.promises.mkdir(l.dirname(y),{recursive:!0});const p=t.contents||"";await a.promises.writeFile(y,p,"utf8"),i(e,"INFO","File created successfully",{filePath:c});break}case"delete":{a.existsSync(y)?(await a.promises.unlink(y),i(e,"INFO","File deleted successfully",{filePath:c})):i(e,"WARN","File to delete does not exist",{filePath:c});break}case"rename":{const p=t.renameFrom;if(!p||typeof p!="string"){i(e,"WARN","Rename operation missing renameFrom",{filePath:c});break}const v=l.normalize(p).replace(/^[\/\\]+/,""),N=l.join(r,v),E=l.resolve(N);if(!E.startsWith(g+l.sep)&&E!==g){i(e,"WARN","Unsafe rename source path, skipping",{renameFrom:v,resolvedPath:E});break}a.existsSync(N)?(await a.promises.mkdir(l.dirname(y),{recursive:!0}),await a.promises.rename(N,y),i(e,"INFO","File renamed successfully",{oldPath:v,newPath:c})):i(e,"WARN","File to rename does not exist",{renameFrom:v});break}case"modify":{if(!a.existsSync(y)){i(e,"WARN","File to modify does not exist",{filePath:c});break}const p=await a.promises.readFile(y,"utf8"),v=typeof t.contents=="string"?t.contents:"",{finalContent:N,changesCount:E}=L(p,v);if(E===0){i(e,"INFO","File content unchanged, skipping write",{filePath:c});break}await a.promises.writeFile(y,N,"utf8"),i(e,"INFO","File modified successfully",{filePath:c,changesCount:E});break}default:{i(e,"WARN","Unsupported operation type",{operation:f,filePath:c});break}}}i(e,"DEBUG","Start cleaning empty directories",{projectId:e});try{await C(r,R.TRAVERSE_EXCLUDE_DIRS||[])}catch(t){i(e,"WARN","Failed to clean empty directories",{projectId:e,error:t&&t.message})}return i(e,"INFO","Specified files updated successfully",{projectId:e,filesCount:n.length,elapsedMs:Date.now()-w}),{success:!0,message:"Specified files updated successfully",projectId:e,filesCount:n.length}}catch(t){throw i(e,"ERROR","Failed to process file operations",{projectId:e,error:t&&t.message,elapsedMs:Date.now()-w}),t}}catch(s){throw s.isOperational?s:new S("Failed to backup project",{projectId:e,originalError:s&&s.message})}}async function U(e,F,n,d,P={}){const w=Date.now();if(!e)throw new m("Project ID cannot be empty",{field:"projectId"});const b=Number(F);if(!Number.isFinite(b))throw new m("codeVersion must be a number",{field:"codeVersion"});if(!Array.isArray(n))throw new m("files must be an array",{field:"files"});const r=x(e,P);if(!a.existsSync(r))throw i(e,"ERROR","Project does not exist",{projectId:e,projectPath:r}),new O("Project does not exist",{projectId:e});let u="";try{const s=l.join(R.UPLOAD_PROJECT_DIR,e);a.existsSync(s)||a.mkdirSync(s,{recursive:!0});const o=`${e}-v${b}.zip`;u=l.join(s,o),i(e,"DEBUG","Start backing up project",{projectId:e,backupZipPath:u}),await D(e,r,u);try{i(e,"DEBUG","Start writing files",{projectId:e,filesCount:n.length});for(const t of n){if(!t||typeof t.name!="string")continue;const f=l.join(r,t.name);if(t.renameFrom&&typeof t.renameFrom=="string"){const p=l.join(r,t.renameFrom);if(a.existsSync(p)){await a.promises.mkdir(l.dirname(f),{recursive:!0}),await a.promises.rename(p,f),i(e,"INFO","File renamed successfully",{projectId:e,oldPath:t.renameFrom,newPath:t.name});continue}}const h=t.binary===!0,c=t.binary===!1,y=!!t.sizeExceeded,k=typeof t.contents=="string"&&t.contents.length>0;if(h){if(a.existsSync(f)){i(e,"INFO","Binary file already exists, skipping write",{filePath:t.name});continue}if(k)try{await a.promises.mkdir(l.dirname(f),{recursive:!0});const p=Buffer.from(t.contents,"base64");await a.promises.writeFile(f,p),i(e,"INFO","Binary file written successfully",{filePath:t.name})}catch(p){i(e,"ERROR","Failed to write binary file",{filePath:t.name,error:p&&p.message})}else i(e,"WARN","Binary file does not exist and has no content, skipping",{filePath:t.name});continue}c&&(!y||y&&k)&&(await a.promises.mkdir(l.dirname(f),{recursive:!0}),await a.promises.writeFile(f,t.contents||"","utf8"))}}catch(t){throw i(e,"ERROR","Failed to write files",{projectId:e,error:t&&t.message,elapsedMs:Date.now()-w}),t}try{i(e,"DEBUG","Start cleaning missing files and empty directories",{projectId:e});const t=new Set(n.filter(f=>f&&typeof f.name=="string").map(f=>l.normalize(f.name)));await V(r,t,R.TRAVERSE_EXCLUDE_DIRS||[]),await C(r,R.TRAVERSE_EXCLUDE_DIRS||[])}catch(t){throw i(e,"ERROR","Failed to clean missing files, starting rollback",{projectId:e,error:t&&t.message,elapsedMs:Date.now()-w}),t}return i(e,"INFO","Files submitted successfully",{projectId:e,filesCount:n.length,elapsedMs:Date.now()-w}),{success:!0,message:"Files submitted successfully",projectId:e,restarted:!1}}catch(s){throw s.isOperational?s:new S("Failed to backup old version",{projectId:e,originalError:s&&s.message})}}async function $(e,F,n,d,P,w={}){const b=Date.now();if(!e)throw new m("Project ID cannot be empty",{field:"projectId"});const r=Number(F);if(!Number.isFinite(r))throw new m("codeVersion must be a number",{field:"codeVersion"});if(!n)throw new m("File cannot be empty",{field:"file"});if(!d||typeof d!="string")throw new m("File path cannot be empty",{field:"filePath"});const u=x(e,w);if(!a.existsSync(u))throw i(e,"ERROR","Project does not exist",{projectId:e,projectPath:u}),new O("Project does not exist",{projectId:e});const s=l.normalize(d).replace(/^[\/\\]+/,""),o=l.join(u,s),t=l.resolve(o),f=l.resolve(u);if(!t.startsWith(f))throw new m("File path is not safe, cannot exceed project directory",{field:"filePath",providedPath:d,resolvedPath:t});let h="";try{const c=l.join(R.UPLOAD_PROJECT_DIR,e);a.existsSync(c)||a.mkdirSync(c,{recursive:!0});const y=`${e}-v${r}.zip`;h=l.join(c,y),i(e,"DEBUG","Start backing up project",{projectId:e,backupZipPath:h}),await D(e,u,h),i(e,"INFO",`Project backed up: ${h}`,{projectId:e,zipPath:h});try{if(i(e,"DEBUG","Start writing uploaded file",{projectId:e,filePath:s}),await a.promises.mkdir(l.dirname(o),{recursive:!0}),!n.buffer)throw new m("File content format is incorrect, missing buffer",{field:"file"});if(i(e,"INFO","Prepare to write file",{targetPath:o,bufferLength:n.buffer.length,expectedSize:n.size,bufferIsBuffer:Buffer.isBuffer(n.buffer),sizeMatch:n.buffer.length===n.size}),await a.promises.writeFile(o,n.buffer),i(e,"INFO","File uploaded successfully",{projectId:e,filePath:s,targetPath:t,fileSize:n.buffer?n.buffer.length:0,elapsedMs:Date.now()-b}),!1)try{const g=await B(P,e);return i(e,"INFO","Restart development server successfully",{projectId:e,pid:g.pid,port:g.port}),{success:!0,message:"File uploaded and restarted development server successfully",projectId:e,filePath:s,targetPath:t,fileSize:n.buffer?n.buffer.length:0,pid:g.pid,port:g.port,restarted:!0}}catch(g){i(e,"ERROR","Failed to restart development server",{projectId:e,filePath:s,error:g&&g.message})}else return i(e,"INFO","File modification does not require restarting development server",{projectId:e,filePath:s}),{success:!0,message:"File uploaded successfully, no need to restart development server",projectId:e,restarted:!1}}catch(k){throw i(e,"ERROR","Failed to write file",{projectId:e,filePath:s,error:k&&k.message,elapsedMs:Date.now()-b}),k}}catch(c){throw c.isOperational?c:new S("Failed to backup project",{projectId:e,filePath:s,originalError:c&&c.message})}}async function A(e,F,n,d,P={}){const w=Date.now();if(!e)throw new m("Project ID cannot be empty",{field:"projectId"});const b=Number(F);if(!Number.isFinite(b))throw new m("codeVersion must be a number",{field:"codeVersion"});const r=Number(n);if(!Number.isFinite(r))throw new m("rollbackTo must be a number",{field:"rollbackTo"});if(r<0)throw new m("rollbackTo cannot be less than 0",{field:"rollbackTo"});if(r>=b)throw new m("rollbackTo must be less than current codeVersion",{field:"rollbackTo"});const u=x(e,P);if(!a.existsSync(u))throw i(e,"ERROR","Project does not exist",{projectId:e,projectPath:u}),new O("Project does not exist",{projectId:e});const s=l.join(R.UPLOAD_PROJECT_DIR,e),o=`${e}-v${r}.zip`,t=l.join(s,o);if(!a.existsSync(t))throw i(e,"ERROR","Rollback version backup file does not exist",{projectId:e,rollbackTo:r,zipPath:t}),new O("Rollback version backup file does not exist",{projectId:e,rollbackTo:r});let f="";try{a.existsSync(s)||a.mkdirSync(s,{recursive:!0});const h=`${e}-v${b}.zip`;return f=l.join(s,h),a.existsSync(f)?i(e,"INFO","Current version backup already exists, skipping backup",{projectId:e,zipPath:f}):(await D(e,u,f),i(e,"INFO","Current version backed up",{projectId:e,zipPath:f})),i(e,"DEBUG","Start restoring project from backup",{projectId:e,rollbackToNum:r,rollbackZipPath:t}),await z(e,u,t),i(e,"INFO","Project rolled back successfully",{projectId:e,newVersion:b,toVersion:r,rollbackZipPath:t,elapsedMs:Date.now()-w}),{success:!0,message:"Project rolled back successfully",newVersion:b,rollbackTo:r}}catch(h){if(i(e,"ERROR","Failed to rollback project",{projectId:e,rollbackTo:r,error:h&&h.message,elapsedMs:Date.now()-w}),f&&a.existsSync(f))try{i(e,"INFO","Failed to rollback, trying to restore current version",{projectId:e,backupPath:f}),await z(e,u,f),i(e,"INFO","Current version restored",{projectId:e})}catch(c){i(e,"ERROR","Failed to restore current version",{projectId:e,error:c&&c.message})}throw h.isOperational?h:new S("Failed to rollback project",{projectId:e,rollbackTo:r,originalError:h&&h.message})}}export{T as specifiedFilesUpdate,U as allFilesUpdate,$ as uploadSingleFile,A as rollbackVersion};export default{specifiedFilesUpdate:T,allFilesUpdate:U,uploadSingleFile:$,rollbackVersion:A};
|
|
4
|
+
`;return{finalContent:n.join(m),changesCount:l}}function ie(e,d){return e===d?{finalContent:e,changesCount:0}:{finalContent:d,changesCount:-1}}async function T(e,d,n,g,F={}){const b=Date.now();if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});if(d==null)throw new h("codeVersion cannot be empty",{field:"codeVersion"});const y=Number(d);if(!Number.isFinite(y))throw new h("codeVersion must be a number",{field:"codeVersion"});if(!Array.isArray(n))throw new h("files must be an array",{field:"files"});for(let s=0;s<n.length;s++){const a=n[s];if(!a||typeof a.operation!="string")throw new h(`files[${s}].operation cannot be empty`,{field:`files[${s}].operation`});if(!a.name||typeof a.name!="string")throw new h(`files[${s}].name cannot be empty`,{field:`files[${s}].name`});const i=a.operation.toLowerCase();if(!["create","delete","rename","modify"].includes(i))throw new h(`files[${s}].operation must be one of create, delete, rename or modify`,{field:`files[${s}].operation`});if(i==="rename"&&!a.renameFrom)throw new h(`files[${s}].renameFrom cannot be empty (rename operation requires)`,{field:`files[${s}].renameFrom`});if(i==="modify"&&typeof a.contents!="string")throw new h(`files[${s}].contents must be a string (modify operation requires)`,{field:`files[${s}].contents`})}const l=S(e,F);if(!r.existsSync(l))throw t(e,"ERROR","Project does not exist",{projectId:e,projectPath:l}),new D("Project does not exist",{projectId:e});let m="";try{const s=o.join(R.UPLOAD_PROJECT_DIR,e);r.existsSync(s)||r.mkdirSync(s,{recursive:!0});const a=`${e}-v${y}.zip`;m=o.join(s,a),t(e,"DEBUG","Start backing up project",{projectId:e,backupZipPath:m}),R.GIT_ENABLED||await O(e,l,m),t(e,"INFO","Project backed up successfully",{projectId:e,zipPath:m});try{t(e,"DEBUG","Start processing file operations",{projectId:e,filesCount:n.length});for(const i of n){const c=i.operation.toLowerCase(),f=i.name,u=o.normalize(f).replace(/^[\/\\]+/,""),w=o.join(l,u),k=o.resolve(w),p=o.resolve(l);if(!k.startsWith(p+o.sep)&&k!==p){t(e,"WARN","Unsafe file path, skipping",{filePath:u,resolvedPath:k});continue}switch(c){case"create":{if(i.isDir===!0)r.existsSync(w)?t(e,"INFO","Directory already exists, skipping creation",{filePath:u}):(await r.promises.mkdir(w,{recursive:!0}),t(e,"INFO","Directory created successfully",{filePath:u}));else{await r.promises.mkdir(o.dirname(w),{recursive:!0});const P=i.contents||"";await r.promises.writeFile(w,P,"utf8"),t(e,"INFO","File created successfully",{filePath:u})}break}case"delete":{r.existsSync(w)?(await r.promises.stat(w)).isDirectory()?(await r.promises.rm(w,{recursive:!0,force:!0}),t(e,"INFO","Directory deleted successfully",{filePath:u})):(await r.promises.unlink(w),t(e,"INFO","File deleted successfully",{filePath:u})):t(e,"WARN","File or directory to delete does not exist",{filePath:u});break}case"rename":{const P=i.renameFrom;if(!P||typeof P!="string"){t(e,"WARN","Rename operation missing renameFrom",{filePath:u});break}const v=o.normalize(P).replace(/^[\/\\]+/,""),E=o.join(l,v),N=o.resolve(E);if(!N.startsWith(p+o.sep)&&N!==p){t(e,"WARN","Unsafe rename source path, skipping",{renameFrom:v,resolvedPath:N});break}r.existsSync(E)?(await r.promises.mkdir(o.dirname(w),{recursive:!0}),await r.promises.rename(E,w),t(e,"INFO","File renamed successfully",{oldPath:v,newPath:u})):t(e,"WARN","File to rename does not exist",{renameFrom:v});break}case"modify":{if(!r.existsSync(w)){t(e,"WARN","File to modify does not exist",{filePath:u});break}const P=await r.promises.readFile(w,"utf8"),v=typeof i.contents=="string"?i.contents:"",{finalContent:E,changesCount:N}=V(P,v);if(N===0){t(e,"INFO","File content unchanged, skipping write",{filePath:u});break}await r.promises.writeFile(w,E,"utf8"),t(e,"INFO","File modified successfully",{filePath:u,changesCount:N});break}default:{t(e,"WARN","Unsupported operation type",{operation:c,filePath:u});break}}}return t(e,"INFO","Specified files updated successfully",{projectId:e,filesCount:n.length,elapsedMs:Date.now()-b}),{success:!0,message:"Specified files updated successfully",projectId:e,filesCount:n.length}}catch(i){throw t(e,"ERROR","Failed to process file operations",{projectId:e,error:i&&i.message,elapsedMs:Date.now()-b}),i}}catch(s){throw s.isOperational?s:new x("Failed to backup project",{projectId:e,originalError:s&&s.message})}}async function C(e,d,n,g,F={}){const b=Date.now();if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});const y=Number(d);if(!Number.isFinite(y))throw new h("codeVersion must be a number",{field:"codeVersion"});if(!Array.isArray(n))throw new h("files must be an array",{field:"files"});const l=S(e,F);if(!r.existsSync(l))throw t(e,"ERROR","Project does not exist",{projectId:e,projectPath:l}),new D("Project does not exist",{projectId:e});let m="";try{const s=o.join(R.UPLOAD_PROJECT_DIR,e);r.existsSync(s)||r.mkdirSync(s,{recursive:!0});const a=`${e}-v${y}.zip`;m=o.join(s,a),t(e,"DEBUG","Start backing up project",{projectId:e,backupZipPath:m}),R.GIT_ENABLED||await O(e,l,m);try{t(e,"DEBUG","Start writing files",{projectId:e,filesCount:n.length});for(const i of n){if(!i||typeof i.name!="string")continue;const c=o.join(l,i.name);if(i.isDir===!0){r.existsSync(c)||(await r.promises.mkdir(c,{recursive:!0}),t(e,"INFO","Directory ensured",{filePath:i.name}));continue}if(i.renameFrom&&typeof i.renameFrom=="string"){const P=o.join(l,i.renameFrom);if(r.existsSync(P)){await r.promises.mkdir(o.dirname(c),{recursive:!0}),await r.promises.rename(P,c),t(e,"INFO","File renamed successfully",{projectId:e,oldPath:i.renameFrom,newPath:i.name});continue}}const f=i.binary===!0,u=i.binary===!1,w=!!i.sizeExceeded,k=typeof i.contents=="string"&&i.contents.length>0;if(f){if(r.existsSync(c)){t(e,"INFO","Binary file already exists, skipping write",{filePath:i.name});continue}if(k)try{await r.promises.mkdir(o.dirname(c),{recursive:!0});const P=Buffer.from(i.contents,"base64");await r.promises.writeFile(c,P),t(e,"INFO","Binary file written successfully",{filePath:i.name})}catch(P){t(e,"ERROR","Failed to write binary file",{filePath:i.name,error:P&&P.message})}else t(e,"WARN","Binary file does not exist and has no content, skipping",{filePath:i.name});continue}u&&(!w||w&&k)&&(await r.promises.mkdir(o.dirname(c),{recursive:!0}),await r.promises.writeFile(c,i.contents||"","utf8"))}}catch(i){throw t(e,"ERROR","Failed to write files",{projectId:e,error:i&&i.message,elapsedMs:Date.now()-b}),i}try{t(e,"DEBUG","Start cleaning missing files",{projectId:e});const i=new Set(n.filter(f=>f&&typeof f.name=="string").map(f=>o.normalize(f.name))),c=n.filter(f=>f&&typeof f.name=="string"&&f.isDir===!0);await L(l,i,R.TRAVERSE_EXCLUDE_DIRS||[]);for(const f of c){const u=o.join(l,f.name);r.existsSync(u)||(await r.promises.mkdir(u,{recursive:!0}),t(e,"INFO","Empty directory recreated",{dirPath:f.name}))}}catch(i){throw t(e,"ERROR","Failed to clean missing files, starting rollback",{projectId:e,error:i&&i.message,elapsedMs:Date.now()-b}),i}return t(e,"INFO","Files submitted successfully",{projectId:e,filesCount:n.length,elapsedMs:Date.now()-b}),{success:!0,message:"Files submitted successfully",projectId:e,restarted:!1}}catch(s){throw s.isOperational?s:new x("Failed to backup old version",{projectId:e,originalError:s&&s.message})}}async function B(e,d,n,g,F,b={}){const y=Date.now();if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});const l=Number(d);if(!Number.isFinite(l))throw new h("codeVersion must be a number",{field:"codeVersion"});if(!n)throw new h("File cannot be empty",{field:"file"});if(!g||typeof g!="string")throw new h("File path cannot be empty",{field:"filePath"});const m=S(e,b);if(!r.existsSync(m))throw t(e,"ERROR","Project does not exist",{projectId:e,projectPath:m}),new D("Project does not exist",{projectId:e});const s=o.normalize(g).replace(/^[\/\\]+/,""),a=o.join(m,s),i=o.resolve(a),c=o.resolve(m);if(!i.startsWith(c))throw new h("File path is not safe, cannot exceed project directory",{field:"filePath",providedPath:g,resolvedPath:i});let f="";try{const u=o.join(R.UPLOAD_PROJECT_DIR,e);r.existsSync(u)||r.mkdirSync(u,{recursive:!0});const w=`${e}-v${l}.zip`;f=o.join(u,w),t(e,"DEBUG","Start backing up project",{projectId:e,backupZipPath:f}),R.GIT_ENABLED||await O(e,m,f),t(e,"INFO",`Project backed up: ${f}`,{projectId:e,zipPath:f});try{if(t(e,"DEBUG","Start writing uploaded file",{projectId:e,filePath:s}),await r.promises.mkdir(o.dirname(a),{recursive:!0}),!n.buffer)throw new h("File content format is incorrect, missing buffer",{field:"file"});if(t(e,"INFO","Prepare to write file",{targetPath:a,bufferLength:n.buffer.length,expectedSize:n.size,bufferIsBuffer:Buffer.isBuffer(n.buffer),sizeMatch:n.buffer.length===n.size}),await r.promises.writeFile(a,n.buffer),t(e,"INFO","File uploaded successfully",{projectId:e,filePath:s,targetPath:i,fileSize:n.buffer?n.buffer.length:0,elapsedMs:Date.now()-y}),!1)try{const p=await U(F,e);return t(e,"INFO","Restart development server successfully",{projectId:e,pid:p.pid,port:p.port}),{success:!0,message:"File uploaded and restarted development server successfully",projectId:e,filePath:s,targetPath:i,fileSize:n.buffer?n.buffer.length:0,pid:p.pid,port:p.port,restarted:!0}}catch(p){t(e,"ERROR","Failed to restart development server",{projectId:e,filePath:s,error:p&&p.message})}else return t(e,"INFO","File modification does not require restarting development server",{projectId:e,filePath:s}),{success:!0,message:"File uploaded successfully, no need to restart development server",projectId:e,restarted:!1}}catch(k){throw t(e,"ERROR","Failed to write file",{projectId:e,filePath:s,error:k&&k.message,elapsedMs:Date.now()-y}),k}}catch(u){throw u.isOperational?u:new x("Failed to backup project",{projectId:e,filePath:s,originalError:u&&u.message})}}async function A(e,d,n,g,F={}){const b=Date.now();if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});const y=Number(d);if(!Number.isFinite(y))throw new h("codeVersion must be a number",{field:"codeVersion"});const l=Number(n);if(!Number.isFinite(l))throw new h("rollbackTo must be a number",{field:"rollbackTo"});if(l<0)throw new h("rollbackTo cannot be less than 0",{field:"rollbackTo"});if(l>=y)throw new h("rollbackTo must be less than current codeVersion",{field:"rollbackTo"});const m=S(e,F);if(!r.existsSync(m))throw t(e,"ERROR","Project does not exist",{projectId:e,projectPath:m}),new D("Project does not exist",{projectId:e});const s=o.join(R.UPLOAD_PROJECT_DIR,e),a=`${e}-v${l}.zip`,i=o.join(s,a);if(!r.existsSync(i))throw t(e,"ERROR","Rollback version backup file does not exist",{projectId:e,rollbackTo:l,zipPath:i}),new D("Rollback version backup file does not exist",{projectId:e,rollbackTo:l});let c="";try{r.existsSync(s)||r.mkdirSync(s,{recursive:!0});const f=`${e}-v${y}.zip`;return c=o.join(s,f),r.existsSync(c)?t(e,"INFO","Current version backup already exists, skipping backup",{projectId:e,zipPath:c}):(await O(e,m,c),t(e,"INFO","Current version backed up",{projectId:e,zipPath:c})),t(e,"DEBUG","Start restoring project from backup",{projectId:e,rollbackToNum:l,rollbackZipPath:i}),await z(e,m,i),t(e,"INFO","Project rolled back successfully",{projectId:e,newVersion:y,toVersion:l,rollbackZipPath:i,elapsedMs:Date.now()-b}),{success:!0,message:"Project rolled back successfully",newVersion:y,rollbackTo:l}}catch(f){if(t(e,"ERROR","Failed to rollback project",{projectId:e,rollbackTo:l,error:f&&f.message,elapsedMs:Date.now()-b}),c&&r.existsSync(c))try{t(e,"INFO","Failed to rollback, trying to restore current version",{projectId:e,backupPath:c}),await z(e,m,c),t(e,"INFO","Current version restored",{projectId:e})}catch(u){t(e,"ERROR","Failed to restore current version",{projectId:e,error:u&&u.message})}throw f.isOperational?f:new x("Failed to rollback project",{projectId:e,rollbackTo:l,originalError:f&&f.message})}}async function $(e,d,n,g,F,b={}){const y=Date.now();if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});const l=Number(d);if(!Number.isFinite(l))throw new h("codeVersion must be a number",{field:"codeVersion"});if(!n||n.length===0)throw new h("Files cannot be empty",{field:"files"});if(!g||g.length!==n.length)throw new h("filePaths and files count mismatch",{field:"filePaths"});const m=S(e,b);if(!r.existsSync(m))throw t(e,"ERROR","Project does not exist",{projectId:e,projectPath:m}),new D("Project does not exist",{projectId:e});let s="";try{const a=o.join(R.UPLOAD_PROJECT_DIR,e);r.existsSync(a)||r.mkdirSync(a,{recursive:!0});const i=`${e}-v${l}.zip`;s=o.join(a,i),t(e,"DEBUG","Start backing up project",{projectId:e,backupZipPath:s}),R.GIT_ENABLED||await O(e,m,s),t(e,"INFO",`Project backed up: ${s}`,{projectId:e,zipPath:s});const c=[];for(let f=0;f<n.length;f++){const u=n[f],w=g[f];if(!u||!u.buffer){t(e,"WARN","Skip empty file",{index:f,filePath:w});continue}if(!w||typeof w!="string"){t(e,"WARN","Skip invalid filePath",{index:f,filePath:w});continue}const k=o.normalize(w).replace(/^[\/\\]+/,""),p=o.join(m,k),P=o.resolve(p),v=o.resolve(m);if(!P.startsWith(v)){t(e,"WARN","Skip unsafe file path",{filePath:w,resolvedTargetPath:P});continue}await r.promises.mkdir(o.dirname(p),{recursive:!0}),await r.promises.writeFile(p,u.buffer),c.push({filePath:k,size:u.buffer.length})}return t(e,"INFO","Batch files uploaded successfully",{projectId:e,fileCount:c.length,elapsedMs:Date.now()-y}),{success:!0,message:`${c.length} files uploaded successfully`,projectId:e,fileCount:c.length,files:c,restarted:!1}}catch(a){t(e,"ERROR","Batch upload failed, rolling back",{projectId:e,error:a&&a.message,elapsedMs:Date.now()-y});let i=null;if(s&&r.existsSync(s))try{await z(e,m,s),t(e,"INFO","Rollback completed after batch upload failure")}catch(c){i=c,t(e,"ERROR","Rollback failed",{error:c&&c.message})}throw i||a}}export{T as specifiedFilesUpdate,C as allFilesUpdate,B as uploadSingleFile,$ as uploadBatchFiles,A as rollbackVersion};export default{specifiedFilesUpdate:T,allFilesUpdate:C,uploadSingleFile:B,uploadBatchFiles:$,rollbackVersion:A};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import P from"path";import i from"fs";import dt from"crypto";import l from"isomorphic-git";import{createPatch as ut}from"diff";import B from"../appConfig/index.js";import{log as w}from"../utils/log/logUtils.js";import{ValidationError as v,BusinessError as G,SystemError as A,ResourceError as D}from"../utils/error/errorHandler.js";import{resolveProjectPath as ht}from"../utils/common/projectPathUtils.js";import{isGitRepo as gt,ensureGitRepo as T,ensureGitignore as M,addAll as U,stageFiles as L,getDefaultAuthor as mt}from"../utils/git/gitUtils.js";function k(n){const{workspaceType:t,projectId:a,isolationContext:e,userId:s,cId:r}=n||{};if(!t||!["pageApp","taskAgent"].includes(t))throw new v("workspaceType is required and must be pageApp or taskAgent",{field:"workspaceType"});if(t==="taskAgent"){if(!s||!r)throw new v("taskAgent mode requires userId and cId",{field:"userId/cId"});const c=P.join(B.COMPUTER_WORKSPACE_DIR,String(s),String(r));if(!i.existsSync(c))throw new D("Computer workspace does not exist",{userId:s,cId:r});return{targetPath:c,logId:`computer:${s}:${r}`}}if(!a)throw new v("pageApp mode requires projectId",{field:"projectId"});const o=ht(a,e||{});if(!i.existsSync(o))throw new D("Project does not exist",{projectId:a});return{targetPath:o,logId:a}}function q(n){for(let t=0;t<Math.min(n.length,8e3);t++)if(n[t]===0)return!0;return!1}function C(n){const t=Buffer.from(`blob ${n.length}\0`);return dt.createHash("sha1").update(Buffer.concat([t,n])).digest("hex")}function wt(n,t,a,e,s,r,o){const d=ut(n,e?t:"",s?a:"",e?`a/${n}`:"/dev/null",s?`b/${n}`:"/dev/null").split(`
|
|
2
|
+
`).slice(4);d.length>0&&d[d.length-1]===""&&d.pop();const g=[];let u=0,h=0;for(const m of d){if(m.startsWith("@@")){const p=m.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);if(p){const S=p[1],F=p[2]!==void 0?parseInt(p[2],10):1,N=p[3],R=p[4]!==void 0?parseInt(p[4],10):1,$=F===1?S:`${S},${F}`,H=R===1?N:`${N},${R}`;g.push(`@@ -${$} +${H} @@`);continue}}m.startsWith("+")&&!m.startsWith("+++")?u++:m.startsWith("-")&&!m.startsWith("---")&&h++,g.push(m)}const f=e&&t!==""&&!t.endsWith(`
|
|
3
|
+
`),b=s&&a!==""&&!a.endsWith(`
|
|
4
|
+
`);if((f||b)&&g.length>0){let m=0;for(let R=0;R<g.length;R++)g[R].startsWith("@@")&&(m=R);let p=-1,S=-1,F=-1;for(let R=g.length-1;R>m;R--){const $=g[R];$.startsWith("-")&&!$.startsWith("---")&&p<0?p=R:$.startsWith("+")&&!$.startsWith("++")&&S<0?S=R:$.startsWith(" ")&&F<0&&(F=R)}const N=Math.max(p,S,F);if(N>=0){const R=g[N];if(R.startsWith(" "))(f||b)&&g.splice(N+1,0,"\");else if(R.startsWith("+")){let $=-1;for(let H=N-1;H>m;H--)if(g[H].startsWith("-")&&!g[H].startsWith("---")){$=H;break}$>=0&&f?(g.splice($+1,0,"\"),b&&g.splice(N+2,0,"\")):b&&g.splice(N+1,0,"\")}else R.startsWith("-")&&f&&g.splice(N+1,0,"\")}}const O=e&&r?C(r).substring(0,7):"0000000",x=s&&o?C(o).substring(0,7):"0000000",E=[];return E.push(`diff --git a/${n} b/${n}`),e?s?E.push(`index ${O}..${x} 100644`):(E.push("deleted file mode 100644"),E.push(`index ${O}..0000000`)):(E.push("new file mode 100644"),E.push(`index 0000000..${x}`)),E.push(`--- ${e?`a/${n}`:"/dev/null"}`),E.push(`+++ ${s?`b/${n}`:"/dev/null"}`),{diff:[...E,...g].join(`
|
|
5
|
+
`)+`
|
|
6
|
+
`,insertions:u,deletions:h}}async function W(n){return l.resolveRef({fs:i,dir:n,ref:"HEAD"})}async function _(n,t,a){try{const{blob:e}=await l.readBlob({fs:i,dir:n,oid:t,filepath:a}),s=Buffer.from(e);return{content:s.toString("utf8"),buf:s}}catch{return null}}async function z(n={}){const{targetPath:t,logId:a}=k(n);if(gt(t))return{success:!0,message:"Git repository already initialized",logId:a,alreadyExists:!0};try{return await l.init({fs:i,dir:t,defaultBranch:"main"}),M(t),await l.setConfig({fs:i,dir:t,path:"user.name",value:B.GIT_DEFAULT_AUTHOR_NAME}),await l.setConfig({fs:i,dir:t,path:"user.email",value:B.GIT_DEFAULT_AUTHOR_EMAIL}),w(a,"INFO","Git repository initialized",{logId:a,targetPath:t}),{success:!0,message:"Git repository initialized successfully",logId:a,alreadyExists:!1}}catch(e){throw w(a,"ERROR","Failed to initialize Git repository",{logId:a,error:e.message}),new A("Failed to initialize Git repository",{originalError:e.message})}}async function K(n={}){const{targetPath:t,logId:a}=k(n);await T(t);try{const e=await l.currentBranch({fs:i,dir:t,fullname:!1}),s=await l.statusMatrix({fs:i,dir:t}),r=[],o=[],c=[],y=[],d=[];for(const[g,u,h,f]of s)u!==f&&r.push(g),u===0&&f!==0&&c.push(g),(u===1&&f===0||h===0&&f!==0)&&y.push(g),f!==0&&h!==0&&h!==f&&o.push(g),u===0&&f===0&&h!==0&&d.push(g);return{success:!0,logId:a,current:e,staged:[...new Set(r)],modified:[...new Set(o)],created:[...new Set(c)],deleted:[...new Set(y)],untracked:[...new Set(d)],conflicted:[],ahead:0,behind:0,tracking:null}}catch(e){throw w(a,"ERROR","Failed to get Git status",{logId:a,error:e.message}),new A("Failed to get Git status",{originalError:e.message})}}async function V(n={}){const{message:t,files:a,authorName:e,authorEmail:s}=n;if(!t||typeof t!="string")throw new v("Commit message cannot be empty",{field:"message"});const{targetPath:r,logId:o}=k(n);await T(r);try{Array.isArray(a)&&a.length>0?await L(r,a):await U(r);const y=(await l.statusMatrix({fs:i,dir:r})).some(([,,,u])=>u!==1);if(!y)return{success:!0,message:"Nothing to commit",logId:o,nothingToCommit:!0};const d={name:e||B.GIT_DEFAULT_AUTHOR_NAME,email:s||B.GIT_DEFAULT_AUTHOR_EMAIL},g=await l.commit({fs:i,dir:r,message:t,author:d});return w(o,"INFO","Git commit successful",{logId:o,commitHash:g,message:t}),{success:!0,message:"Commit successful",logId:o,commit:g,summary:{changes:y?1:0}}}catch(c){throw w(o,"ERROR","Failed to commit",{logId:o,error:c.message}),new A("Failed to commit",{originalError:c.message})}}async function J(n={}){const{files:t}=n,{targetPath:a,logId:e}=k(n);await T(a);try{return Array.isArray(t)&&t.length>0?await L(a,t):await U(a),w(e,"INFO","Git add successful",{logId:e,filesCount:t?t.length:"all"}),{success:!0,message:"Files staged successfully",logId:e}}catch(s){throw w(e,"ERROR","Failed to add files",{logId:e,error:s.message}),new A("Failed to add files",{originalError:s.message})}}async function Q(n={}){const{files:t}=n,{targetPath:a,logId:e}=k(n);await T(a);try{if(Array.isArray(t)&&t.length>0){for(const o of t)await l.resetIndex({fs:i,dir:a,filepath:o});return w(e,"INFO","Git unstage specified files",{logId:e,files:t}),{success:!0,message:"Specified files unstaged successfully",logId:e,files:t}}const r=(await l.statusMatrix({fs:i,dir:a})).filter(([,,,o])=>o!==1).map(([o])=>o);for(const o of r)await l.resetIndex({fs:i,dir:a,filepath:o});return w(e,"INFO","Git unstage all files",{logId:e}),{success:!0,message:"All files unstaged successfully",logId:e,files:"all"}}catch(s){throw w(e,"ERROR","Failed to unstage",{logId:e,error:s.message}),new A("Failed to unstage files",{originalError:s.message})}}async function j(n,t){const a=new Set;for(const s of t){let r=P.dirname(s);for(;r&&r!==".";)a.add(r),r=P.dirname(r)}const e=[...a].sort((s,r)=>{const o=s.split(/[/\\]/).length;return r.split(/[/\\]/).length-o});for(const s of e){const r=P.join(n,s);try{(await i.promises.readdir(r)).length===0&&await i.promises.rmdir(r)}catch{}}}async function X(n={}){const{files:t}=n,{targetPath:a,logId:e}=k(n);await T(a);try{const s=await l.statusMatrix({fs:i,dir:a}),r=Array.isArray(t)&&t.length>0?new Set(t):null,o=[],c=[],y=[];for(const[u,h,f,b]of s)r&&!r.has(u)||(h!==0?(b!==1||f!==1)&&o.push(u):h===0&&b!==0?c.push(u):h===0&&b===0&&f!==0&&y.push(u));const d=await W(a);for(const u of o){const h=await _(a,d,u);if(!h)continue;const f=P.join(a,u);i.mkdirSync(P.dirname(f),{recursive:!0}),i.writeFileSync(f,h.buf),await l.add({fs:i,dir:a,filepath:u})}for(const u of c){try{await l.remove({fs:i,dir:a,filepath:u})}catch{}const h=P.join(a,u);i.existsSync(h)&&await i.promises.unlink(h)}c.length>0&&await j(a,c);for(const u of y){const h=P.join(a,u);i.existsSync(h)&&await i.promises.unlink(h)}y.length>0&&await j(a,y);const g=o.length+c.length+y.length;return w(e,"INFO","Git discard",{logId:e,trackedFiles:o.length,newFiles:c.length,untrackedFiles:y.length}),{success:!0,message:"Files discarded successfully",logId:e,discardedCount:g,trackedFiles:o,newFiles:c,untrackedFiles:y}}catch(s){throw w(e,"ERROR","Failed to discard",{logId:e,error:s.message}),new A("Failed to discard files",{originalError:s.message})}}async function Y(n={}){const{maxCount:t=50,branch:a,skip:e=0,filePath:s}=n,{targetPath:r,logId:o}=k(n);await T(r);try{const c=Math.min(Math.max(1,t),500),y=Math.max(0,e),d={fs:i,dir:r,depth:c+y};a&&(d.ref=a),s&&(d.filepath=s);const h=(await l.log(d)).slice(y,y+c).map(f=>({hash:f.oid,date:new Date(f.commit.author.timestamp*1e3).toISOString(),message:f.commit.message,author_name:f.commit.author.name,author_email:f.commit.author.email}));return{success:!0,logId:o,commits:h,total:h.length}}catch(c){throw w(o,"ERROR","Failed to get Git log",{logId:o,error:c.message}),new A("Failed to get Git log",{originalError:c.message})}}async function Z(n={}){const{source:t="worktree",from:a,to:e,paths:s}=n,{targetPath:r,logId:o}=k(n);await T(r);try{const c=Array.isArray(s)&&s.length>0?new Set(s):null;let y="";const d=[];let g=0,u=0;async function h(f,b,O){const x=!!b,E=!!O;if(x&&E&&b.content===O.content)return;let I=!1;if(x&&q(b.buf)&&(I=!0),E&&!I&&q(O.buf)&&(I=!0),I){const F=x?C(b.buf).substring(0,7):"0000000",N=E?C(O.buf).substring(0,7):"0000000",R=[`diff --git a/${f} b/${f}`];x?E?R.push(`index ${F}..${N} 100644`):(R.push("deleted file mode 100644"),R.push(`index ${F}..0000000`)):(R.push("new file mode 100644"),R.push(`index 0000000..${N}`)),R.push(`Binary files ${x?`a/${f}`:"/dev/null"} and ${E?`b/${f}`:"/dev/null"} differ`),y+=R.join(`
|
|
7
|
+
`)+`
|
|
8
|
+
`,d.push({file:f,changes:0,insertions:0,deletions:0,binary:!0});return}const{diff:m,insertions:p,deletions:S}=wt(f,x?b.content:"",E?O.content:"",x,E,x?b.buf:null,E?O.buf:null);y+=m,d.push({file:f,changes:p+S,insertions:p,deletions:S,binary:!1}),g+=p,u+=S}if(t==="commit"){let f,b;if(a&&e)f=a,b=e;else if(a){const I=await l.readCommit({fs:i,dir:r,oid:a});I.commit.parent&&I.commit.parent.length>0?(f=I.commit.parent[0],b=a):(f=null,b=a)}else throw new v("source=commit requires at least 'from'",{field:"from"});const O=f?new Set(await l.listFiles({fs:i,dir:r,ref:f})):new Set,x=new Set(await l.listFiles({fs:i,dir:r,ref:b})),E=new Set([...O,...x]);for(const I of E){if(c&&!c.has(I))continue;const m=O.has(I)?await _(r,f,I):null,p=x.has(I)?await _(r,b,I):null;await h(I,m,p)}}else{const f=await W(r),b=await l.statusMatrix({fs:i,dir:r});for(const[O,x,E,I]of b){if(c&&!c.has(O))continue;if(t==="staged"){if(I===1||x===0&&I===0)continue}else if(x===0&&I===0||x===1&&E===1&&I===1)continue;const m=x!==0?await _(r,f,O):null;let p=null;if(E!==0){const S=P.join(r,O);if(i.existsSync(S))try{const F=i.readFileSync(S);p={content:F.toString("utf8"),buf:F}}catch{}}await h(O,m,p)}}return{success:!0,logId:o,source:t,diff:y,summary:{files:d,insertions:g,deletions:u}}}catch(c){throw c instanceof v?c:(w(o,"ERROR","Failed to get Git diff",{logId:o,error:c.message}),new A("Failed to get Git diff",{originalError:c.message}))}}async function tt(n={}){const{ref:t="HEAD",filePath:a}=n;if(!a)throw new v("filePath is required",{field:"filePath"});const{targetPath:e,logId:s}=k(n);await T(e);try{let r;if(t==="worktree"||t==="staged"||t===""){const o=P.join(e,a);r=i.existsSync(o)?i.readFileSync(o,"utf8"):""}else try{const o=await l.resolveRef({fs:i,dir:e,ref:t}),{blob:c}=await l.readBlob({fs:i,dir:e,oid:o,filepath:a});r=Buffer.from(c).toString("utf8")}catch{r=""}return{success:!0,logId:s,filePath:a,ref:t,content:r}}catch(r){throw r instanceof D?r:(w(s,"ERROR","Failed to get file content",{logId:s,ref:t,filePath:a,error:r.message}),new A("Failed to get file content",{originalError:r.message}))}}async function et(n={}){const{target:t,mode:a="mixed"}=n;if(!t)throw new v("Reset target cannot be empty",{field:"target"});if(!["soft","mixed","hard"].includes(a))throw new v("Mode must be soft, mixed or hard",{field:"mode"});const{targetPath:e,logId:s}=k(n);await T(e);try{const r=await l.log({fs:i,dir:e,depth:1}),o=r.length>0?r[0].oid:null,c=await l.currentBranch({fs:i,dir:e,fullname:!1});if(!c)throw new G("Cannot reset: detached HEAD");const y=a==="hard"?new Set(await l.listFiles({fs:i,dir:e,ref:"HEAD"})):null;if(await l.writeRef({fs:i,dir:e,ref:`refs/heads/${c}`,value:t,force:!0}),a==="mixed"||a==="hard"){const d=await l.listFiles({fs:i,dir:e,ref:t}),g=new Set(d);for(const h of d)await l.resetIndex({fs:i,dir:e,filepath:h,ref:t});const u=await l.statusMatrix({fs:i,dir:e});for(const[h,,,f]of u)if(!g.has(h)&&f!==0)try{await l.remove({fs:i,dir:e,filepath:h})}catch{}}if(a==="hard"){const d=await l.listFiles({fs:i,dir:e,ref:t}),g=new Set(d);for(const u of d){const h=await _(e,t,u);if(!h)continue;const f=P.join(e,u);i.mkdirSync(P.dirname(f),{recursive:!0}),i.writeFileSync(f,h.buf)}if(y){for(const u of y)if(!g.has(u)){const h=P.join(e,u);i.existsSync(h)&&i.unlinkSync(h)}}M(e),await l.add({fs:i,dir:e,filepath:".gitignore"})}return w(s,"INFO","Git reset successful",{logId:s,target:t,mode:a,previousHead:o}),{success:!0,message:`Reset (${a}) to ${t} successful`,logId:s,target:t,mode:a,previousHead:o}}catch(r){throw r instanceof G?r:(w(s,"ERROR","Failed to reset",{logId:s,target:t,mode:a,error:r.message}),new A("Failed to reset",{originalError:r.message}))}}async function at(n={}){const{target:t,message:a,authorName:e,authorEmail:s}=n;if(!t)throw new v("Revert target cannot be empty",{field:"target"});const{targetPath:r,logId:o}=k(n);await T(r);try{let c;try{c=(await l.readCommit({fs:i,dir:r,oid:t})).oid}catch{throw new v("Revert target commit does not exist",{field:"target",target:t})}const y=await l.statusMatrix({fs:i,dir:r});if(y.some(([m,p,S,F])=>p===0&&F===0?!1:S!==1||F!==1)){const m=y.filter(([,F])=>F!==0),p=m.filter(([,,F])=>F!==1).map(([F])=>F),S=m.filter(([,,,F])=>F!==1).map(([F])=>F);throw new G("Working directory is not clean, please commit or stash your changes before reverting",{staged:S,modified:p})}const g=new Set(await l.listFiles({fs:i,dir:r,ref:c})),u=await W(r),h=new Set(await l.listFiles({fs:i,dir:r,ref:u})),f=[];for(const m of g){const p=await _(r,c,m);if(!p)continue;const S=P.join(r,m);i.mkdirSync(P.dirname(S),{recursive:!0}),i.writeFileSync(S,p.buf),await l.add({fs:i,dir:r,filepath:m})}for(const m of h)if(!g.has(m)){f.push(m);try{await l.remove({fs:i,dir:r,filepath:m})}catch{}const p=P.join(r,m);i.existsSync(p)&&await i.promises.unlink(p)}if(f.length>0&&await j(r,f),M(r),await l.add({fs:i,dir:r,filepath:".gitignore"}),!(await l.statusMatrix({fs:i,dir:r})).some(([,,,m])=>m!==1))return w(o,"INFO","Nothing to revert, current HEAD already matches target",{logId:o,target:c}),{success:!0,message:"Nothing to revert, already at target state",logId:o,nothingToCommit:!0,target:c};const x={name:e||B.GIT_DEFAULT_AUTHOR_NAME,email:s||B.GIT_DEFAULT_AUTHOR_EMAIL},E=a||`Revert to ${c.substring(0,7)}`,I=await l.commit({fs:i,dir:r,message:E,author:x});return w(o,"INFO","Git revert successful",{logId:o,target:c,commitHash:I,previousHead:u,removedFilesCount:f.length}),{success:!0,message:"Revert successful",logId:o,commit:I,target:c,previousHead:u}}catch(c){throw c instanceof v||c instanceof G?c:(w(o,"ERROR","Failed to revert",{logId:o,target:t,error:c.message}),new A("Failed to revert",{originalError:c.message}))}}async function rt(n={}){const{target:t}=n;if(!t)throw new v("Checkout target cannot be empty",{field:"target"});const{targetPath:a,logId:e}=k(n);await T(a);try{const s=await l.listFiles({fs:i,dir:a,ref:t});for(const r of s){const o=await _(a,t,r);if(!o)continue;const c=P.join(a,r);i.mkdirSync(P.dirname(c),{recursive:!0}),i.writeFileSync(c,o.buf),await l.add({fs:i,dir:a,filepath:r})}return M(a),await l.add({fs:i,dir:a,filepath:".gitignore"}),w(e,"INFO","Git checkout files successful",{logId:e,target:t}),{success:!0,message:`Checkout files from ${t} successful`,logId:e,target:t}}catch(s){throw w(e,"ERROR","Failed to checkout files",{logId:e,target:t,error:s.message}),new A("Failed to checkout files",{originalError:s.message})}}async function st(n={}){const{targetPath:t,logId:a}=k(n);await T(t);try{const e=await l.listTags({fs:i,dir:t});return{success:!0,logId:a,tags:e,latest:e.length>0?e[e.length-1]:null}}catch(e){throw w(a,"ERROR","Failed to list tags",{logId:a,error:e.message}),new A("Failed to list tags",{originalError:e.message})}}async function it(n={}){const{tagName:t,message:a}=n;if(!t)throw new v("Tag name cannot be empty",{field:"tagName"});const{targetPath:e,logId:s}=k(n);await T(e);try{return a?await l.annotatedTag({fs:i,dir:e,ref:t,message:a,tagger:mt()}):await l.tag({fs:i,dir:e,ref:t}),w(s,"INFO","Git tag created",{logId:s,tagName:t,annotated:!!a}),{success:!0,message:"Tag created successfully",logId:s,tagName:t}}catch(r){throw w(s,"ERROR","Failed to create tag",{logId:s,tagName:t,error:r.message}),new A("Failed to create tag",{originalError:r.message})}}async function nt(n={}){const{tagName:t}=n;if(!t)throw new v("Tag name cannot be empty",{field:"tagName"});const{targetPath:a,logId:e}=k(n);await T(a);try{return await l.deleteRef({fs:i,dir:a,ref:`refs/tags/${t}`}),w(e,"INFO","Git tag deleted",{logId:e,tagName:t}),{success:!0,message:"Tag deleted successfully",logId:e,tagName:t}}catch(s){throw w(e,"ERROR","Failed to delete tag",{logId:e,tagName:t,error:s.message}),new A("Failed to delete tag",{originalError:s.message})}}async function ot(n={}){const{targetPath:t,logId:a}=k(n);await T(t);try{const e=await l.listBranches({fs:i,dir:t}),s=await l.currentBranch({fs:i,dir:t,fullname:!1}),r={};for(const o of e)r[o]={name:o,current:o===s};return{success:!0,logId:a,branches:r,current:s}}catch(e){throw w(a,"ERROR","Failed to list branches",{logId:a,error:e.message}),new A("Failed to list branches",{originalError:e.message})}}async function ct(n={}){const{branchName:t,startPoint:a}=n;if(!t)throw new v("Branch name cannot be empty",{field:"branchName"});const{targetPath:e,logId:s}=k(n);await T(e);try{const r=a||"HEAD";return await l.branch({fs:i,dir:e,ref:t,object:r,checkout:!0}),w(s,"INFO","Git branch created",{logId:s,branchName:t,startPoint:a}),{success:!0,message:"Branch created and switched to",logId:s,branchName:t}}catch(r){throw w(s,"ERROR","Failed to create branch",{logId:s,branchName:t,error:r.message}),new A("Failed to create branch",{originalError:r.message})}}async function lt(n={}){const{branchName:t}=n;if(!t)throw new v("Branch name cannot be empty",{field:"branchName"});const{targetPath:a,logId:e}=k(n);await T(a);try{const s=await l.statusMatrix({fs:i,dir:a});if(s.some(([o,c,y,d])=>c===0&&d===0?!1:y!==1||d!==1)){const o=s.filter(([,d])=>d!==0),c=o.filter(([,,d])=>d!==1).map(([d])=>d),y=o.filter(([,,,d])=>d!==1).map(([d])=>d);throw new G("Working directory is not clean, please commit or stash your changes before switching branches",{staged:y,modified:c})}return await l.checkout({fs:i,dir:a,ref:t}),w(e,"INFO","Git branch switched",{logId:e,branchName:t}),{success:!0,message:"Branch switched successfully",logId:e,branchName:t}}catch(s){throw s instanceof G?s:(w(e,"ERROR","Failed to switch branch",{logId:e,branchName:t,error:s.message}),new A("Failed to switch branch",{originalError:s.message}))}}async function ft(n={}){const{branchName:t,force:a=!1}=n;if(!t)throw new v("Branch name cannot be empty",{field:"branchName"});const{targetPath:e,logId:s}=k(n);await T(e);try{if(await l.currentBranch({fs:i,dir:e,fullname:!1})===t)throw new G("Cannot delete the current branch, please switch to another branch first");return await l.deleteBranch({fs:i,dir:e,ref:t,force:a}),w(s,"INFO","Git branch deleted",{logId:s,branchName:t,force:a}),{success:!0,message:"Branch deleted successfully",logId:s,branchName:t}}catch(r){throw r instanceof G?r:(w(s,"ERROR","Failed to delete branch",{logId:s,branchName:t,error:r.message}),new A("Failed to delete branch",{originalError:r.message}))}}export{z as init,K as status,V as commit,J as add,Q as unstage,X as discard,Y as logHistory,Z as diff,tt as fileContent,et as reset,at as revert,rt as checkout,st as listTags,it as createTag,nt as deleteTag,ot as listBranches,ct as createBranch,lt as switchBranch,ft as deleteBranch};export default{init:z,status:K,commit:V,add:J,unstage:Q,discard:X,logHistory:Y,diff:Z,fileContent:tt,reset:et,revert:at,checkout:rt,listTags:st,createTag:it,deleteTag:nt,listBranches:ot,createBranch:ct,switchBranch:lt,deleteBranch:ft};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{log as r}from"../utils/log/logUtils.js";import E from"../appConfig/index.js";import w 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 p,BusinessError as C,SystemError as k,FileError as b,ResourceError as z}from"../utils/error/errorHandler.js";import{sanitizeSensitivePaths as W}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 G}from"../utils/common/npmrcUtils.js";import{copyNodeModulesFromCache as L}from"../utils/common/templateCacheUtils.js";import{resolveProjectPath as v}from"../utils/common/projectPathUtils.js";import{ensurePrimaryAgentDirs as te,syncAgents as ie}from"../utils/common/AgentWorkspaceUtils.js";async function J(e,i="react",l={}){const a=Date.now();if(!e)throw new p("Project ID cannot be empty",{field:"projectId"});const s=["react","vue3"];if(!s.includes(i))throw new p("Template type is invalid, only supports react or vue3",{field:"templateType",templateType:i,allowedValues:s});const c={react:E.INIT_PROJECT_NAME_REACT||"react-vite-template",vue3:E.INIT_PROJECT_NAME_VUE3||"vue3-vite-template"}[i],u=v(e,l);if(t.existsSync(u))throw new C(`Project directory ${e} already exists`,{projectId:e,projectPath:u});try{t.mkdirSync(u,{recursive:!0}),r(e,"INFO",`Project directory created successfully: ${u}`,{projectId:e});const f=E.INIT_PROJECT_DIR,y=w.join(f,`${c}.zip`),m=w.join(f,c);if(r(e,"DEBUG","Start checking template directory",{templateDir:m,templateZipPath:y}),!t.existsSync(m)){if(!t.existsSync(y))throw r(e,"ERROR",`Initialization template does not exist: ${y}`,{projectId:e,templateZipPath:y}),new z("Initialization template does not exist",{});if(r(e,"INFO",`Template directory does not exist, starting to unzip template: ${y}`,{projectId:e,templateZipPath:y}),await _(y,m),r(e,"INFO","Template unzip completed",{projectId:e}),!t.existsSync(m))throw new k("Template unzip directory still does not exist",{})}r(e,"DEBUG","Start copying template content to project directory",{templateDir:m,projectPath:u});const d=await t.promises.readdir(m,{withFileTypes:!0});for(const o of d){const h=w.join(m,o.name),N=w.join(u,o.name);o.isDirectory()?(await t.promises.mkdir(N,{recursive:!0}),await ee(h,N)):o.isFile()&&(await t.promises.mkdir(w.dirname(N),{recursive:!0}),await t.promises.copyFile(h,N))}r(e,"DEBUG","Try copying node_modules from template cache",{projectPath:u});const D=await L(u,e);return D.cached&&r(e,"INFO",`node_modules copied from cache: ${D.templateType}`,{elapsed:D.elapsed}),r(e,"DEBUG","Start creating .npmrc configuration file",{projectPath:u}),await G(u,e),r(e,"INFO",`Project ${e} initialized successfully`,{projectId:e,elapsedMs:Date.now()-a}),{success:!0,message:`Project ${e} created successfully`,projectPath:u}}catch(f){throw r(e,"ERROR",`Project ${e} initialization failed: ${f.message}`,{projectId:e,elapsedMs:Date.now()-a}),new k(`Project ${e} initialization failed: ${f.message}`,{projectId:e,projectPath:u,originalError:f.message})}}async function re(e){const i=await t.promises.readdir(e,{withFileTypes:!0}),l=E.TOP_LEVEL_NOISE_PATTERNS,a=i.filter(s=>{const n=s.name;return n.startsWith(".")?!1:!l.some(c=>c.endsWith("*")?n.startsWith(c.slice(0,-1)):n===c)});if(a.length===1&&a[0].isDirectory()){const s=w.join(e,a[0].name),n=w.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 c=await t.promises.readdir(n);for(const u of c){const f=w.join(n,u),y=w.join(e,u);try{await t.promises.rename(f,y)}catch(m){if(m.code==="EXDEV")await t.promises.cp(f,y,{recursive:!0}),await t.promises.rm(f,{recursive:!0,force:!0});else throw m}}await t.promises.rm(n,{recursive:!0,force:!0})}}async function U(e,i={}){if(!e)throw new p("Project ID cannot be empty",{field:"projectId"});const l=v(e,i);if(t.existsSync(l))try{r(e,"INFO",`Start cleaning project directory: ${l}`,{projectId:e}),await t.promises.rm(l,{recursive:!0,force:!0}),r(e,"INFO",`Project directory cleaned up: ${l}`,{projectId:e})}catch(a){throw r(e,"ERROR",`Failed to clean project directory: ${a.message}`,{projectId:e,projectPath:l,originalError:a.message}),new k(`Failed to clean project directory: ${a.message}`,{projectId:e,projectPath:l,originalError:a.message})}else r(e,"INFO",`Project directory does not exist, no need to clean: ${l}`,{projectId:e})}function se(e){if(!t.existsSync(e))return!0;try{return t.readdirSync(e).filter(a=>!a.startsWith(".")&&a!=="node_modules").length===0}catch(i){const l=w.basename(e);return r(l,"ERROR",`Failed to check if directory is empty: ${i.message}`,{dirPath:e}),!0}}async function V(e,i,l,a,s,n,c={}){const u=Date.now(),f=v(e,c);try{if(se(f))r(e,"INFO","Project directory is empty, directly deploying new project",{projectId:e});else{r(e,"INFO","Project directory is not empty, starting to backup current version",{projectId:e});const m=parseInt(a)-1,d=w.join(E.UPLOAD_PROJECT_DIR,e),D=w.join(d,`${e}-v${m}.zip`);if(t.existsSync(D))r(e,"INFO",`Backup file already exists, skipping backup: ${D}`,{projectId:e});else try{await O(e,m,c),r(e,"INFO",`Current version backed up: ${D}`,{projectId:e})}catch(o){throw r(e,"ERROR",`Failed to backup current version: ${o.message}`,{projectId:e}),new k(`Failed to backup current version: ${o.message}`,{projectId:e,originalError:o.message})}if(s&&!isNaN(Number(s))){const o=Number(s);r(e,"INFO",`Stopping old version dev server, PID: ${o}`,{projectId:e});try{await B(l,e,o,{strict:!0}),r(e,"INFO","Old version dev server stopped",{projectId:e})}catch(h){r(e,"WARN",`Failed to stop old version dev server: ${h.message}`,{projectId:e,pid:o})}}t.existsSync(f)&&(r(e,"INFO",`Cleaning project directory: ${f}`,{projectId:e}),await t.promises.rm(f,{recursive:!0,force:!0}))}return t.mkdirSync(f,{recursive:!0}),r(e,"INFO",`Project directory created successfully: ${f}`,{projectId:e}),r(e,"DEBUG","Start extracting zip file to project directory",{projectId:e,zipFilePath:i}),await _(i,f),r(e,"DEBUG","Zip file extracted successfully",{projectId:e}),r(e,"DEBUG","Check and remove top level folder",{projectId:e}),await re(f),r(e,"DEBUG","Check and remove node_modules folder",{projectId:e}),await j(f),r(e,"DEBUG","Try copying node_modules from template cache",{projectId:e}),await L(f,e),r(e,"DEBUG","Start creating .npmrc configuration file",{projectId:e}),await G(f,e),r(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(y){r(e,"ERROR",`Failed to upload project: ${y.message}`,{projectId:e,elapsedMs:Date.now()-u});try{await U(e,c),r(e,"INFO","Failed to upload project, project directory cleaned up",{projectId:e})}catch(m){r(e,"ERROR",`Failed to clean project directory: ${m.message}`,{projectId:e,originalError:m.message})}throw y.isOperational?y:new k(`Failed to upload project: ${y.message}`,{projectId:e,projectPath:f,zipFilePath:i,originalError:y.message})}}async function O(e,i,l={}){if(!e)throw new p("Project ID cannot be empty",{field:"projectId"});if(i==null)throw new p("codeVersion cannot be empty",{field:"codeVersion"});const a=Number(i);if(!Number.isFinite(a))throw new p("codeVersion must be a number",{field:"codeVersion"});const s=v(e,l);if(!t.existsSync(s))throw new z("Project does not exist",{projectId:e});const n=w.join(E.UPLOAD_PROJECT_DIR,e);t.existsSync(n)||t.mkdirSync(n,{recursive:!0});const c=`${e}-v${a}.zip`,u=w.join(n,c);return r(e,"DEBUG","Start backing up project to zip",{projectId:e,versionNum:a,outZipPath:u}),await I(e,s,u)}async function Z(e,i,l){if(!e)throw new p("Project ID cannot be empty",{field:"projectId"});if(!i)throw new p("codeVersion cannot be empty",{field:"codeVersion"});if(!l)throw new p("Please upload a zip file",{field:"zipFile"});const a=w.join(E.UPLOAD_PROJECT_DIR,e);t.existsSync(a)||t.mkdirSync(a,{recursive:!0});const s=l.path,n=w.join(a,`${e}-v${i}.zip`);try{try{t.renameSync(s,n)}catch(c){if(c.code==="EXDEV")t.copyFileSync(s,n),t.unlinkSync(s);else throw c}return r(e,"INFO","File saved successfully",{projectId:e,codeVersion:i,filePath:n}),{success:!0,filePath:n}}catch(c){if(r(e,"ERROR","Failed to move file",{projectId:e,codeVersion:i,error:c.message}),t.existsSync(s))try{t.unlinkSync(s)}catch(u){r(e,"ERROR","Failed to clean temporary file",{projectId:e,error:u.message})}throw new k("Failed to save file",{projectId:e,codeVersion:i,originalError:c.message})}}function ae(e){if(!e)return[];if(Array.isArray(e))return e.map(i=>typeof i=="string"?i.trim():"").filter(Boolean);if(typeof e=="string"){const i=e.trim();if(!i)return[];try{const l=JSON.parse(i);if(Array.isArray(l))return l.map(a=>typeof a=="string"?a.trim():"").filter(Boolean)}catch{}return[i]}return[]}async function X(e){t.existsSync(e)&&await t.promises.rm(e,{recursive:!0,force:!0})}async function q(e,i){try{await t.promises.rename(e,i)}catch(l){if(l.code==="EXDEV"){async function a(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 a(w.join(s,f),w.join(n,f))}else await t.promises.copyFile(s,n)}await a(e,i),await t.promises.rm(e,{recursive:!0,force:!0})}else throw l}}async function oe(e,i){const l=w.join(e,i);if(t.existsSync(l)&&(await t.promises.lstat(l)).isDirectory())return l;const a=await t.promises.readdir(e,{withFileTypes:!0});for(const s of a){if(!s.isDirectory())continue;const n=w.join(e,s.name,i);if(t.existsSync(n)&&(await t.promises.lstat(n)).isDirectory())return n}return null}async function ne(e,i,l){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 s=a.headers.get("content-type")||"";s&&!s.includes("zip")&&!s.includes("octet-stream")&&r(l,"WARN","Downloaded skill url content-type is not typical zip",{url:e,contentType:s});const n=await a.arrayBuffer(),c=Buffer.from(n);await t.promises.writeFile(i,c)}async function H(e,i,l,a={}){const s=String(e),n=Date.now(),c=ae(l),u=[],f=[];if(!e)throw new p("Project ID cannot be empty",{field:"projectId"});if(!i&&c.length===0)throw new p("file or skillUrls cannot both be empty",{field:"file|skillUrls"});if(i){if(!i.path)throw new p("Uploaded file has no valid path",{field:"file.path"});if(w.extname(i.originalname||i.filename||"").toLowerCase()!==".zip")throw new p("Only zip files are supported",{field:"file",originalName:i?.originalname})}const y=v(String(e),a);if(!t.existsSync(y))throw new p("Project does not exist",{field:"projectId"});const{skillsDir:m,agentTypes:d}=await te(y),D=w.join(E.UPLOAD_PROJECT_DIR,"temp");try{await t.promises.mkdir(m,{recursive:!0}),await t.promises.mkdir(D,{recursive:!0});const o=new Set;if(i){const P=w.join(D,`project_skill_push_${Date.now()}_${Math.round(Math.random()*1e6)}`);f.push(P),await t.promises.mkdir(P,{recursive:!0}),await _(i.path,P);const $=await oe(P,"skills");if(!$)r(s,"WARN","skills directory not found in uploaded zip",{projectId:e,extractRoot:P});else{const R=(await t.promises.readdir($,{withFileTypes:!0})).filter(F=>F.isDirectory()&&!F.name.startsWith("."));for(const F of R){const x=w.join($,F.name),T=w.join(m,F.name);t.existsSync(T)&&await X(T),await q(x,T),o.add(F.name)}}}for(let P=0;P<c.length;P+=1){const $=c[P],S=w.join(D,`project_skill_url_${Date.now()}_${P}_${Math.round(Math.random()*1e6)}.zip`);u.push(S);const R=w.join(D,`project_skill_url_extract_${Date.now()}_${P}_${Math.round(Math.random()*1e6)}`);f.push(R),await t.promises.mkdir(R,{recursive:!0}),await ne($,S,s),await _(S,R);const x=(await t.promises.readdir(R,{withFileTypes:!0})).filter(g=>g.isDirectory()&&!g.name.startsWith(".")),M=x.find(g=>g.name==="skills")?(await t.promises.readdir(w.join(R,"skills"),{withFileTypes:!0})).filter(g=>g.isDirectory()&&!g.name.startsWith(".")).map(g=>({name:g.name,sourcePath:w.join(R,"skills",g.name)})):x.map(g=>({name:g.name,sourcePath:w.join(R,g.name)}));if(M.length===0){r(s,"WARN","No skill directory found after extracting skill url zip",{projectId:e,skillUrl:$,extractRoot:R});continue}for(const g of M){const A=w.join(m,g.name);t.existsSync(A)&&await X(A),await q(g.sourcePath,A),o.add(g.name)}}const h=Array.from(o),N=h.length>0?`Pushed ${h.length} skills: ${h.join(", ")}`:"No valid skill directories found in file or skillUrls";return r(s,"INFO","Push skills to project workspace completed",{projectId:e,updatedSkills:h,skillUrlsCount:c.length,hasFile:!!i,agentTypes:d,elapsedMs:Date.now()-n}),await ie(y),{message:N,projectPath:y,updatedSkills:h}}catch(o){throw r(s,"ERROR","Push skills to project workspace failed",{projectId:e,error:o.message,elapsedMs:Date.now()-n}),o instanceof p||o instanceof b||o instanceof C||o instanceof k?o:new k(`Failed to push skills to project workspace: ${o.message}`,{projectId:e})}finally{for(const o of f)try{t.existsSync(o)&&await t.promises.rm(o,{recursive:!0,force:!0})}catch(h){r(s,"WARN","Failed to clean extracted skill temp dir",{extractRoot:o,error:h.message})}for(const o of u)try{t.existsSync(o)&&await t.promises.unlink(o)}catch(h){r(s,"WARN","Failed to clean downloaded skill zip",{zipPath:o,error:h.message})}try{i?.path&&t.existsSync(i.path)&&await t.promises.unlink(i.path)}catch(o){r(s,"WARN","Failed to clean uploaded skill zip",{tempZipPath:i?.path,error:o.message})}}}async function K(e,i,l,a={}){const s=Date.now();if(!e)throw new p("Project ID cannot be empty",{field:"projectId"});const n=null;try{if(i&&!isNaN(Number(i))){const m=Number(i);r(n,"INFO",`[delete-project] Stopping development server, PID: ${m}`,{projectId:e,pid:m});try{await B(l,e,m,{strict:!0}),r(n,"INFO","[delete-project] Development server stopped",{projectId:e})}catch(d){r(n,"WARN",`[delete-project] Failed to stop development server: ${d.message}`,{projectId:e,pid:m})}}const c=[w.join(E.UPLOAD_PROJECT_DIR,e),v(e,a),w.join(E.DIST_TARGET_DIR,e),w.join(E.LOG_BASE_DIR,e)],u=[],f=[];for(const m of c)if(t.existsSync(m))try{await t.promises.rm(m,{recursive:!0,force:!0}),u.push(m),r(n,"INFO",`[delete-project] Directory deleted successfully: ${m}`,{projectId:e})}catch(d){f.push({path:m,error:d.message}),r(n,"ERROR",`[delete-project] Directory deleted failed: ${m}`,{projectId:e,error:d.message})}else r(n,"INFO",`[delete-project] Directory does not exist, skipping deletion: ${m}`,{projectId:e});const y={success:!0,message:`Project ${e} deleted successfully`,projectId:e,deletedDirectories:u,failedDirectories:f};return f.length>0&&(y.message+=`, but ${f.length} directories deleted failed`,r(n,"WARN","[delete-project] Some directories deleted failed",{projectId:e,failedDirs:f})),r(n,"INFO",`[delete-project] Project deleted successfully: ${e}`,{projectId:e,elapsedMs:Date.now()-s}),y}catch(c){throw r(n,"ERROR",`[delete-project] Failed to delete project: ${c.message}`,{projectId:e,originalError:c.message,elapsedMs:Date.now()-s}),c.isOperational?c:new k(`Failed to delete project: ${c.message}`,{projectId:e,originalError:c.message})}}async function Q(e,i,l,a,s={}){const n=Date.now();if(!e)throw new p("Project ID cannot be empty",{field:"projectId"});if(i==null)throw new p("codeVersion cannot be empty",{field:"codeVersion"});const c=Number(i);if(!Number.isFinite(c))throw new p("codeVersion must be a number",{field:"codeVersion"});const u=v(e,s);if(!t.existsSync(u))throw new z("Project does not exist",{projectId:e});const f=w.join(E.UPLOAD_PROJECT_DIR,e),y=`${e}-v${c}.zip`,m=w.join(f,y);if(l!=="LATEST"){if(t.existsSync(m))return r(e,"INFO",`Using existing export file: ${m}`,{projectId:e,zipPath:m}),{success:!0,projectId:e,zipPath:m};throw new z(`Specified version zip file does not exist: ${m}`,{projectId:e,zipPath:m})}const d=w.join(u,"cpage_config.json");let D=!1;try{if(a)try{const h=JSON.stringify(a,null,2);await t.promises.writeFile(d,h,"utf8"),D=!0,r(e,"INFO",`Configuration file created successfully: ${d}`,{projectId:e,configFilePath:d})}catch(h){throw r(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})}r(e,"DEBUG","Start executing export and packaging",{projectId:e,codeVersion:i});const o=await O(e,i,s);return r(e,"INFO",`Project exported successfully: ${o}`,{projectId:e,zipPath:o,elapsedMs:Date.now()-n}),{success:!0,projectId:e,zipPath:o}}catch(o){throw r(e,"ERROR",`Failed to export project: ${o?.message}`,{projectId:e,elapsedMs:Date.now()-n}),o.isOperational?o:new k("Failed to export project",{projectId:e,originalError:o&&o.message?W(o.message):o&&o.message})}finally{if(D&&t.existsSync(d))try{await t.promises.unlink(d),r(e,"INFO",`Temporary configuration file deleted successfully: ${d}`,{projectId:e,configFilePath:d})}catch(o){r(e,"WARN",`Failed to delete temporary configuration file: ${o.message}`,{projectId:e,error:o.message})}}}async function Y(e,i,l={}){const a=Date.now();if(!e)throw new p("Project ID cannot be empty",{field:"projectId"});if(i==null)throw new p("codeVersion cannot be empty",{field:"codeVersion"});try{const s=await O(e,i,l);return r(e,"INFO",`Current version backed up successfully: ${s}`,{projectId:e,zipPath:s,elapsedMs:Date.now()-a}),{success:!0,projectId:e,zipPath:s}}catch(s){throw r(e,"ERROR",`Failed to backup current version: ${s?.message}`,{projectId:e,elapsedMs:Date.now()-a}),s.isOperational?s:new k("Failed to backup current version",{projectId:e,originalError:s&&s.message?W(s.message):s&&s.message})}}export{J as createProject,V as uploadProject,Y as backupCurrentVersion,Q as exportProject,O as backupProjectOfVersion,U as cleanupProjectDirectory,Z as handleFileUpload,H as pushSkillsToWorkspace,K as deleteProject};export default{createProject:J,uploadProject:V,backupCurrentVersion:Y,exportProject:Q,backupProjectOfVersion:O,cleanupProjectDirectory:U,handleFileUpload:Z,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 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 +1 @@
|
|
|
1
|
-
import v from"path";import c from"fs";import{log as t}from"../log/logUtils.js";import{BusinessError as f,FileError as g,ResourceError as S}from"../error/errorHandler.js";import{isProjectStarting as
|
|
1
|
+
import v from"path";import c from"fs";import{log as t}from"../log/logUtils.js";import{BusinessError as f,FileError as g,ResourceError as S}from"../error/errorHandler.js";import{isProjectStarting as w,addStartingProject as p,removeStartingProject as P,startDev_NonBlocking as N}from"./processManager.js";import h from"../error/errorCodes.js";import{stopDevServer as y}from"./stopDevUtils.js";import{removeNodeModules as F}from"../buildDependency/dependencyManager.js";import{createPnpmNpmrc as d}from"../common/npmrcUtils.js";import{copyNodeModulesFromCache as O}from"../common/templateCacheUtils.js";import{extractIsolationContext as R,resolveProjectPath as E}from"../common/projectPathUtils.js";async function x(r,e){t(e,"INFO","Start restarting development server",{projectId:e,requestId:r.requestId});const l=R(r?.query||{}),s=E(e,l),o=v.join(s,"package.json");if(!c.existsSync(o))throw t(e,"WARN","Project missing package.json file",{projectId:e,requestId:r.requestId}),new S("Project missing package.json file",{projectId:e,projectPath:s});let a;try{a=JSON.parse(c.readFileSync(o,"utf8"))}catch(i){throw new g("package.json file format error",{projectId:e,jsonFilePath:o,originalError:i.message})}const n=a.scripts.dev;if(!n)throw t(e,"WARN","Project missing dev script",{projectId:e,requestId:r.requestId}),new f("Project missing dev script",{projectId:e});if(w(e))throw new f("\u8BE5\u9879\u76EE\u6B63\u5728\u91CD\u542F\u4E2D\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",{projectId:e,code:h.PROJECT_STARTING});p(e);try{const i=r.query.pid;await y(r,e,i,{strict:!1,waitForStop:!0}),t(e,"INFO","Remove node_modules and restore from cache",{projectId:e,requestId:r.requestId}),await F(s,e),await O(s,e),t(e,"INFO","Ensure .npmrc with copy mode",{projectId:e,requestId:r.requestId}),await d(s,e),t(e,"INFO","Start starting dev server",{projectId:e,requestId:r.requestId});const{pid:m,port:u}=await N({req:r,projectId:e,projectPath:s,devScript:n});return t(e,"INFO","Dev server restart completed",{projectId:e,pid:m,port:u,requestId:r.requestId}),{success:!0,message:"Development server restart successfully",projectId:e,pid:m,port:u}}finally{P(e)}}export{x as restartDevServer};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import u from"path";import
|
|
1
|
+
import u from"path";import i from"fs";import{log as o}from"../log/logUtils.js";import{BusinessError as h,FileError as w,ResourceError as x}from"../error/errorHandler.js";import N from"../error/errorCodes.js";import{isProjectStarting as _,addStartingProject as A,removeStartingProject as L,startDev_NonBlocking as k}from"./processManager.js";import{removeNodeModules as O}from"../buildDependency/dependencyManager.js";import{createPnpmNpmrc as E}from"../common/npmrcUtils.js";import{copyNodeModulesFromCache as D}from"../common/templateCacheUtils.js";import{extractIsolationContext as F,resolveProjectPath as q}from"../common/projectPathUtils.js";async function W(t,e){if(_(e))throw new h("\u8BE5\u9879\u76EE\u6B63\u5728\u542F\u52A8\u4E2D\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",{projectId:e,code:N.PROJECT_STARTING});A(e);try{o(e,"INFO","Start starting development server",{projectId:e,requestId:t.requestId});const y=F(t?.query||{}),r=q(e,y),c=u.join(r,"package.json");if(!i.existsSync(c))throw o(e,"WARN","Project missing package.json file",{projectId:e,requestId:t.requestId}),new x("Project missing package.json file",{projectId:e,projectPath:r});let p;try{p=JSON.parse(i.readFileSync(c,"utf8"))}catch(s){throw new w("package.json file format error",{projectId:e,jsonFilePath:c,originalError:s.message})}const d=p.scripts.dev;if(!d)throw o(e,"WARN","Project missing dev script",{projectId:e,requestId:t.requestId}),new h("Project missing dev script, please add dev script in package.json",{projectId:e});try{if(process.platform==="linux"){const n=typeof process.report?.getReport=="function"?process.report.getReport():null,l=n&&n.header&&n.header.glibcVersionRuntime,m=!l,f=u.join(r,"node_modules",".pnpm");if(i.existsSync(f)){const g=await i.promises.readdir(f,{withFileTypes:!0}),S=g.some(a=>a.isDirectory()&&(a.name||"").includes("@rollup+rollup-linux-x64-gnu")),P=g.some(a=>a.isDirectory()&&(a.name||"").includes("@rollup+rollup-linux-x64-musl"));(m&&S||!m&&P)&&(o(e,"WARN","Detected Rollup native package does not match libc, clean dependencies and reinstall",{projectId:e,isMusl:m,glibcVersion:l||null}),await O(r,e))}}}catch(s){o(e,"WARN","Linux native package matching detection failed (ignore continue)",{error:s&&s.message})}try{process.env.ROLLUP_WASM=process.env.ROLLUP_WASM||"1",process.env.ROLLUP_DISABLE_NATIVE=process.env.ROLLUP_DISABLE_NATIVE||"1"}catch{}try{const s=u.join(r,"node_modules");let n=!1;try{i.lstatSync(s).isSymbolicLink()&&(i.existsSync(s)||(o(e,"WARN","node_modules symlink is broken, will restore from cache",{projectId:e,requestId:t.requestId}),n=!0))}catch{o(e,"INFO","node_modules not found, will try to copy from cache",{projectId:e,requestId:t.requestId}),n=!0}n&&(o(e,"INFO","Attempting to restore node_modules from template cache",{projectId:e,requestId:t.requestId}),await D(r,e))}catch(s){o(e,"WARN","Failed to check/restore node_modules, will proceed anyway",{projectId:e,requestId:t.requestId,error:s&&s.message})}await E(r,e),o(e,"INFO","Start executing dev script in non-blocking mode",{projectId:e,requestId:t.requestId});const{pid:R,port:v}=await k({req:t,projectId:e,projectPath:r,devScript:d});return{success:!0,message:"Development server started",projectId:e,pid:R,port:v}}finally{L(e)}}export{W as startDevServer};
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
import m from"fs";import h from"path";import N from"archiver";import I from"../../appConfig/index.js";import{log as r}from"../log/logUtils.js";import{ValidationError as g,SystemError as D}from"../error/errorHandler.js";const A=100*1024*1024,P=I.DOWNLOAD_MAX_FILE_SIZE_BYTES||A;async function O(f,u,a,c){const n=[],w=await m.promises.readdir(f,{withFileTypes:!0});w.sort((i,s)=>i.isDirectory()&&!s.isDirectory()?-1:!i.isDirectory()&&s.isDirectory()?1:i.name.toLowerCase().localeCompare(s.name.toLowerCase()));for(const i of w){const s=h.join(f,i.name);if(!(i.name.startsWith(".")||(I.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(i.name))&&!(i.isDirectory()&&I.TRAVERSE_EXCLUDE_DIRS.includes(i.name)))if(i.isDirectory()){const e=await O(s,u,a,c);if(e.length===0){const l=u||f,p=h.relative(l,s).replace(/\\/g,"/");n.push({name:p,isDir:!0})}else n.push(...e)}else try{const e=u||f,l=h.relative(e,s).replace(/\\/g,"/"),p=i.isSymbolicLink();let o=null;if(c){const y=l.split("/").map(R=>encodeURIComponent(R)).join("/");o=`${c}/${y}`}const t={name:l,isDir:!1,fileProxyUrl:o,isLink:p};n.push(t)}catch(e){r(a,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${s}`,{error:e.message})}}return n}async function k(f,u,a,c,n=""){const w=await m.promises.readdir(h.join(f,n),{withFileTypes:!0});let i=0;for(const s of w){const d=n?h.join(n,s.name):s.name,e=d.split(h.sep).filter(Boolean);if(e.some(t=>t.startsWith(".")))continue;const l=e[e.length-1];if(u.includes(l)||e.some(t=>a.includes(t)))continue;const p=h.join(f,d);let o;try{o=await m.promises.lstat(p)}catch(t){r(c,"WARN","Error occurred when getting file stats, skipping",{filePath:d.replace(/\\/g,"/"),error:t.message});continue}o.isSymbolicLink()||o.nlink>1||(o.isDirectory()?i+=await k(f,u,a,c,d):o.isFile()&&(i+=o.size))}return i}async function $(f,u,a){const c=Date.now(),n=`computer:${f}:${u}`,w=I.COMPUTER_WORKSPACE_DIR;if(!f)throw new g("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!u)throw new g("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const i=String(f),s=String(u),d=h.join(w,i,s);if(!m.existsSync(d))return r(n,"INFO","Directory does not exist, returning empty list",{targetDir:d,userId:i,cId:s}),{files:[]};r(n,"DEBUG","Start getting user file list",{targetDir:d,userId:i,cId:s});try{const e=await O(d,d,n,a);return r(n,"INFO","User file list obtained successfully",{fileCount:e.length,targetDir:d,userId:i,cId:s,elapsedMs:Date.now()-c}),{files:e}}catch(e){throw r(n,"ERROR","Failed to get user file list",{targetDir:d,userId:i,cId:s,error:e.message,elapsedMs:Date.now()-c}),new D(`Failed to get file list: ${e.message}`,{targetDir:d,originalError:e.message})}}async function _(f,u,a){const c=Date.now(),n=`computer:${f}:${u}`,w=I.COMPUTER_WORKSPACE_DIR;if(!f)throw new g("userId cannot be empty",{field:"userId"});if(!u)throw new g("cId cannot be empty",{field:"cId"});if(!Array.isArray(a))throw new g("files must be an array",{field:"files"});const i=String(f),s=String(u),d=h.join(w,i,s);m.existsSync(d)||m.mkdirSync(d,{recursive:!0});for(let e=0;e<a.length;e++){const l=a[e];if(!l||typeof l.operation!="string")throw new g(`files[${e}].operation cannot be empty`,{field:`files[${e}].operation`});if(!l.name||typeof l.name!="string")throw new g(`files[${e}].name cannot be empty`,{field:`files[${e}].name`});const p=l.operation.toLowerCase();if(!["create","delete","rename","modify"].includes(p))throw new g(`files[${e}].operation must be one of create, delete, rename or modify`,{field:`files[${e}].operation`});if(p==="rename"&&!l.renameFrom)throw new g(`files[${e}].renameFrom cannot be empty (rename operation requires)`,{field:`files[${e}].renameFrom`});if(p==="modify"&&l.isDir!==!0&&typeof l.contents!="string")throw new g(`files[${e}].contents must be a string (modify operation requires)`,{field:`files[${e}].contents`})}r(n,"DEBUG","Start updating user files",{userId:i,cId:s,filesCount:a.length});try{for(const e of a){const l=e.operation.toLowerCase(),p=e.name,o=h.normalize(p).replace(/^[\/\\]+/,""),t=h.join(d,o),y=h.resolve(t),R=h.resolve(d);if(!y.startsWith(R+h.sep)&&y!==R){r(n,"WARN","File path is not secure, skipping",{filePath:o,resolvedPath:y});continue}switch(l){case"create":{if(e.isDir===!0){if(m.existsSync(t)){if((await m.promises.stat(t)).isFile())throw new g("Cannot create directory, file with the same name already exists",{filePath:o});r(n,"INFO","Directory already exists, skipping creation",{filePath:o});break}await m.promises.mkdir(t,{recursive:!0}),r(n,"INFO","Directory created successfully",{filePath:o});break}if(m.existsSync(t)){if((await m.promises.stat(t)).isDirectory())throw new g("Cannot create file, directory with the same name already exists",{filePath:o});r(n,"INFO","File already exists, skipping creation",{filePath:o});break}await m.promises.mkdir(h.dirname(t),{recursive:!0});const F=e.contents||"";await m.promises.writeFile(t,F,"utf8"),r(n,"INFO","File created successfully",{filePath:o});break}case"delete":{m.existsSync(t)?(await m.promises.stat(t)).isDirectory()?(await m.promises.rm(t,{recursive:!0,force:!0}),r(n,"INFO","Directory deleted successfully",{filePath:o})):(await m.promises.unlink(t),r(n,"INFO","File deleted successfully",{filePath:o})):r(n,"WARN","The file or directory to be deleted does not exist",{filePath:o});break}case"rename":{const F=e.renameFrom;if(!F||typeof F!="string"){r(n,"WARN","Rename operation missing renameFrom",{filePath:o});break}const E=h.normalize(F).replace(/^[\/\\]+/,""),S=h.join(d,E),b=h.resolve(S);if(!b.startsWith(R+h.sep)&&b!==R){r(n,"WARN","Source path is not secure, skipping rename",{sourcePath:E,targetPath:o});break}if(m.existsSync(S)){const C=(await m.promises.stat(S)).isDirectory();await m.promises.mkdir(h.dirname(t),{recursive:!0}),await m.promises.rename(S,t),r(n,"INFO",C?"Directory renamed successfully":"File renamed successfully",{sourcePath:E,targetPath:o})}else r(n,"WARN","The file or directory to be renamed does not exist",{sourcePath:E});break}case"modify":{if(!m.existsSync(t)){r(n,"WARN","The file to be modified does not exist",{filePath:o});break}if((await m.promises.stat(t)).isDirectory()){r(n,"INFO","The target is a directory, skipping modification",{filePath:o});break}const E=typeof e.contents=="string"?e.contents:"";if(await m.promises.readFile(t,"utf8")===E){r(n,"INFO","File content has no changes, skipping write",{filePath:o});break}await m.promises.writeFile(t,E,"utf8"),r(n,"INFO","File modified successfully",{filePath:o});break}default:{r(n,"WARN","Unsupported operation type",{operation:l,filePath:o});break}}}return r(n,"INFO","User files updated successfully",{userId:i,cId:s,filesCount:a.length,elapsedMs:Date.now()-c}),{success:!0,message:"User files updated successfully",userId:i,cId:s,filesCount:a.length}}catch(e){throw r(n,"ERROR","User files updated failed",{userId:i,cId:s,error:e.message,elapsedMs:Date.now()-c}),new D(`User files updated failed: ${e.message}`,{userId:i,cId:s,originalError:e.message})}}async function z(f,u,a,c){const n=Date.now(),w=`computer:${f}:${u}`,i=I.COMPUTER_WORKSPACE_DIR;if(!f)throw new g("userId cannot be empty",{field:"userId"});if(!u)throw new g("cId cannot be empty",{field:"cId"});if(!a)throw new g("file cannot be empty",{field:"file"});if(!c||typeof c!="string")throw new g("filePath cannot be empty",{field:"filePath"});const s=String(f),d=String(u),e=h.join(i,s,d);m.existsSync(e)||m.mkdirSync(e,{recursive:!0});const l=h.normalize(c).replace(/^[\/\\]+/,""),p=h.join(e,l),o=h.resolve(p),t=h.resolve(e);if(!o.startsWith(t+h.sep)&&o!==t)throw new g("File path is not secure, cannot exceed user directory",{field:"filePath",providedPath:c,resolvedPath:o});try{if(await m.promises.mkdir(h.dirname(p),{recursive:!0}),a.buffer)await m.promises.writeFile(p,a.buffer);else if(typeof a.contents=="string")await m.promises.writeFile(p,a.contents,"utf8");else throw new g("File content format is incorrect",{field:"file",hasBuffer:!!a.buffer,hasContents:typeof a.contents});return r(w,"INFO","File uploaded successfully",{userId:s,cId:d,filePath:l,targetPath:o,fileSize:a.buffer?a.buffer.length:a.contents?a.contents.length:0,elapsedMs:Date.now()-n}),{success:!0,message:"File uploaded successfully",fileSize:a.buffer?a.buffer.length:a.contents?a.contents.length:0}}catch(y){throw r(w,"ERROR","File upload failed",{userId:s,cId:d,filePath:l,error:y.message,elapsedMs:Date.now()-n}),new D(`File upload failed: ${y.message}`,{userId:s,cId:d,filePath:l,originalError:y.message})}}async function T(f,u,a,c){const n=Date.now(),w=`computer:${f}:${u}`;if(!f)throw new g("userId cannot be empty",{field:"userId"});if(!u)throw new g("cId cannot be empty",{field:"cId"});if(!Array.isArray(a))throw new g("files must be an array",{field:"files"});if(!Array.isArray(c))throw new g("filePaths must be an array",{field:"filePaths"});if(a.length!==c.length)throw new g(`File count (${a.length}) does not match path count (${c.length})`,{field:"filePaths"});r(w,"DEBUG","Start batch uploading files",{userId:f,cId:u,filesCount:a.length});const i=[];try{for(let e=0;e<a.length;e++){const l=a[e],p=c[e];if(!l){r(w,"WARN","Empty file object encountered in batch upload, skipping",{index:e,filePath:p}),i.push({success:!1,filePath:p,error:"Empty file object"});continue}if(!p||typeof p!="string"){r(w,"WARN","Invalid file path in batch upload, skipping",{index:e,originalname:l.originalname}),i.push({success:!1,filePath:p||"",originalname:l.originalname,error:"Invalid file path"});continue}try{const o=await z(f,u,l,p);i.push({success:!0,filePath:p,originalname:l.originalname,...o})}catch(o){r(w,"ERROR","Single file upload failed in batch upload",{filePath:p,originalname:l.originalname,error:o.message}),i.push({success:!1,filePath:p,originalname:l.originalname,error:o.message})}}const s=i.filter(e=>e.success).length,d=i.filter(e=>!e.success).length;return r(w,"INFO","Batch upload files completed",{userId:f,cId:u,totalCount:a.length,successCount:s,failCount:d,elapsedMs:Date.now()-n}),{success:!0,message:"Batch upload completed",totalCount:a.length,successCount:s,failCount:d,results:i}}catch(s){throw r(w,"ERROR","Batch upload files failed",{userId:f,cId:u,error:s.message,elapsedMs:Date.now()-n}),new D(`Batch upload files failed: ${s.message}`,{userId:f,cId:u,originalError:s.message})}}async function v(f,u){const a=Date.now(),c=`computer:${f}:${u}`,n=I.COMPUTER_WORKSPACE_DIR;if(!f)throw new g("userId cannot be empty",{field:"userId"});if(!u)throw new g("cId cannot be empty",{field:"cId"});if(!n)throw new D("COMPUTER_WORKSPACE_DIR is not configured, cannot create zip");const w=String(f),i=String(u),s=h.join(n,w,i);if(!m.existsSync(s)){const t=`${w}_${i}.zip`;r(c,"WARN","Workspace directory does not exist, returning empty zip",{targetDir:s,userId:w,cId:i,zipFileName:t});const y=N("zip",{zlib:{level:9}});return y.append(null,{name:`${w}_${i}/`,type:"directory"}),y.on("warning",R=>{if(R.code==="ENOENT")r(c,"WARN","Encountered file problem when creating empty zip",{message:R.message,code:R.code});else throw r(c,"ERROR","Encountered warning when creating empty zip",{message:R.message,code:R.code}),R}),y.on("error",R=>{r(c,"ERROR","Failed to create empty zip",{message:R.message})}),{archive:y,zipFileName:t}}const d=`${w}_${i}.zip`;r(c,"DEBUG","Start creating workspace directory zip",{targetDir:s,zipFileName:d});const e=N("zip",{zlib:{level:9}}),l=I.CONTENT_TRAVERSE_EXCLUDE_FILES||[],p=I.TRAVERSE_EXCLUDE_DIRS||[],o=await k(s,l,p,c);if(o>P){const t=P/1024/1024,y=(o/1024/1024).toFixed(2);throw r(c,"WARN","Download rejected due to oversized workspace",{targetDir:s,downloadableSize:o,maxSizeBytes:P}),new g(`Download failed: total file size ${y}MB exceeds limit ${t}MB`,{field:"downloadSize",downloadableSize:o,maxSizeBytes:P})}return e.directory(s,`${w}_${i}`,t=>{const y=t.name||"",R=y.split(/[\/\\]/).filter(Boolean);if(R.some(E=>E.startsWith(".")))return!1;const F=R[R.length-1];if(l.includes(F)||R.some(E=>p.includes(E)))return!1;try{const E=h.join(s,y),S=m.lstatSync(E);if(S.isSymbolicLink()||S.nlink>1)return!1}catch(E){return r(c,"WARN","Error occurred when detecting link file, skipping",{filePath:y,error:E.message}),!1}return t}),e.on("warning",t=>{if(t.code==="ENOENT")r(c,"WARN","Encountered file problem when creating zip",{message:t.message,code:t.code});else throw r(c,"ERROR","Encountered warning when creating zip",{message:t.message,code:t.code}),t}),e.on("error",t=>{r(c,"ERROR","Failed to create zip",{message:t.message,elapsedMs:Date.now()-a})}),e.on("end",()=>{r(c,"INFO","Workspace directory zip created successfully",{targetDir:s,zipFileName:d,elapsedMs:Date.now()-a})}),{archive:e,zipFileName:d}}export{$ as getFileList,_ as updateFiles,z as uploadFile,T as uploadFiles,v as downloadAllFiles};
|
|
1
|
+
import m from"fs";import h from"path";import C from"archiver";import P from"../../appConfig/index.js";import{log as s}from"../log/logUtils.js";import{ValidationError as y,SystemError as z}from"../error/errorHandler.js";const _=100*1024*1024,O=P.DOWNLOAD_MAX_FILE_SIZE_BYTES||_;async function x(d,u,n,g,f){const r=[],w=await m.promises.readdir(d,{withFileTypes:!0});w.sort((i,t)=>i.isDirectory()&&!t.isDirectory()?-1:!i.isDirectory()&&t.isDirectory()?1:i.name.toLowerCase().localeCompare(t.name.toLowerCase()));for(const i of w){const t=h.join(d,i.name);if(!(i.name.startsWith(".")&&i.name!==".gitignore"||(P.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(i.name))&&!(i.isDirectory()&&P.TRAVERSE_EXCLUDE_DIRS.includes(i.name)))if(i.isDirectory()){const e=await x(t,u,n,g,f);if(e.length===0){const c=u||d,l=h.relative(c,t).replace(/\\/g,"/");r.push({name:l,isDir:!0})}else r.push(...e)}else try{const e=u||d,c=h.relative(e,t).replace(/\\/g,"/"),l=i.isSymbolicLink();let o=null;if(g){const R=c.split("/").map(E=>encodeURIComponent(E)).join("/");o=`${g}/${R}`,f&&(o+=`?customTargetDir=${encodeURIComponent(f)}`)}const a={name:c,isDir:!1,fileProxyUrl:o,isLink:l};r.push(a)}catch(e){s(n,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${t}`,{error:e.message})}}return r}async function A(d,u,n,g,f=""){const r=await m.promises.readdir(h.join(d,f),{withFileTypes:!0});let w=0;for(const i of r){const t=f?h.join(f,i.name):i.name,p=t.split(h.sep).filter(Boolean);if(p.some(o=>o.startsWith(".")))continue;const e=p[p.length-1];if(u.includes(e)||p.some(o=>n.includes(o)))continue;const c=h.join(d,t);let l;try{l=await m.promises.lstat(c)}catch(o){s(g,"WARN","Error occurred when getting file stats, skipping",{filePath:t.replace(/\\/g,"/"),error:o.message});continue}l.isSymbolicLink()||l.nlink>1||(l.isDirectory()?w+=await A(d,u,n,g,t):l.isFile()&&(w+=l.size))}return w}async function L(d,u,n,g){const f=Date.now(),r=`computer:${d}:${u}`,w=P.COMPUTER_WORKSPACE_DIR;if(!d)throw new y("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!u)throw new y("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const i=String(d),t=String(u),p=g&&g.trim()?g:h.join(w,i,t);if(!m.existsSync(p))return s(r,"INFO","Directory does not exist, returning empty list",{targetDir:p,userId:i,cId:t}),{files:[]};s(r,"DEBUG","Start getting user file list",{targetDir:p,userId:i,cId:t});try{const e=await x(p,p,r,n,g);return s(r,"INFO","User file list obtained successfully",{fileCount:e.length,targetDir:p,userId:i,cId:t,elapsedMs:Date.now()-f}),{files:e}}catch(e){throw s(r,"ERROR","Failed to get user file list",{targetDir:p,userId:i,cId:t,error:e.message,elapsedMs:Date.now()-f}),new z(`Failed to get file list: ${e.message}`,{targetDir:p,originalError:e.message})}}async function W(d,u,n,g){const f=Date.now(),r=`computer:${d}:${u}`,w=P.COMPUTER_WORKSPACE_DIR;if(!d)throw new y("userId cannot be empty",{field:"userId"});if(!u)throw new y("cId cannot be empty",{field:"cId"});if(!Array.isArray(n))throw new y("files must be an array",{field:"files"});const i=String(d),t=String(u),p=g&&g.trim()?g:h.join(w,i,t);m.existsSync(p)||m.mkdirSync(p,{recursive:!0});for(let e=0;e<n.length;e++){const c=n[e];if(!c||typeof c.operation!="string")throw new y(`files[${e}].operation cannot be empty`,{field:`files[${e}].operation`});if(!c.name||typeof c.name!="string")throw new y(`files[${e}].name cannot be empty`,{field:`files[${e}].name`});const l=c.operation.toLowerCase();if(!["create","delete","rename","modify"].includes(l))throw new y(`files[${e}].operation must be one of create, delete, rename or modify`,{field:`files[${e}].operation`});if(l==="rename"&&!c.renameFrom)throw new y(`files[${e}].renameFrom cannot be empty (rename operation requires)`,{field:`files[${e}].renameFrom`});if(l==="modify"&&c.isDir!==!0&&typeof c.contents!="string")throw new y(`files[${e}].contents must be a string (modify operation requires)`,{field:`files[${e}].contents`})}s(r,"DEBUG","Start updating user files",{userId:i,cId:t,filesCount:n.length});try{for(const e of n){const c=e.operation.toLowerCase(),l=e.name,o=h.normalize(l).replace(/^[\/\\]+/,""),a=h.join(p,o),R=h.resolve(a),E=h.resolve(p);if(!R.startsWith(E+h.sep)&&R!==E){s(r,"WARN","File path is not secure, skipping",{filePath:o,resolvedPath:R});continue}switch(c){case"create":{if(e.isDir===!0){if(m.existsSync(a)){if((await m.promises.stat(a)).isFile())throw new y("Cannot create directory, file with the same name already exists",{filePath:o});s(r,"INFO","Directory already exists, skipping creation",{filePath:o});break}await m.promises.mkdir(a,{recursive:!0}),s(r,"INFO","Directory created successfully",{filePath:o});break}if(m.existsSync(a)){if((await m.promises.stat(a)).isDirectory())throw new y("Cannot create file, directory with the same name already exists",{filePath:o});s(r,"INFO","File already exists, skipping creation",{filePath:o});break}await m.promises.mkdir(h.dirname(a),{recursive:!0});const S=e.contents||"";await m.promises.writeFile(a,S,"utf8"),s(r,"INFO","File created successfully",{filePath:o});break}case"delete":{m.existsSync(a)?(await m.promises.stat(a)).isDirectory()?(await m.promises.rm(a,{recursive:!0,force:!0}),s(r,"INFO","Directory deleted successfully",{filePath:o})):(await m.promises.unlink(a),s(r,"INFO","File deleted successfully",{filePath:o})):s(r,"WARN","The file or directory to be deleted does not exist",{filePath:o});break}case"rename":{const S=e.renameFrom;if(!S||typeof S!="string"){s(r,"WARN","Rename operation missing renameFrom",{filePath:o});break}const I=h.normalize(S).replace(/^[\/\\]+/,""),D=h.join(p,I),N=h.resolve(D);if(!N.startsWith(E+h.sep)&&N!==E){s(r,"WARN","Source path is not secure, skipping rename",{sourcePath:I,targetPath:o});break}if(m.existsSync(D)){const b=(await m.promises.stat(D)).isDirectory();await m.promises.mkdir(h.dirname(a),{recursive:!0}),await m.promises.rename(D,a),s(r,"INFO",b?"Directory renamed successfully":"File renamed successfully",{sourcePath:I,targetPath:o})}else s(r,"WARN","The file or directory to be renamed does not exist",{sourcePath:I});break}case"modify":{if(!m.existsSync(a)){s(r,"WARN","The file to be modified does not exist",{filePath:o});break}if((await m.promises.stat(a)).isDirectory()){s(r,"INFO","The target is a directory, skipping modification",{filePath:o});break}const I=typeof e.contents=="string"?e.contents:"";if(await m.promises.readFile(a,"utf8")===I){s(r,"INFO","File content has no changes, skipping write",{filePath:o});break}await m.promises.writeFile(a,I,"utf8"),s(r,"INFO","File modified successfully",{filePath:o});break}default:{s(r,"WARN","Unsupported operation type",{operation:c,filePath:o});break}}}return s(r,"INFO","User files updated successfully",{userId:i,cId:t,filesCount:n.length,elapsedMs:Date.now()-f}),{success:!0,message:"User files updated successfully",userId:i,cId:t,filesCount:n.length}}catch(e){throw s(r,"ERROR","User files updated failed",{userId:i,cId:t,error:e.message,elapsedMs:Date.now()-f}),new z(`User files updated failed: ${e.message}`,{userId:i,cId:t,originalError:e.message})}}async function $(d,u,n,g,f){const r=Date.now(),w=`computer:${d}:${u}`,i=P.COMPUTER_WORKSPACE_DIR;if(!d)throw new y("userId cannot be empty",{field:"userId"});if(!u)throw new y("cId cannot be empty",{field:"cId"});if(!n)throw new y("file cannot be empty",{field:"file"});if(!g||typeof g!="string")throw new y("filePath cannot be empty",{field:"filePath"});const t=String(d),p=String(u),e=f&&f.trim()?f:h.join(i,t,p);m.existsSync(e)||m.mkdirSync(e,{recursive:!0});const c=h.normalize(g).replace(/^[\/\\]+/,""),l=h.join(e,c),o=h.resolve(l),a=h.resolve(e);if(!o.startsWith(a+h.sep)&&o!==a)throw new y("File path is not secure, cannot exceed user directory",{field:"filePath",providedPath:g,resolvedPath:o});try{if(await m.promises.mkdir(h.dirname(l),{recursive:!0}),n.buffer)await m.promises.writeFile(l,n.buffer);else if(typeof n.contents=="string")await m.promises.writeFile(l,n.contents,"utf8");else throw new y("File content format is incorrect",{field:"file",hasBuffer:!!n.buffer,hasContents:typeof n.contents});return s(w,"INFO","File uploaded successfully",{userId:t,cId:p,filePath:c,targetPath:o,fileSize:n.buffer?n.buffer.length:n.contents?n.contents.length:0,elapsedMs:Date.now()-r}),{success:!0,message:"File uploaded successfully",fileSize:n.buffer?n.buffer.length:n.contents?n.contents.length:0}}catch(R){throw s(w,"ERROR","File upload failed",{userId:t,cId:p,filePath:c,error:R.message,elapsedMs:Date.now()-r}),new z(`File upload failed: ${R.message}`,{userId:t,cId:p,filePath:c,originalError:R.message})}}async function v(d,u,n,g,f){const r=Date.now(),w=`computer:${d}:${u}`;if(!d)throw new y("userId cannot be empty",{field:"userId"});if(!u)throw new y("cId cannot be empty",{field:"cId"});if(!Array.isArray(n))throw new y("files must be an array",{field:"files"});if(!Array.isArray(g))throw new y("filePaths must be an array",{field:"filePaths"});if(n.length!==g.length)throw new y(`File count (${n.length}) does not match path count (${g.length})`,{field:"filePaths"});s(w,"DEBUG","Start batch uploading files",{userId:d,cId:u,filesCount:n.length});const i=[];try{for(let e=0;e<n.length;e++){const c=n[e],l=g[e];if(!c){s(w,"WARN","Empty file object encountered in batch upload, skipping",{index:e,filePath:l}),i.push({success:!1,filePath:l,error:"Empty file object"});continue}if(!l||typeof l!="string"){s(w,"WARN","Invalid file path in batch upload, skipping",{index:e,originalname:c.originalname}),i.push({success:!1,filePath:l||"",originalname:c.originalname,error:"Invalid file path"});continue}try{const o=await $(d,u,c,l,f);i.push({success:!0,filePath:l,originalname:c.originalname,...o})}catch(o){s(w,"ERROR","Single file upload failed in batch upload",{filePath:l,originalname:c.originalname,error:o.message}),i.push({success:!1,filePath:l,originalname:c.originalname,error:o.message})}}const t=i.filter(e=>e.success).length,p=i.filter(e=>!e.success).length;return s(w,"INFO","Batch upload files completed",{userId:d,cId:u,totalCount:n.length,successCount:t,failCount:p,elapsedMs:Date.now()-r}),{success:!0,message:"Batch upload completed",totalCount:n.length,successCount:t,failCount:p,results:i}}catch(t){throw s(w,"ERROR","Batch upload files failed",{userId:d,cId:u,error:t.message,elapsedMs:Date.now()-r}),new z(`Batch upload files failed: ${t.message}`,{userId:d,cId:u,originalError:t.message})}}async function U(d,u,n){const g=Date.now(),f=`computer:${d}:${u}`,r=P.COMPUTER_WORKSPACE_DIR;if(!d)throw new y("userId cannot be empty",{field:"userId"});if(!u)throw new y("cId cannot be empty",{field:"cId"});if(!r)throw new z("COMPUTER_WORKSPACE_DIR is not configured, cannot create zip");const w=String(d),i=String(u),t=n&&n.trim()?n:h.join(r,w,i);if(!m.existsSync(t)){const a=`${w}_${i}.zip`;s(f,"WARN","Workspace directory does not exist, returning empty zip",{targetDir:t,userId:w,cId:i,zipFileName:a});const R=C("zip",{zlib:{level:9}});return R.append(null,{name:`${w}_${i}/`,type:"directory"}),R.on("warning",E=>{if(E.code==="ENOENT")s(f,"WARN","Encountered file problem when creating empty zip",{message:E.message,code:E.code});else throw s(f,"ERROR","Encountered warning when creating empty zip",{message:E.message,code:E.code}),E}),R.on("error",E=>{s(f,"ERROR","Failed to create empty zip",{message:E.message})}),{archive:R,zipFileName:a}}const p=`${w}_${i}.zip`;s(f,"DEBUG","Start creating workspace directory zip",{targetDir:t,zipFileName:p});const e=C("zip",{zlib:{level:9}}),c=P.CONTENT_TRAVERSE_EXCLUDE_FILES||[],l=P.TRAVERSE_EXCLUDE_DIRS||[],o=await A(t,c,l,f);if(o>O){const a=O/1024/1024,R=(o/1024/1024).toFixed(2);throw s(f,"WARN","Download rejected due to oversized workspace",{targetDir:t,downloadableSize:o,maxSizeBytes:O}),new y(`Download failed: total file size ${R}MB exceeds limit ${a}MB`,{field:"downloadSize",downloadableSize:o,maxSizeBytes:O})}return e.directory(t,`${w}_${i}`,a=>{const R=a.name||"",E=R.split(/[\/\\]/).filter(Boolean);if(E.some(I=>I.startsWith(".")))return!1;const S=E[E.length-1];if(c.includes(S)||E.some(I=>l.includes(I)))return!1;try{const I=h.join(t,R),D=m.lstatSync(I);if(D.isSymbolicLink()||D.nlink>1)return!1}catch(I){return s(f,"WARN","Error occurred when detecting link file, skipping",{filePath:R,error:I.message}),!1}return a}),e.on("warning",a=>{if(a.code==="ENOENT")s(f,"WARN","Encountered file problem when creating zip",{message:a.message,code:a.code});else throw s(f,"ERROR","Encountered warning when creating zip",{message:a.message,code:a.code}),a}),e.on("error",a=>{s(f,"ERROR","Failed to create zip",{message:a.message,elapsedMs:Date.now()-g})}),e.on("end",()=>{s(f,"INFO","Workspace directory zip created successfully",{targetDir:t,zipFileName:p,elapsedMs:Date.now()-g})}),{archive:e,zipFileName:p}}async function M(d,u,n=200){const g=Date.now(),f=`computer:${d}:${u}`;if(!d)throw new y("userId cannot be empty",{field:"userId"});if(!u)throw new y("cId cannot be empty",{field:"cId"});const r=String(d),w=String(u),i=P.COMPUTER_WORKSPACE_DIR;if(!i)return s(f,"WARN","COMPUTER_WORKSPACE_DIR is not configured"),{success:!0,message:"Log workspace is not configured",logs:[],totalLines:0,startIndex:1,logFileName:null};const t=h.join(i,r,w,".logs");if(!m.existsSync(t))return s(f,"DEBUG","Log directory does not exist",{logDir:t}),{success:!0,message:"Log directory does not exist",logs:[],totalLines:0,startIndex:1,logFileName:null};const e=(await m.promises.readdir(t,{withFileTypes:!0})).filter(F=>F.isFile()).map(F=>F.name);if(e.length===0)return s(f,"DEBUG","No log file found",{logDir:t}),{success:!0,message:"No log file found",logs:[],totalLines:0,startIndex:1,logFileName:null};const c=[];for(const F of e){const b=h.join(t,F),k=await m.promises.stat(b);c.push({name:F,fullPath:b,mtime:k.mtimeMs,size:k.size})}c.sort((F,b)=>b.mtime-F.mtime);const l=c[0],a=(await m.promises.readFile(l.fullPath,"utf8")).split(`
|
|
2
|
+
`).filter(F=>F.length>0),R=a.length,E=Math.max(1,n),S=Math.max(0,R-E),I=S+1,N=a.slice(S).map((F,b)=>({line:I+b,content:F}));return s(f,"DEBUG","Get latest logs",{fileName:l.name,fileSize:l.size,totalLines:R,returnedLines:N.length,elapsedMs:Date.now()-g}),{success:!0,message:"Get log successfully",logs:N,totalLines:R,startIndex:I,logFileName:l.name}}export{L as getFileList,W as updateFiles,$ as uploadFile,v as uploadFiles,U as downloadAllFiles,M as getLatestLogs};
|
|
@@ -1 +1,14 @@
|
|
|
1
|
-
import t from"fs";import o from"path";import J from"../../appConfig/index.js";import{extractZip as M}from"../common/zipUtils.js";import{ValidationError as S,SystemError as U,FileError as C}from"../error/errorHandler.js";import{log as n}from"../log/logUtils.js";import{ensurePrimaryAgentDirs as G,syncAgents as B}from"../common/AgentWorkspaceUtils.js";function K(e){if(!e)return[];if(Array.isArray(e))return e.map(i=>typeof i=="string"?i.trim():"").filter(Boolean);if(typeof e=="string"){const i=e.trim();if(!i)return[];try{const a=JSON.parse(i);if(Array.isArray(a))return a.map(f=>typeof f=="string"?f.trim():"").filter(Boolean)}catch{}return[i]}return[]}async function L(e,i,a){let f;try{f=await fetch(e)}catch(E){throw new C(`Failed to download skill zip from url: ${e}`,{url:e,reason:E.message})}if(!f.ok)throw new C(`Failed to download skill zip from url: ${e}`,{url:e,status:f.status,statusText:f.statusText});const u=f.headers.get("content-type")||"";u&&!u.includes("zip")&&!u.includes("octet-stream")&&n(a,"WARN","Downloaded skill url content-type is not typical zip",{url:e,contentType:u});const s=await f.arrayBuffer(),y=Buffer.from(s);await t.promises.writeFile(i,y)}async function V(e="computer"){const i=J.COMPUTER_WORKSPACE_DIR;if(!i)throw new S("COMPUTER_WORKSPACE_DIR configuration does not exist",{field:"COMPUTER_WORKSPACE_DIR"});return t.existsSync(i)||(await t.promises.mkdir(i,{recursive:!0}),n(e,"INFO","Created user workspace root directory",{workspaceRoot:i})),i}async function Z(e,i){const a=o.join(e,i);if(t.existsSync(a)&&(await t.promises.lstat(a)).isDirectory())return a;const f=await t.promises.readdir(e,{withFileTypes:!0});for(const u of f){if(!u.isDirectory())continue;const s=o.join(e,u.name),y=o.join(s,i);if(t.existsSync(y)&&(await t.promises.lstat(y)).isDirectory())return y}return null}async function ot(e){return Z(e,"skills")}const X=".dynamic_add.lock";function Y(e){const i=o.join(e,X);return t.existsSync(i)&&t.statSync(i).isFile()}async function A(e){t.existsSync(e)&&await t.promises.rm(e,{recursive:!0,force:!0})}async function T(e,i){try{await t.promises.rename(e,i)}catch(a){if(a.code==="EXDEV"){async function f(u,s){if((await t.promises.lstat(u)).isDirectory()){await t.promises.mkdir(s,{recursive:!0});const E=await t.promises.readdir(u);for(const N of E)await f(o.join(u,N),o.join(s,N))}else await t.promises.copyFile(u,s)}await f(e,i),await t.promises.rm(e,{recursive:!0,force:!0})}else throw a}}async function q(e,i,a,f){const u=Date.now(),s=`computer:${e}:${i}`,y=K(f),E=[],N=[],P=new Set;if(!e)throw new S("userId cannot be empty",{field:"userId"});if(!i)throw new S("cId cannot be empty",{field:"cId"});const x=await V(s),D=o.join(x,String(e),String(i),".tmp"),_=o.join(x,String(e),String(i)),{skillsDir:g,agentsDir:l,agentTypes:R}=await G(_);t.existsSync(_)||await t.promises.mkdir(_,{recursive:!0});const v=o.join(D,`preserved_skills_${Date.now()}_${Math.round(Math.random()*1e6)}`);if(t.existsSync(g)){const p=(await t.promises.readdir(g,{withFileTypes:!0})).filter(m=>m.isDirectory()&&Y(o.join(g,m.name)));if(p.length>0){await t.promises.mkdir(v,{recursive:!0});for(const m of p){const d=o.join(g,m.name),h=o.join(v,m.name);await T(d,h)}n(s,"INFO","\u4FDD\u7559\u542B .dynamic_add.lock \u7684 skill",{preserved:p.map(m=>m.name)})}}if(await A(g),await A(l),await t.promises.mkdir(g,{recursive:!0}),await t.promises.mkdir(l,{recursive:!0}),t.existsSync(v)){const r=await t.promises.readdir(v,{withFileTypes:!0});for(const p of r)if(p.isDirectory()){const m=o.join(v,p.name),d=o.join(g,p.name);await T(m,d)}await A(v)}const k=t.existsSync(g),F=t.existsSync(l);if(n(s,"INFO","Deleted old skills and agents directories completed",{userId:e,cId:i,targetSkillsDir:g,targetAgentsDir:l,agentTypes:R,skillsExists:k,agentsExists:F}),!a&&y.length===0)return await B(_),n(s,"INFO","Created workspace (no uploaded file, no skills and agents)",{userId:e,cId:i,workspaceRoot:x,skillsDir:null,agentsDir:null,agentTypes:R,elapsedMs:Date.now()-u}),{message:"Workspace created (no uploaded file, no skills and agents)",workspaceRoot:x};if(a){if(!a.path)throw new S("Uploaded file has no valid path",{field:"file.path"});if(o.extname(a.originalname||a.filename||"").toLowerCase()!==".zip")throw new S("Only zip files are supported",{field:"file",originalName:a.originalname})}n(s,"DEBUG","Start processing workspace resources",{userId:e,cId:i,workspaceRoot:x,hasUploadedZip:!!a,skillUrlsCount:y.length});try{t.existsSync(D)||await t.promises.mkdir(D,{recursive:!0});const r=[];if(a){const d=o.join(D,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);N.push(d),await t.promises.mkdir(d,{recursive:!0}),n(s,"DEBUG","Start extracting uploaded zip file",{extractRoot:d}),await M(a.path,d),n(s,"DEBUG","Uploaded zip file extracted successfully",{extractRoot:d});const h=await Z(d,"skills"),j=await Z(d,"agents");if(h){await t.promises.mkdir(g,{recursive:!0});const c=await t.promises.readdir(h,{withFileTypes:!0});for(const z of c){if(!z.isDirectory())continue;const $=o.join(h,z.name),O=o.join(g,z.name);t.existsSync(O)&&await A(O),await T($,O),P.add(z.name)}r.push("skills"),n(s,"INFO","skills updated to workspace",{userId:e,cId:i,workspaceRoot:x,targetSkillsDir:g,agentTypes:R})}else n(s,"INFO","skills directory not found in uploaded zip, skipping",{userId:e,cId:i,extractRoot:d});j?(await A(l),await T(j,l),r.push("agents"),n(s,"INFO","agents updated to workspace",{userId:e,cId:i,workspaceRoot:x,targetAgentsDir:l,agentTypes:R})):n(s,"INFO","agents directory not found in uploaded zip, skipping",{userId:e,cId:i,extractRoot:d})}for(let d=0;d<y.length;d+=1){const h=y[d],j=o.join(D,`skill_url_${Date.now()}_${d}_${Math.round(Math.random()*1e6)}.zip`);E.push(j);const c=o.join(D,`skill_url_extract_${Date.now()}_${d}_${Math.round(Math.random()*1e6)}`);N.push(c),await t.promises.mkdir(c,{recursive:!0}),n(s,"INFO","Start download skill zip from url",{userId:e,cId:i,skillUrl:h}),await L(h,j,s),n(s,"INFO","Skill zip downloaded, start extracting",{userId:e,cId:i,skillUrl:h,downloadedZipPath:j}),await M(j,c);const $=(await t.promises.readdir(c,{withFileTypes:!0})).filter(w=>w.isDirectory()&&!w.name.startsWith(".")),b=$.find(w=>w.name==="skills")?(await t.promises.readdir(o.join(c,"skills"),{withFileTypes:!0})).filter(w=>w.isDirectory()&&!w.name.startsWith(".")).map(w=>({name:w.name,sourcePath:o.join(c,"skills",w.name)})):$.map(w=>({name:w.name,sourcePath:o.join(c,w.name)}));if(b.length===0){n(s,"WARN","No skill directory found after extracting skill url zip",{userId:e,cId:i,skillUrl:h,extractRoot:c});continue}await t.promises.mkdir(g,{recursive:!0});for(const w of b){const W=o.join(g,w.name);t.existsSync(W)&&await A(W),await T(w.sourcePath,W),P.add(w.name),n(s,"INFO","Skill from url updated to workspace",{userId:e,cId:i,skillUrl:h,skillName:w.name,destSkillPath:W})}r.includes("skills")||r.push("skills")}r.length===0&&n(s,"WARN","No valid skills or agents directories found",{userId:e,cId:i,hasUploadedZip:!!a,skillUrlsCount:y.length});const p=Array.from(P),m=r.length>0?`Workspace created successfully, ${r.join(" and ")} updated`:"Workspace created successfully (skills and agents directories not found)";return n(s,"INFO",m,{userId:e,cId:i,updatedDirs:r,updatedSkills:p,agentTypes:R,elapsedMs:Date.now()-u}),await B(_),{message:m,workspaceRoot:x,updatedSkills:p}}catch(r){throw n(s,"ERROR","Failed to process workspace resources",{userId:e,cId:i,error:r.message,elapsedMs:Date.now()-u}),r instanceof S||r instanceof C||r instanceof U?r:new U(`Failed to create workspace: ${r.message}`,{userId:e,cId:i})}finally{try{for(const r of N)t.existsSync(r)&&await t.promises.rm(r,{recursive:!0,force:!0})}catch(r){n(s,"WARN","Failed to clean up temporary extracted zip",{error:r.message})}for(const r of E)try{t.existsSync(r)&&await t.promises.unlink(r)}catch(p){n(s,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:r,error:p.message})}try{a&&a.path&&t.existsSync(a.path)&&await t.promises.unlink(a.path)}catch(r){n(s,"WARN","Failed to clean up uploaded zip file",{tempZipPath:a?.path,error:r.message})}}}async function H(e,i,a,f){const u=Date.now(),s=`computer:${e}:${i}`,y=K(f),E=[],N=[];if(!e)throw new S("userId cannot be empty",{field:"userId"});if(!i)throw new S("cId cannot be empty",{field:"cId"});if(!a&&y.length===0)throw new S("file or skillUrls cannot both be empty",{field:"file|skillUrls"});if(a){if(!a.path)throw new S("Uploaded file has no valid path",{field:"file.path"});if(o.extname(a.originalname||a.filename||"").toLowerCase()!==".zip")throw new S("Only zip files are supported",{field:"file",originalName:a?.originalname})}const P=await V(s),x=o.join(P,String(e),String(i),".tmp"),D=o.join(P,String(e),String(i)),{skillsDir:_,agentTypes:g}=await G(D);try{t.existsSync(D)||await t.promises.mkdir(D,{recursive:!0}),t.existsSync(_)||await t.promises.mkdir(_,{recursive:!0}),t.existsSync(x)||await t.promises.mkdir(x,{recursive:!0});const l=[],R=new Set;if(a){const k=o.join(x,`skill_push_${Date.now()}_${Math.round(Math.random()*1e6)}`);N.push(k),await t.promises.mkdir(k,{recursive:!0}),n(s,"DEBUG","Start extracting skill zip file",{extractRoot:k}),await M(a.path,k),n(s,"DEBUG","Skill zip file extracted successfully",{extractRoot:k});const F=await Z(k,"skills");if(!F)n(s,"WARN","skills directory not found in uploaded zip",{userId:e,cId:i,extractRoot:k});else{const p=(await t.promises.readdir(F,{withFileTypes:!0})).filter(m=>m.isDirectory()&&!m.name.startsWith("."));if(p.length===0)n(s,"WARN","skills directory in uploaded zip has no skill subdirectories",{userId:e,cId:i,skillsDir:F});else for(const m of p){const d=o.join(F,m.name),h=o.join(_,m.name);t.existsSync(h)&&await A(h),await T(d,h),R.add(m.name),n(s,"INFO","skill pushed to workspace from uploaded zip",{userId:e,cId:i,skillName:m.name,destSkillPath:h})}}}for(let k=0;k<y.length;k+=1){const F=y[k],r=o.join(x,`skill_push_url_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}.zip`);E.push(r);const p=o.join(x,`skill_push_url_extract_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}`);N.push(p),await t.promises.mkdir(p,{recursive:!0}),n(s,"INFO","Start download skill zip for push from url",{userId:e,cId:i,skillUrl:F}),await L(F,r,s),await M(r,p);const d=(await t.promises.readdir(p,{withFileTypes:!0})).filter(c=>c.isDirectory()&&!c.name.startsWith(".")),j=d.find(c=>c.name==="skills")?(await t.promises.readdir(o.join(p,"skills"),{withFileTypes:!0})).filter(c=>c.isDirectory()&&!c.name.startsWith(".")).map(c=>({name:c.name,sourcePath:o.join(p,"skills",c.name)})):d.map(c=>({name:c.name,sourcePath:o.join(p,c.name)}));if(j.length===0){n(s,"WARN","No skill directory found after extracting push skill url zip",{userId:e,cId:i,skillUrl:F,extractRoot:p});continue}for(const c of j){const z=o.join(_,c.name);t.existsSync(z)&&await A(z),await T(c.sourcePath,z),R.add(c.name),n(s,"INFO","skill pushed to workspace from url zip",{userId:e,cId:i,skillUrl:F,skillName:c.name,destSkillPath:z})}}for(const k of R)l.push(k);const v=l.length>0?`Pushed ${l.length} skills: ${l.join(", ")}`:"No valid skill directories found in file or skillUrls";return n(s,"INFO",v,{userId:e,cId:i,updatedSkills:l,agentTypes:g,elapsedMs:Date.now()-u}),await B(D),{message:v,workspaceRoot:P,updatedSkills:l}}catch(l){throw n(s,"ERROR","Failed to push skill to workspace",{userId:e,cId:i,error:l.message,elapsedMs:Date.now()-u}),l instanceof S||l instanceof C||l instanceof U?l:new U(`Failed to push skill to workspace: ${l.message}`,{userId:e,cId:i})}finally{try{for(const l of N)t.existsSync(l)&&await t.promises.rm(l,{recursive:!0,force:!0})}catch(l){n(s,"WARN","Failed to clean up temporary extracted zip",{error:l.message})}for(const l of E)try{t.existsSync(l)&&await t.promises.unlink(l)}catch(R){n(s,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:l,error:R.message})}try{a&&a.path&&t.existsSync(a.path)&&await t.promises.unlink(a.path)}catch(l){n(s,"WARN","Failed to clean up uploaded zip file",{tempZipPath:a?.path,error:l.message})}}}export{q as createWorkspace,H as pushSkillsToWorkspace};
|
|
1
|
+
import i from"fs";import r from"path";import{exec as le}from"child_process";import pe from"cross-spawn";import de from"archiver";import B from"../../appConfig/index.js";import{extractZip as G}from"../common/zipUtils.js";import{ValidationError as g,SystemError as A,FileError as Z}from"../error/errorHandler.js";import{log as c}from"../log/logUtils.js";import{createPnpmNpmrc as me}from"../common/npmrcUtils.js";import{ensurePrimaryAgentDirs as te,syncAgents as X}from"../common/AgentWorkspaceUtils.js";function ie(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 n=JSON.parse(t);if(Array.isArray(n))return n.map(o=>typeof o=="string"?o.trim():"").filter(Boolean)}catch{}return[t]}return[]}async function ne(e,t,n){let o;try{o=await fetch(e)}catch(y){throw new Z(`Failed to download skill zip from url: ${e}`,{url:e,reason:y.message})}if(!o.ok)throw new Z(`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")&&c(n,"WARN","Downloaded skill url content-type is not typical zip",{url:e,contentType:s});const a=await o.arrayBuffer(),m=Buffer.from(a);await i.promises.writeFile(t,m)}async function $(e="computer"){const t=B.COMPUTER_WORKSPACE_DIR;if(!t)throw new g("COMPUTER_WORKSPACE_DIR configuration does not exist",{field:"COMPUTER_WORKSPACE_DIR"});return i.existsSync(t)||(await i.promises.mkdir(t,{recursive:!0}),c(e,"INFO","Created user workspace root directory",{workspaceRoot:t})),t}async function H(e,t){const n=r.join(e,t);if(i.existsSync(n)&&(await i.promises.lstat(n)).isDirectory())return n;const o=await i.promises.readdir(e,{withFileTypes:!0});for(const s of o){if(!s.isDirectory())continue;const a=r.join(e,s.name),m=r.join(a,t);if(i.existsSync(m)&&(await i.promises.lstat(m)).isDirectory())return m}return null}async function Ue(e){return H(e,"skills")}const fe=".dynamic_add.lock";function ue(e){const t=r.join(e,fe);return i.existsSync(t)&&i.statSync(t).isFile()}async function C(e){i.existsSync(e)&&await i.promises.rm(e,{recursive:!0,force:!0})}async function T(e,t){try{await i.promises.rename(e,t)}catch(n){if(n.code==="EXDEV"){async function o(s,a){if((await i.promises.lstat(s)).isDirectory()){await i.promises.mkdir(a,{recursive:!0});const y=await i.promises.readdir(s);for(const l of y)await o(r.join(s,l),r.join(a,l))}else await i.promises.copyFile(s,a)}await o(e,t),await i.promises.rm(e,{recursive:!0,force:!0})}else throw n}}async function we(e,t,n,o,s,a){const m=t&&t.trim(),y=n&&n.trim(),l=o&&o.trim(),p=Array.isArray(s)&&s.length>0;if(!m&&!y&&!l&&!p)return;const h=r.join(e,".claude");try{if(i.existsSync(h)||await i.promises.mkdir(h,{recursive:!0}),m)try{const f={mcpServers:JSON.parse(t)},S=r.join(e,".mcp.json");await i.promises.writeFile(S,JSON.stringify(f,null,2),"utf-8"),c(a,"INFO","Written .mcp.json to workspace root")}catch(f){c(a,"WARN","Failed to parse/write mcpServersConfig, skipping",{error:f.message})}const d={};if(y)try{d.hooks=JSON.parse(n)}catch(f){c(a,"WARN","Failed to parse hooksConfig, skipping",{error:f.message})}if(l)try{d.permissions=JSON.parse(o)}catch(f){c(a,"WARN","Failed to parse permissionsConfig, skipping",{error:f.message})}if(Object.keys(d).length>0){const f=r.join(h,"settings.json");await i.promises.writeFile(f,JSON.stringify(d,null,2),"utf-8"),c(a,"INFO","Written .claude/settings.json",{keys:Object.keys(d)})}if(p){const f=r.join(h,"hooks");i.existsSync(f)||await i.promises.mkdir(f,{recursive:!0});for(const S of s){if(!S||!S.path||!S.content)continue;const w=r.normalize(S.path);if(w.startsWith("..")||r.isAbsolute(w)){c(a,"WARN","Hook script path contains traversal, skipping",{path:S.path});continue}const x=r.join(h,w),j=r.dirname(x);i.existsSync(j)||await i.promises.mkdir(j,{recursive:!0}),await i.promises.writeFile(x,S.content,"utf-8"),await i.promises.chmod(x,493),c(a,"INFO","Written hook script",{path:S.path})}}}catch(d){c(a,"WARN","Failed to write .claude/settings or .mcp.json or hook scripts, not blocking workspace creation",{error:d.message})}}async function he(e,t,n,o,s,a,m,y){const l=Date.now(),p=`computer:${e}:${t}`,h=ie(o),d=[],f=[],S=new Set;if(!e)throw new g("userId cannot be empty",{field:"userId"});if(!t)throw new g("cId cannot be empty",{field:"cId"});const w=await $(p),x=r.join(w,String(e),String(t),".tmp"),j=r.join(w,String(e),String(t)),{skillsDir:k,agentsDir:D,agentTypes:_}=await te(j);i.existsSync(j)||await i.promises.mkdir(j,{recursive:!0});const N=r.join(x,`preserved_skills_${Date.now()}_${Math.round(Math.random()*1e6)}`);if(i.existsSync(k)){const F=(await i.promises.readdir(k,{withFileTypes:!0})).filter(O=>O.isDirectory()&&ue(r.join(k,O.name)));if(F.length>0){await i.promises.mkdir(N,{recursive:!0});for(const O of F){const R=r.join(k,O.name),v=r.join(N,O.name);await T(R,v)}c(p,"INFO","\u4FDD\u7559\u542B .dynamic_add.lock \u7684 skill",{preserved:F.map(O=>O.name)})}}if(await C(k),await C(D),await i.promises.mkdir(k,{recursive:!0}),await i.promises.mkdir(D,{recursive:!0}),i.existsSync(N)){const u=await i.promises.readdir(N,{withFileTypes:!0});for(const F of u)if(F.isDirectory()){const O=r.join(N,F.name),R=r.join(k,F.name);await T(O,R)}await C(N)}const E=i.existsSync(k),W=i.existsSync(D);c(p,"INFO","Deleted old skills and agents directories completed",{userId:e,cId:t,targetSkillsDir:k,targetAgentsDir:D,agentTypes:_,skillsExists:E,agentsExists:W});const b=r.join(j,".mcp.json");i.existsSync(b)&&await i.promises.unlink(b);const L=r.join(j,".claude","settings.json");if(i.existsSync(L)&&await i.promises.unlink(L),await we(j,s,m,a,y,p),!n&&h.length===0)return await X(j),c(p,"INFO","Created workspace (no uploaded file, no skills and agents)",{userId:e,cId:t,workspaceRoot:w,skillsDir:null,agentsDir:null,agentTypes:_,elapsedMs:Date.now()-l}),{message:"Workspace created (no uploaded file, no skills and agents)",workspaceRoot:w};if(n){if(!n.path)throw new g("Uploaded file has no valid path",{field:"file.path"});if(r.extname(n.originalname||n.filename||"").toLowerCase()!==".zip")throw new g("Only zip files are supported",{field:"file",originalName:n.originalname})}c(p,"DEBUG","Start processing workspace resources",{userId:e,cId:t,workspaceRoot:w,hasUploadedZip:!!n,skillUrlsCount:h.length});try{i.existsSync(x)||await i.promises.mkdir(x,{recursive:!0});const u=[];if(n){const R=r.join(x,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);f.push(R),await i.promises.mkdir(R,{recursive:!0}),c(p,"DEBUG","Start extracting uploaded zip file",{extractRoot:R}),await G(n.path,R),c(p,"DEBUG","Uploaded zip file extracted successfully",{extractRoot:R});const v=await H(R,"skills"),M=await H(R,"agents");if(v){await i.promises.mkdir(k,{recursive:!0});const z=await i.promises.readdir(v,{withFileTypes:!0});for(const U of z){if(!U.isDirectory())continue;const J=r.join(v,U.name),K=r.join(k,U.name);i.existsSync(K)&&await C(K),await T(J,K),S.add(U.name)}u.push("skills"),c(p,"INFO","skills updated to workspace",{userId:e,cId:t,workspaceRoot:w,targetSkillsDir:k,agentTypes:_})}else c(p,"INFO","skills directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:R});M?(await C(D),await T(M,D),u.push("agents"),c(p,"INFO","agents updated to workspace",{userId:e,cId:t,workspaceRoot:w,targetAgentsDir:D,agentTypes:_})):c(p,"INFO","agents directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:R})}for(let R=0;R<h.length;R+=1){const v=h[R],M=r.join(x,`skill_url_${Date.now()}_${R}_${Math.round(Math.random()*1e6)}.zip`);d.push(M);const z=r.join(x,`skill_url_extract_${Date.now()}_${R}_${Math.round(Math.random()*1e6)}`);f.push(z),await i.promises.mkdir(z,{recursive:!0}),c(p,"INFO","Start download skill zip from url",{userId:e,cId:t,skillUrl:v}),await ne(v,M,p),c(p,"INFO","Skill zip downloaded, start extracting",{userId:e,cId:t,skillUrl:v,downloadedZipPath:M}),await G(M,z);const J=(await i.promises.readdir(z,{withFileTypes:!0})).filter(P=>P.isDirectory()&&!P.name.startsWith(".")),ee=J.find(P=>P.name==="skills")?(await i.promises.readdir(r.join(z,"skills"),{withFileTypes:!0})).filter(P=>P.isDirectory()&&!P.name.startsWith(".")).map(P=>({name:P.name,sourcePath:r.join(z,"skills",P.name)})):J.map(P=>({name:P.name,sourcePath:r.join(z,P.name)}));if(ee.length===0){c(p,"WARN","No skill directory found after extracting skill url zip",{userId:e,cId:t,skillUrl:v,extractRoot:z});continue}await i.promises.mkdir(k,{recursive:!0});for(const P of ee){const q=r.join(k,P.name);i.existsSync(q)&&await C(q),await T(P.sourcePath,q),S.add(P.name),c(p,"INFO","Skill from url updated to workspace",{userId:e,cId:t,skillUrl:v,skillName:P.name,destSkillPath:q})}u.includes("skills")||u.push("skills")}u.length===0&&c(p,"WARN","No valid skills or agents directories found",{userId:e,cId:t,hasUploadedZip:!!n,skillUrlsCount:h.length});const F=Array.from(S),O=u.length>0?`Workspace created successfully, ${u.join(" and ")} updated`:"Workspace created successfully (skills and agents directories not found)";return c(p,"INFO",O,{userId:e,cId:t,updatedDirs:u,updatedSkills:F,agentTypes:_,elapsedMs:Date.now()-l}),await X(j),{message:O,workspaceRoot:w,updatedSkills:F}}catch(u){throw c(p,"ERROR","Failed to process workspace resources",{userId:e,cId:t,error:u.message,elapsedMs:Date.now()-l}),u instanceof g||u instanceof Z||u instanceof A?u:new A(`Failed to create workspace: ${u.message}`,{userId:e,cId:t})}finally{try{for(const u of f)i.existsSync(u)&&await i.promises.rm(u,{recursive:!0,force:!0})}catch(u){c(p,"WARN","Failed to clean up temporary extracted zip",{error:u.message})}for(const u of d)try{i.existsSync(u)&&await i.promises.unlink(u)}catch(F){c(p,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:u,error:F.message})}try{n&&n.path&&i.existsSync(n.path)&&await i.promises.unlink(n.path)}catch(u){c(p,"WARN","Failed to clean up uploaded zip file",{tempZipPath:n?.path,error:u.message})}}}async function ge(e,t,n,o){const s=Date.now(),a=`computer:${e}:${t}`,m=ie(o),y=[],l=[];if(!e)throw new g("userId cannot be empty",{field:"userId"});if(!t)throw new g("cId cannot be empty",{field:"cId"});if(!n&&m.length===0)throw new g("file or skillUrls cannot both be empty",{field:"file|skillUrls"});if(n){if(!n.path)throw new g("Uploaded file has no valid path",{field:"file.path"});if(r.extname(n.originalname||n.filename||"").toLowerCase()!==".zip")throw new g("Only zip files are supported",{field:"file",originalName:n?.originalname})}const p=await $(a),h=r.join(p,String(e),String(t),".tmp"),d=r.join(p,String(e),String(t)),{skillsDir:f,agentTypes:S}=await te(d);try{i.existsSync(d)||await i.promises.mkdir(d,{recursive:!0}),i.existsSync(f)||await i.promises.mkdir(f,{recursive:!0}),i.existsSync(h)||await i.promises.mkdir(h,{recursive:!0});const w=[],x=new Set;if(n){const k=r.join(h,`skill_push_${Date.now()}_${Math.round(Math.random()*1e6)}`);l.push(k),await i.promises.mkdir(k,{recursive:!0}),c(a,"DEBUG","Start extracting skill zip file",{extractRoot:k}),await G(n.path,k),c(a,"DEBUG","Skill zip file extracted successfully",{extractRoot:k});const D=await H(k,"skills");if(!D)c(a,"WARN","skills directory not found in uploaded zip",{userId:e,cId:t,extractRoot:k});else{const N=(await i.promises.readdir(D,{withFileTypes:!0})).filter(E=>E.isDirectory()&&!E.name.startsWith("."));if(N.length===0)c(a,"WARN","skills directory in uploaded zip has no skill subdirectories",{userId:e,cId:t,skillsDir:D});else for(const E of N){const W=r.join(D,E.name),b=r.join(f,E.name);i.existsSync(b)&&await C(b),await T(W,b),x.add(E.name),c(a,"INFO","skill pushed to workspace from uploaded zip",{userId:e,cId:t,skillName:E.name,destSkillPath:b})}}}for(let k=0;k<m.length;k+=1){const D=m[k],_=r.join(h,`skill_push_url_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}.zip`);y.push(_);const N=r.join(h,`skill_push_url_extract_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}`);l.push(N),await i.promises.mkdir(N,{recursive:!0}),c(a,"INFO","Start download skill zip for push from url",{userId:e,cId:t,skillUrl:D}),await ne(D,_,a),await G(_,N);const W=(await i.promises.readdir(N,{withFileTypes:!0})).filter(u=>u.isDirectory()&&!u.name.startsWith(".")),L=W.find(u=>u.name==="skills")?(await i.promises.readdir(r.join(N,"skills"),{withFileTypes:!0})).filter(u=>u.isDirectory()&&!u.name.startsWith(".")).map(u=>({name:u.name,sourcePath:r.join(N,"skills",u.name)})):W.map(u=>({name:u.name,sourcePath:r.join(N,u.name)}));if(L.length===0){c(a,"WARN","No skill directory found after extracting push skill url zip",{userId:e,cId:t,skillUrl:D,extractRoot:N});continue}for(const u of L){const F=r.join(f,u.name);i.existsSync(F)&&await C(F),await T(u.sourcePath,F),x.add(u.name),c(a,"INFO","skill pushed to workspace from url zip",{userId:e,cId:t,skillUrl:D,skillName:u.name,destSkillPath:F})}}for(const k of x)w.push(k);const j=w.length>0?`Pushed ${w.length} skills: ${w.join(", ")}`:"No valid skill directories found in file or skillUrls";return c(a,"INFO",j,{userId:e,cId:t,updatedSkills:w,agentTypes:S,elapsedMs:Date.now()-s}),await X(d),{message:j,workspaceRoot:p,updatedSkills:w}}catch(w){throw c(a,"ERROR","Failed to push skill to workspace",{userId:e,cId:t,error:w.message,elapsedMs:Date.now()-s}),w instanceof g||w instanceof Z||w instanceof A?w:new A(`Failed to push skill to workspace: ${w.message}`,{userId:e,cId:t})}finally{try{for(const w of l)i.existsSync(w)&&await i.promises.rm(w,{recursive:!0,force:!0})}catch(w){c(a,"WARN","Failed to clean up temporary extracted zip",{error:w.message})}for(const w of y)try{i.existsSync(w)&&await i.promises.unlink(w)}catch(x){c(a,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:w,error:x.message})}try{n&&n.path&&i.existsSync(n.path)&&await i.promises.unlink(n.path)}catch(w){c(a,"WARN","Failed to clean up uploaded zip file",{tempZipPath:n?.path,error:w.message})}}}async function ye(e,t,n,o){const s=Date.now(),a=`computer:${e}:${t}`;if(!e)throw new g("userId cannot be empty",{field:"userId"});if(!t)throw new g("cId cannot be empty",{field:"cId"});if(!n||!n.path)throw new g("file is required",{field:"file"});if(r.extname(n.originalname||n.filename||"").toLowerCase()!==".zip")throw new g("Only zip files are supported",{field:"file",originalName:n.originalname});const y=await $(a),l=r.join(y,String(e),String(t)),p=r.join(l,".tmp"),h=r.join(p,`template_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{i.existsSync(l)||await i.promises.mkdir(l,{recursive:!0}),i.existsSync(p)||await i.promises.mkdir(p,{recursive:!0}),await i.promises.mkdir(h,{recursive:!0}),c(a,"DEBUG","Start extracting template zip file",{extractRoot:h}),await G(n.path,h),c(a,"DEBUG","Template zip file extracted successfully",{extractRoot:h});const d=await i.promises.readdir(h,{withFileTypes:!0});for(const f of d){const S=r.join(h,f.name),w=r.join(l,f.name);if(f.isDirectory())await T(S,w);else{const x=r.dirname(w);i.existsSync(x)||await i.promises.mkdir(x,{recursive:!0}),await i.promises.copyFile(S,w)}}if(c(a,"INFO","Template files extracted to workspace",{userId:e,cId:t,targetDir:l,fileCount:d.length}),B.GIT_ENABLED&&(o==="true"||o===!0)){const f=await import("../../service/gitService.js");await f.default.init({workspaceType:"taskAgent",userId:e,cId:t}),await f.default.commit({workspaceType:"taskAgent",userId:e,cId:t,message:"Initial commit"}),c(a,"INFO","Git init and initial commit done",{userId:e,cId:t})}return c(a,"INFO","Init project template completed",{userId:e,cId:t,targetDir:l,elapsedMs:Date.now()-s}),{message:"Project template initialized successfully",workspaceRoot:l}}catch(d){throw c(a,"ERROR","Failed to init project template",{userId:e,cId:t,error:d.message,elapsedMs:Date.now()-s}),d instanceof g||d instanceof Z||d instanceof A?d:new A(`Failed to init project template: ${d.message}`,{userId:e,cId:t})}finally{try{i.existsSync(h)&&await i.promises.rm(h,{recursive:!0,force:!0})}catch(d){c(a,"WARN","Failed to clean up temporary extracted zip",{error:d.message})}try{n&&n.path&&i.existsSync(n.path)&&await i.promises.unlink(n.path)}catch(d){c(a,"WARN","Failed to clean up uploaded zip file",{tempZipPath:n?.path,error:d.message})}}}async function ke(e,t,n){if(!e)throw new g("userId cannot be empty",{field:"userId"});if(!t)throw new g("cId cannot be empty",{field:"cId"});if(!n||typeof n!="string"||!n.trim())throw new g("command cannot be empty",{field:"command"});const o=await $("computer"),s=r.join(o,String(e),String(t));if(!i.existsSync(s))throw new g("workspace directory does not exist",{field:"workDir",workDir:s});const a=`computer:${e}:${t}`;c(a,"INFO","Execute command in workspace",{userId:e,cId:t,workDir:s,command:n});const m=10*60*1e3,y={cwd:s,timeout:m,maxBuffer:50*1024*1024};return B.BASH_PATH&&(y.shell=B.BASH_PATH),new Promise((l,p)=>{le(n,y,(h,d,f)=>{const S=h?h.killed?-1:h.code||1:0,w=h&&h.killed?(f||"")+`
|
|
2
|
+
Command timed out after ${m/1e3}s`:f||"";c(a,"INFO","Execute command completed",{userId:e,cId:t,exitCode:S,killed:h?h.killed:!1,stdoutLength:d?d.length:0,stderrLength:w.length}),l({stdout:d||"",stderr:w,exitCode:S})})})}async function Se(e,t,n){const o=`computer:${e}:${t}`;if(!e)throw new g("userId cannot be empty",{field:"userId"});if(!t)throw new g("cId cannot be empty",{field:"cId"});const s=await $(o),a=r.join(s,String(e),String(t));if(!i.existsSync(a))throw new g("workspace directory does not exist",{field:"workDir",workDir:a});const m=B.ZIP_WORKSPACE_EXCLUDE||[],y=Array.isArray(n)?n:[],l=new Set([...m,...y]),p=`${e}_${t}.zip`,h=Date.now();c(o,"INFO","Zip workspace request",{userId:e,cId:t,targetDir:a,excludeDirs:[...l]});const d=de("zip",{zlib:{level:9}});return d.directory(a,!1,f=>{const S=f.name||"";if(S.split(/[\/\\]/).filter(Boolean).some(x=>l.has(x)))return!1;try{const x=r.join(a,S);if(i.lstatSync(x).isSymbolicLink())return!1}catch{return!1}return f}),d.on("warning",f=>{f.code==="ENOENT"?c(o,"WARN","Encountered file problem when creating zip",{message:f.message,code:f.code}):c(o,"ERROR","Encountered warning when creating zip",{message:f.message,code:f.code})}),d.on("error",f=>{c(o,"ERROR","Failed to create zip",{message:f.message,elapsedMs:Date.now()-h})}),d.on("end",()=>{c(o,"INFO","Workspace zip created successfully",{targetDir:a,zipFileName:p,elapsedMs:Date.now()-h})}),{archive:d,zipFileName:p}}const Q=new Set([...B.ZIP_WORKSPACE_EXCLUDE||[],"dist-packages"]);function V(e){let t;try{t=i.readdirSync(e,{withFileTypes:!0})}catch{return null}const n=r.join(e,"scripts","package-platforms.mjs");if(i.existsSync(n))return e;for(const o of t)if(o.isDirectory()&&!Q.has(o.name)){const s=V(r.join(e,o.name));if(s)return s}return null}function Y(e,t,n,o=10*60*1e3){return new Promise(s=>{const a=pe(e,t,{...n,timeout:o});let m="",y="";a.stdout?.on("data",l=>{m+=l.toString()}),a.stderr?.on("data",l=>{y+=l.toString()}),a.on("error",l=>{s({stdout:m,stderr:y+`
|
|
3
|
+
Failed to spawn ${e}: ${l.message}`,exitCode:1})}),a.on("close",(l,p)=>{s(l===null&&p?{stdout:m,stderr:y+`
|
|
4
|
+
Process killed by signal ${p} (timeout after ${o/1e3}s)`,exitCode:-1}:{stdout:m,stderr:y,exitCode:l??1})})})}function xe(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 n=t.split("-");if(n.length<4)return null;let o=-1;for(let s=n.length-1;s>=2;s--)if(/^\d+\.\d+\.\d+$/.test(n[s])){o=s;break}return o<3?null:n.slice(2,o).join("-")}async function Ne(e,t,n,o){const s=`computer:${e}:${t}`;if(!e)throw new g("userId cannot be empty",{field:"userId"});if(!t)throw new g("cId cannot be empty",{field:"cId"});if(!n)throw new g("agentId cannot be empty",{field:"agentId"});if(!o)throw new g("version cannot be empty",{field:"version"});const a=await $(s),m=r.join(a,String(e),String(t));if(!i.existsSync(m))throw new g("workspace directory does not exist",{field:"workDir",workDir:m});c(s,"INFO","Build agent package request",{userId:e,cId:t,agentId:n,version:o});const y=V(m);if(!y)throw new g("package-platforms.mjs not found in workspace",{field:"script"});c(s,"INFO","Found package script",{projectDir:y});const l=await ae(y,s);if(c(s,"INFO","pnpm install completed",{exitCode:l.exitCode,stderrLength:l.stderr.length}),l.exitCode!==0)throw c(s,"ERROR","pnpm install failed",{stderr:l.stderr}),new A(`pnpm install failed: ${l.stderr}`);const p=r.join(y,"dist-packages"),h=["scripts/package-platforms.mjs",`agent-${n}`,o,p,"--print-artifacts"],d=await Y("node",h,{cwd:y,env:I()});if(c(s,"INFO","Build completed",{exitCode:d.exitCode,stdoutLength:d.stdout.length}),d.exitCode!==0)throw c(s,"ERROR","Build failed",{stderr:d.stderr}),new A(`Build failed: ${d.stderr}`);const f=[".tar.gz",".tar.bz2",".zip",".tgz"],S=[],w=d.stdout.split(`
|
|
5
|
+
`);for(const x of w){const j=x.trim();if(!j)continue;const k=j.toLowerCase();if(!f.some(W=>k.endsWith(W)))continue;const D=r.resolve(y,j);let _=r.relative(m,D);_=_.split(r.sep).join("/");const N=r.basename(j),E=xe(N);S.push({path:_,fileName:N,platform:E||""})}return c(s,"INFO","Build agent package completed",{artifactsCount:S.length,artifacts:S}),{artifacts:S}}async function je(e,t){const n=`computer:${e}:${t}`;if(!e)throw new g("userId cannot be empty",{field:"userId"});if(!t)throw new g("cId cannot be empty",{field:"cId"});const o=await $(n),s=r.join(o,String(e),String(t));if(!i.existsSync(s))return c(n,"WARN","Workspace not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const a=V(s);if(!a)return c(n,"WARN","package-platforms.mjs not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const m=r.join(a,"dist-packages");return i.existsSync(m)?(i.rmSync(m,{recursive:!0,force:!0}),c(n,"INFO","Cleaned dist-packages",{distPackagesDir:m}),{cleaned:!0}):(c(n,"INFO","dist-packages not found, nothing to clean",{distPackagesDir:m}),{cleaned:!1})}async function De(e,t){const n=`computer:${e}:${t}`;if(!e)throw new g("userId cannot be empty",{field:"userId"});if(!t)throw new g("cId cannot be empty",{field:"cId"});const o=await $(n),s=r.join(o,String(e),String(t));return i.existsSync(s)?(await i.promises.rm(s,{recursive:!0,force:!0}),c(n,"INFO","Workspace deleted",{userId:e,cId:t,targetDir:s})):c(n,"WARN","Workspace not found, skip delete",{userId:e,cId:t,targetDir:s}),{deleted:!0}}function se(e){const t=r.join(e,"package.json");if(i.existsSync(t))return e;let n;try{n=i.readdirSync(e,{withFileTypes:!0})}catch{return null}for(const o of n)if(o.isDirectory()&&!Q.has(o.name)&&o.name!=="node_modules"){const s=se(r.join(e,o.name));if(s)return s}return null}const oe=["neverBuiltDependencies","onlyBuiltDependencies","ignoredBuiltDependencies"],Re=["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 Pe(e,t){const n=r.join(e,"package.json");if(!i.existsSync(n))return;const o=await i.promises.readFile(n,"utf8");let s;try{s=JSON.parse(o)}catch{c(t,"WARN","Skip sanitizing package.json: invalid JSON",{projectDir:e});return}if(!s.pnpm||typeof s.pnpm!="object")return;const a=oe.filter(m=>m in s.pnpm);if(a.length!==0){for(const m of a)delete s.pnpm[m];Object.keys(s.pnpm).length===0&&delete s.pnpm,await i.promises.writeFile(n,`${JSON.stringify(s,null,2)}
|
|
6
|
+
`,"utf8"),c(t,"INFO","Removed conflicting pnpm built dependency settings from package.json",{projectDir:e,removedKeys:a})}}async function Fe(e,t){const n=r.join(e,"pnpm-workspace.yaml");if(!i.existsSync(n))return;const s=(await i.promises.readFile(n,"utf8")).split(`
|
|
7
|
+
`),a=[];let m=-1,y=[];for(const l of s){const p=l.trimStart(),h=l.length-p.length,d=oe.find(f=>new RegExp(`^${f}:\\s*(.*)$`).test(p));if(d){y.push(d);const f=p.slice(d.length+1).trim();(!f||f==="|"||f===">")&&(m=h);continue}if(m>=0){if(p===""||h>m)continue;m=-1}a.push(l)}y.length!==0&&(await i.promises.writeFile(n,a.join(`
|
|
8
|
+
`),"utf8"),c(t,"INFO","Removed conflicting pnpm built dependency settings from pnpm-workspace.yaml",{projectDir:e,removedKeys:y}))}async function re(e){if(!i.existsSync(e))return"";const t=await i.promises.readFile(e,"utf8"),n=t.split(`
|
|
9
|
+
`).filter(o=>!/^\s*(never-built-dependencies|only-built-dependencies|ignored-built-dependencies)\b/.test(o)).join(`
|
|
10
|
+
`);return n!==t&&await i.promises.writeFile(e,n,"utf8"),n}async function _e(e,t){await Pe(e,t),await Fe(e,t),await re(r.join(e,".npmrc"))}async function Ee(e,t){await me(e,t),await _e(e,t);const n=r.join(e,".npmrc");let o=await re(n);const s=[];if(/dangerously-allow-all-builds\s*=/.test(o)||s.push("dangerously-allow-all-builds=true"),/production\s*=/.test(o)||s.push("production=false"),/confirm-modules-purge\s*=/.test(o)||s.push("confirm-modules-purge=false"),s.length>0){const a=o&&!o.endsWith(`
|
|
11
|
+
`)?`
|
|
12
|
+
`:"";await i.promises.writeFile(n,`${o}${a}${s.join(`
|
|
13
|
+
`)}
|
|
14
|
+
`,"utf8"),c(t,"INFO","Updated .npmrc for pnpm install",{projectDir:e,additions:s})}}function I(){const e={...process.env};delete e.CI,delete e.NPM_CONFIG_PRODUCTION,delete e.npm_config_production;for(const t of Re)delete e[t];return e.NODE_ENV="development",e.NPM_CONFIG_PRODUCTION="false",e}async function ae(e,t){await Ee(e,t);const n=I();return Y("pnpm",["install","--prefer-offline","--config.production=false","--config.confirmModulesPurge=false","--config.dangerouslyAllowAllBuilds=true"],{cwd:e,env:n})}function ce(e){const t=r.join(e,"pyproject.toml"),n=r.join(e,"requirements.txt");if(i.existsSync(t)||i.existsSync(n))return e;let o;try{o=i.readdirSync(e,{withFileTypes:!0})}catch{return null}for(const s of o)if(s.isDirectory()&&!Q.has(s.name)&&s.name!==".venv"&&s.name!=="venv"&&s.name!=="__pycache__"){const a=ce(r.join(e,s.name));if(a)return a}return null}async function Oe(e,t,n){const o=`computer:${e}:${t}`,s=String(n||"").trim().toLowerCase();if(!e)throw new g("userId cannot be empty",{field:"userId"});if(!t)throw new g("cId cannot be empty",{field:"cId"});if(!s)throw new g("programmingLanguage cannot be empty",{field:"programmingLanguage"});const a=await $(o),m=r.join(a,String(e),String(t));if(!i.existsSync(m))throw new g("workspace directory does not exist",{field:"workDir",workDir:m});c(o,"INFO","Install project dependencies request",{userId:e,cId:t,programmingLanguage:s});const y=I();let l=null,p=null;if(s==="typescript"||s==="ts"){if(l=V(m)||se(m),!l)throw new g("TypeScript project not found in workspace",{field:"projectDir"});p=await ae(l,o)}else if(s==="python"||s==="py"){if(l=ce(m),!l)throw new g("Python project not found in workspace",{field:"projectDir"});const h=r.join(l,"pyproject.toml"),d=r.join(l,"requirements.txt");if(i.existsSync(h))p=await Y("pip",["install","-e","."],{cwd:l,env:y});else if(i.existsSync(d))p=await Y("pip",["install","-r","requirements.txt"],{cwd:l,env:y});else throw new g("No pyproject.toml or requirements.txt found in workspace",{field:"projectDir"})}else throw new g("Unsupported programmingLanguage",{field:"programmingLanguage",programmingLanguage:s});if(c(o,"INFO","Project dependencies install completed",{projectDir:l,programmingLanguage:s,exitCode:p.exitCode,stderrLength:p.stderr.length}),p.exitCode!==0)throw c(o,"ERROR","Project dependencies install failed",{projectDir:l,programmingLanguage:s,stderr:p.stderr}),new A(`Project dependencies install failed: ${p.stderr||p.stdout}`);return{message:"Project dependencies installed successfully",projectDir:l,programmingLanguage:s}}export{he as createWorkspace,ge as pushSkillsToWorkspace,ye as initProjectTemplate,ke as executeCommand,De as deleteWorkspace,Se as zipWorkspace,Ne as buildAgentPackage,je as cleanupBuildArtifacts,Oe as installProjectDependencies};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import f from"path";import n from"fs";import e from"isomorphic-git";import o from"../../appConfig/index.js";function g(i){return n.existsSync(f.join(i,".git"))}function m(){return{name:o.GIT_DEFAULT_AUTHOR_NAME,email:o.GIT_DEFAULT_AUTHOR_EMAIL}}async function d(i){const t=await e.statusMatrix({fs:n,dir:i});await Promise.all(t.map(([a,s,l])=>{if(l===2)return e.add({fs:n,dir:i,filepath:a}).catch(()=>{});if(s===1&&l===0)return e.remove({fs:n,dir:i,filepath:a}).catch(()=>{})}))}async function A(i,t){for(const a of t){const s=f.join(i,a);n.existsSync(s)?await e.add({fs:n,dir:i,filepath:a}):await e.remove({fs:n,dir:i,filepath:a}).catch(()=>{})}}async function T(i){if(g(i))c(i);else{await e.init({fs:n,dir:i,defaultBranch:"main"}),await e.setConfig({fs:n,dir:i,path:"user.name",value:o.GIT_DEFAULT_AUTHOR_NAME}),await e.setConfig({fs:n,dir:i,path:"user.email",value:o.GIT_DEFAULT_AUTHOR_EMAIL}),c(i);const t=f.join(i,".gitignore");n.existsSync(t)&&await e.add({fs:n,dir:i,filepath:".gitignore"}),(await e.listFiles({fs:n,dir:i})).length===0&&(n.writeFileSync(f.join(i,".gitkeep"),""),await e.add({fs:n,dir:i,filepath:".gitkeep"})),await e.commit({fs:n,dir:i,message:"Initial commit",author:m()})}}async function _(i,t){if(g(i))try{Array.isArray(t)&&t.length>0?await A(i,t):await d(i)}catch{}}function c(i){if(!o.GIT_AUTO_GITIGNORE)return;const t=f.join(i,".gitignore"),a=o.GIT_GITIGNORE_ENTRIES||[];let s=[];n.existsSync(t)&&(s=n.readFileSync(t,"utf8").split(/\r?\n/));const l=new Set(s.map(r=>r.trim()).filter(Boolean)),u=a.filter(r=>!l.has(r));if(u.length>0){const r=s.length>0&&s[s.length-1]!==""?`
|
|
2
|
+
`+u.join(`
|
|
3
|
+
`)+`
|
|
4
|
+
`:u.join(`
|
|
5
|
+
`)+`
|
|
6
|
+
`;n.appendFileSync(t,r,"utf8")}}export{g as isGitRepo,T as ensureGitRepo,c as ensureGitignore,_ as autoGitAdd,d as addAll,A as stageFiles,m as getDefaultAuthor};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import r from"fs";import
|
|
1
|
+
import r from"fs";import c from"path";import{log as a}from"../log/logUtils.js";import{copyDirectoryFiltered as $}from"./backupUtils.js";import{ValidationError as y,BusinessError as w,SystemError as x}from"../error/errorHandler.js";import{createPnpmNpmrc as E}from"../common/npmrcUtils.js";import{copyNodeModulesFromCache as C}from"../common/templateCacheUtils.js";import{resolveProjectPath as u}from"../common/projectPathUtils.js";import F from"../../appConfig/index.js";async function S(o,i,f={}){if(!o)throw new y("Source project ID cannot be empty",{field:"sourceProjectId"});if(!i)throw new y("Target project ID cannot be empty",{field:"targetProjectId"});const h=f.sourceIsolationContext||{},m=f.targetIsolationContext||{},n=u(o,h),s=u(i,m);if(!r.existsSync(n))throw new w(`Source project ${o} does not exist`,{sourceProjectId:o,sourceProjectPath:n});if(r.existsSync(s))throw new w(`Target project ${i} already exists`,{targetProjectId:i,targetProjectPath:s});try{a(i,"INFO",`Start copying project from ${o} to ${i}`,{sourceProjectId:o,targetProjectId:i}),r.mkdirSync(s,{recursive:!0}),a(i,"INFO",`Target project directory created successfully: ${s}`,{targetProjectId:i});const p=await r.promises.readdir(n,{withFileTypes:!0});for(const e of p){const l=c.join(n,e.name),t=c.join(s,e.name);e.isDirectory()?(await r.promises.mkdir(t,{recursive:!0}),await $(l,t)):e.isFile()&&(await r.promises.mkdir(c.dirname(t),{recursive:!0}),await r.promises.copyFile(l,t))}if(a(i,"INFO",`Project copied successfully: ${i}`,{sourceProjectId:o,targetProjectId:i}),await C(s,i),await E(s,i),F.GIT_ENABLED)try{const e=await import("../../service/gitService.js");await e.init({workspaceType:"pageApp",projectId:i,isolationContext:m}),await e.commit({workspaceType:"pageApp",projectId:i,isolationContext:m,message:`copy project: ${o} -> ${i}`})}catch(e){a(i,"WARN","Git init/commit failed, skipping",{error:e.message})}return{success:!0,message:`Project ${o} successfully copied to ${i}`,sourceProjectId:o,targetProjectId:i,targetProjectPath:s}}catch(p){if(a(i,"ERROR",`Copy project failed: ${p.message}`,{sourceProjectId:o,targetProjectId:i}),r.existsSync(s))try{await r.promises.rm(s,{recursive:!0,force:!0}),a(i,"INFO","Copy failed, target project directory cleaned",{targetProjectId:i})}catch(e){a(i,"ERROR",`Clean target project directory failed: ${e.message}`,{targetProjectId:i,originalError:e.message})}throw p.isOperational?p:new x(`Copy project failed: ${p.message}`,{sourceProjectId:o,targetProjectId:i,sourceProjectPath:n,targetProjectPath:s,originalError:p.message})}}export{S as copyProject};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import l from"fs";import m from"path";import
|
|
1
|
+
import l from"fs";import m from"path";import E from"../../appConfig/index.js";import{log as o}from"../log/logUtils.js";import{ValidationError as F,SystemError as C,ResourceError as R}from"../error/errorHandler.js";import{extractZip as b}from"../common/zipUtils.js";import{getFrameworkInfo as x}from"./frameworkDetectorUtils.js";import{resolveProjectPath as p,shouldUseIsolationPath as $}from"../common/projectPathUtils.js";async function _(e){try{const s=l.readFileSync(e);if(s.includes(0))return!0;const c=s.toString("utf-8");for(let f=0;f<c.length;f++){const i=c.charCodeAt(f);if(i<32&&i!==9&&i!==10&&i!==13)return!0}return!1}catch{return!1}}function h(e){const s=m.extname(e).toLowerCase();return[".jpg",".jpeg",".png",".gif",".bmp",".webp",".svg"].includes(s)}async function D(e,s,c,f){const i=[],a=await l.promises.readdir(e,{withFileTypes:!0});a.sort((r,n)=>r.isDirectory()&&!n.isDirectory()?-1:!r.isDirectory()&&n.isDirectory()?1:r.name.toLowerCase().localeCompare(n.name.toLowerCase()));for(const r of a){const n=m.join(e,r.name);if(!(r.name.startsWith(".")&&r.name!==".gitignore"||(E.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(r.name))&&!(r.isDirectory()&&E.TRAVERSE_EXCLUDE_DIRS.includes(r.name)))if(r.isDirectory()){const t=await D(n,s,c,f);if(t.length===0){const u=s||p(c),y=m.relative(u,n).replace(/\\/g,"/");i.push({name:y,isDir:!0})}else i.push(...t)}else try{const t=await l.promises.stat(n),u=s||p(c),y=m.relative(u,n),w=await _(n),S=`${f}/${y}`,d={name:y,isDir:!1,binary:w,sizeExceeded:t.size>E.MAX_INLINE_FILE_SIZE_BYTES,contents:"",fileProxyUrl:S};if(!d.sizeExceeded)if(d.binary){if(h(n)){const v=l.readFileSync(n);d.contents=v.toString("base64")}}else d.contents=l.readFileSync(n,"utf-8");i.push(d)}catch(t){o(c,"WARN",`Process file failed: ${n} - ${t.message}`,{filePath:n,error:t.message})}}return i}async function N(e,s,c){const f=Date.now(),i=m.basename(e);try{o(i,"INFO","Start getting project content",{projectPath:e,command:s}),o(i,"DEBUG","Start traversing project directory",{projectPath:e});const a=await D(e,e,i,c);o(i,"DEBUG","Project directory traversal completed",{projectPath:e,fileCount:a.length});let r=a;s!=="cpage_config"&&(r=a.filter(t=>t.name!=="cpage_config.json")),o(i,"DEBUG","Start detecting framework information",{projectPath:e});const n=x(e),g={files:r,...n};return o(i,"INFO",`Project content obtained, total ${r.length} files`,{projectPath:e,fileCount:r.length,command:s,elapsedMs:Date.now()-f}),g}catch(a){throw o(i,"ERROR",`Get project content failed: ${a.message}`,{projectPath:e,originalError:a.message,elapsedMs:Date.now()-f}),new C(`Get project content failed: ${a.message}`,{projectPath:e,originalError:a.message})}}async function U(e,s,c,f,i={}){const a=Date.now(),r=Number(s);if(!Number.isFinite(r))throw new F("Code version must be a number",{field:"codeVersion"});const n=m.join(E.UPLOAD_PROJECT_DIR,e),g=m.join(n,`${e}-v${r}.zip`);if(!l.existsSync(g))throw new R(`Backup file for version ${r} does not exist`,{projectId:e,codeVersion:r,backupZipPath:g});const t=$(i)?m.join(E.PROJECT_SOURCE_DIR,String(i.tenantId),String(i.spaceId),"_his",e):m.join(E.PROJECT_SOURCE_DIR,"_his",e);try{l.existsSync(t)&&await l.promises.rm(t,{recursive:!0,force:!0}),await l.promises.mkdir(t,{recursive:!0}),o(e,"INFO",`Start extracting backup file for version ${r}`,{projectId:e,codeVersion:r,backupZipPath:g,tempExtractDir:t}),o(e,"DEBUG","Start extracting version backup file",{projectId:e,backupZipPath:g,tempExtractDir:t}),await b(g,t),o(e,"DEBUG",`Version ${r} backup file extraction completed`,{projectId:e,codeVersion:r,tempExtractDir:t}),o(e,"DEBUG","Start traversing version directory",{projectId:e,tempExtractDir:t});const u=await D(t,t,e,f);let y=u;c!=="cpage_config"&&(y=u.filter(S=>S.name!=="cpage_config.json"));const w={files:y};return o(e,"INFO",`Version ${r} project content obtained`,{projectId:e,codeVersion:r,fileCount:w.files?w.files.length:0,command:c,elapsedMs:Date.now()-a}),w}finally{try{l.existsSync(t)&&(await l.promises.rm(t,{recursive:!0,force:!0}),o(e,"INFO",`Temporary directory cleaned: ${t}`,{projectId:e,codeVersion:r}))}catch(u){o(e,"WARN",`Clean temporary directory failed: ${u.message}`,{projectId:e,codeVersion:r,tempExtractDir:t,error:u.message})}}}export{N as getProjectContent,U as getProjectContentByVersion,D as traverseDirectory,_ as isBinaryFile,h as isImageFile};
|
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.
|
|
4
|
+
"version": "1.3.1",
|
|
5
5
|
"description": "Cross-platform file service deployment tool with start/stop/restart CLI commands",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "index.js",
|
|
@@ -77,6 +77,8 @@
|
|
|
77
77
|
"multer": "^2.0.2",
|
|
78
78
|
"node-cron": "^3.0.3",
|
|
79
79
|
"pm2": "^6.0.11",
|
|
80
|
+
"isomorphic-git": "^1.30.1",
|
|
81
|
+
"diff": "^7.0.0",
|
|
80
82
|
"swagger-jsdoc": "^6.2.8",
|
|
81
83
|
"swagger-ui-express": "^5.0.0",
|
|
82
84
|
"tree-kill": "^1.2.2",
|