nuwax-file-server 1.3.0-beta.15 → 1.3.0-beta.17

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.
@@ -1 +1 @@
1
- import E from"path";import n from"fs";import o from"os";import{fileURLToPath as T}from"url";import I from"dotenv";const L=E.dirname(T(import.meta.url));I.config();function O(){const e=process.env.LOG_BASE_DIR,_=E.join(o.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 p(e){const _=E.join(L,"..",`env.${e}`);if(n.existsSync(_))I.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)}}p(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:O(),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",".agents/",".claude/",".opencode/",".codex/"],GIT_ENABLED:process.env.GIT_ENABLED?.toLowerCase()==="true",GIT_PATH:process.env.GIT_PATH||"",BASH_PATH:process.env.BASH_PATH||"",ZIP_WORKSPACE_EXCLUDE_DIRS:process.env.ZIP_WORKSPACE_EXCLUDE_DIRS?process.env.ZIP_WORKSPACE_EXCLUDE_DIRS.split(",").map(e=>e.trim()).filter(Boolean):[".git",".tmp",".claude",".agents",".codex",".opencode",".logs","__pycache__","node_modules","dist"]};export default R;
1
+ import E from"path";import n from"fs";import o from"os";import{fileURLToPath as T}from"url";import I from"dotenv";const L=E.dirname(T(import.meta.url));I.config();function O(){const e=process.env.LOG_BASE_DIR,_=E.join(o.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 p(e){const _=E.join(L,"..",`env.${e}`);if(n.existsSync(_))I.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)}}p(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:O(),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",".agents/",".claude/",".opencode/",".codex/"],GIT_ENABLED:process.env.GIT_ENABLED?.toLowerCase()==="true",BASH_PATH:process.env.BASH_PATH||"",ZIP_WORKSPACE_EXCLUDE_DIRS:process.env.ZIP_WORKSPACE_EXCLUDE_DIRS?process.env.ZIP_WORKSPACE_EXCLUDE_DIRS.split(",").map(e=>e.trim()).filter(Boolean):[".git",".tmp",".claude",".agents",".codex",".opencode",".logs","__pycache__","node_modules","dist"]};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.15",d=new ee,w={reset:"\x1B[0m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",cyan:"\x1B[36m"};function j(e){console.error(`${w.red}ERROR: ${e}${w.reset}`)}function b(e){console.log(`${w.green}${e}${w.reset}`)}function oe(e){console.log(`${w.blue}${e}${w.reset}`)}function y(e){return async(...t)=>{try{let o=await e(...t);o&&o.success===!1?(j(o.message||"Command failed"),process.exitCode=1):process.exitCode=0}catch(o){j(o?.message||"Command failed with unexpected error"),process.exitCode=1}}}async function se(e){let t=await N({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(`Service started (PID: ${t.pid})`),t}async function re(e){let t=await T({force:e.force,timeout:Number(e.timeout)});return t.success&&b(t.message||"Service stopped"),t}async function ne(e){let t=await O({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(t.message||"Service restarted"),t}function ie(){let e=_();return oe(`${n.name} service status:`),console.log(""),console.log(` Service name: ${n.name}`),console.log(` Running status: ${e.running?"Running":"Stopped"}`),console.log(` Message: ${e.message}`),console.log(` PID file: ${$()}`),e.pidInfo&&(console.log(` Process ID: ${e.pidInfo.pid}`),console.log(` Environment: ${e.pidInfo.env||"Unknown"}`),console.log(` Port: ${e.pidInfo.port||"Unknown"}`),console.log(` Version: ${e.pidInfo.version||A}`),console.log(` Platform: ${e.pidInfo.platform||process.platform}`),console.log(` Started at: ${e.pidInfo.startedAt||"Unknown"}`),console.log(` Uptime: ${L(e.pidInfo.startedAt)}`)),console.log(""),{success:e.running,message:e.message}}function ce(){d.name("nuwax-file-server").description("Cross-platform file service deployment tool, supporting start/stop/restart/status").version(A,"-v, --version","Display version number").helpOption("-h, --help","Display help information"),d.command("start").allowUnknownOption().description("Start service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(se)),d.command("stop").description("Stop service").option("--force","Force stop").option("--timeout <ms>","\u505C\u6B62\u670D\u52A1\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).action(y(re)),d.command("restart").allowUnknownOption().description("Restart service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(ne)),d.command("status").description("View service status").action(y(ie)),d.command("help").description("Display help information").action(()=>{d.outputHelp()}),d.parse(process.argv),process.argv.slice(2).length||(d.outputHelp(),process.exitCode=0)}ce();
2
+ import{Command as ee}from"commander";import{createRequire as te}from"module";import f from"path";import U from"os";import c from"fs-extra";import{spawn as E}from"cross-spawn";import V from"tree-kill";import{fileURLToPath as q}from"url";import{execFileSync as R}from"child_process";import M from"http";import{createRequire as G}from"module";var I=f.dirname(q(import.meta.url)),H=G(import.meta.url),n={name:"nuwax-file-server",pidDir:f.join(U.tmpdir(),"nuwax-file-server"),pidFileName:"server.pid",lockFileName:"start.lock",defaultStopTimeout:3e4,defaultStartTimeout:3e4,staleLockTimeout:12e4,checkInterval:500};function $(){return f.join(n.pidDir,n.pidFileName)}function F(){return f.join(n.pidDir,n.lockFileName)}function J(){let e=[f.join(I,"..","server.js"),f.join(I,"server.js")];for(let t of e)if(c.existsSync(t))return t;return e[0]}function W(){let e=["../../package.json","../package.json"];for(let t of e)try{let o=H(t);if(o?.version)return o.version}catch{}return"unknown"}function x(){try{let e=$();if(!c.existsSync(e))return null;let t=c.readFileSync(e,"utf8"),o=JSON.parse(t);return!o||typeof o.pid!="number"?null:o}catch(e){return e.code!=="ENOENT"&&console.error(`Read PID file failed: ${e.message}`),null}}function B(e){try{let t=$();c.ensureDirSync(n.pidDir),c.writeFileSync(t,JSON.stringify(e,null,2)),console.debug(`PID file written: ${t}`)}catch(t){throw console.error(`Write PID file failed: ${t.message}`),t}}function v(){try{let e=$();c.existsSync(e)&&(c.removeSync(e),console.debug(`PID file deleted: ${e}`))}catch(e){console.error(`Delete PID file failed: ${e.message}`)}}function g(e){try{return process.kill(e,0),!0}catch(t){return t.code!=="ESRCH"}}function K(e){try{return k()?R("powershell",["-NoProfile","-Command",`(Get-CimInstance Win32_Process -Filter "ProcessId = ${e}").CommandLine`],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim():R("ps",["-p",String(e),"-o","command="],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return""}}function X(e){if(!g(e))return!1;let t=K(e);if(!t)return!1;let o=t.toLowerCase(),s=f.basename(f.join(I,"..","server.js")).toLowerCase(),i=n.name.toLowerCase();return o.includes(s)||o.includes(i)}function z(){c.ensureDirSync(n.pidDir);let e=F(),t=`${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2,10)}`,o=JSON.stringify({pid:process.pid,token:t,createdAt:new Date().toISOString()},null,2);try{let s=c.openSync(e,"wx");return c.writeSync(s,o),{fd:s,token:t}}catch(s){if(s.code!=="EEXIST")throw s;try{let i=c.readFileSync(e,"utf8"),r=JSON.parse(i),a=Number(r?.pid),u=r?.createdAt?new Date(r.createdAt).getTime():0,l=Date.now()-u,C=Number.isFinite(a)&&a>0&&g(a),p=!u||Number.isNaN(l)||l>n.staleLockTimeout;if(!C||p){c.removeSync(e);let S=c.openSync(e,"wx");return c.writeSync(S,o),console.warn("Detected stale start lock, auto cleaned"),{fd:S,token:t}}}catch{c.removeSync(e);let r=c.openSync(e,"wx");return c.writeSync(r,o),console.warn("Detected invalid start lock, auto cleaned"),{fd:r,token:t}}throw s}}function Q(e){let t=e?.fd,o=e?.token;try{t!=null&&c.closeSync(t)}catch{}try{let s=F();if(c.existsSync(s)){let i=!1;if(!o)i=!0;else try{let r=c.readFileSync(s,"utf8");i=JSON.parse(r)?.token===o}catch{i=!0}i&&c.removeSync(s)}}catch{}}function k(){return process.platform==="win32"}async function Y(e=n.defaultStopTimeout){let t=x();return t?X(t.pid)?(console.log(`Existing service process detected (PID: ${t.pid}), stopping before start...`),await h(t.pid,!1)&&await D(t.pid,e)?(v(),{success:!0,message:`Existing process ${t.pid} stopped gracefully`}):(console.warn(`Graceful stop timeout or failed for PID ${t.pid}, force stop...`),await h(t.pid,!0)?await D(t.pid,e)?(v(),{success:!0,message:`Existing process ${t.pid} stopped forcibly`}):{success:!1,message:`Existing process ${t.pid} did not exit after force stop`}:{success:!1,message:`Failed to stop existing process ${t.pid}`})):(console.log(`Found stale PID file (PID: ${t.pid}), clean it...`),v(),{success:!0,message:"Stale PID file cleaned"}):{success:!0,message:"No existing service process"}}async function h(e,t=!1){return new Promise(o=>{if(!g(e)){console.debug(`Process ${e} does not exist, already stopped`),o(!0);return}let s=t?"SIGKILL":"SIGTERM",i=k()?"taskkill":"tree-kill";if(console.debug(`Use ${i} to stop process ${e} (signal: ${s})`),k()){let r=t?["/F","/PID",String(e)]:["/PID",String(e)],a=E("taskkill",r,{stdio:["ignore","pipe","pipe"],windowsHide:!0}),u="";a.stdout.on("data",l=>{u+=l.toString()}),a.stderr.on("data",l=>{u+=l.toString()}),a.on("error",l=>{console.error(`Stop process failed: ${l.message}`),o(!1)}),a.on("close",l=>{l===0?(console.debug(`Process ${e} stopped`),o(!0)):(console.warn(`taskkill exit code: ${l}, output: ${u}`),t?o(!1):h(e,!0).then(o))})}else V(e,s,r=>{r?r.code==="ESRCH"?(console.debug(`Process ${e} does not exist`),o(!0)):(console.error(`Stop process failed: ${r.message}`),o(!1)):(console.debug(`Process ${e} stopped`),o(!0))})})}async function D(e,t=n.defaultStopTimeout){let o=Date.now();for(;g(e);){if(Date.now()-o>t)return console.warn(`Wait for process ${e} to stop timeout (${t}ms)`),!1;await new Promise(i=>setTimeout(i,n.checkInterval))}let s=Date.now()-o;return console.debug(`Process ${e} stopped after ${s}ms`),!0}async function Z(e,t=n.defaultStartTimeout){let o=Number(e);if(!Number.isFinite(o)||o<=0)return!1;let s=Date.now();for(;Date.now()-s<=t;){if(await new Promise(r=>{let a=M.get({host:"127.0.0.1",port:o,path:"/health",timeout:Math.min(2e3,n.checkInterval*4)},u=>{r(u.statusCode>=200&&u.statusCode<300),u.resume()});a.on("timeout",()=>{a.destroy(),r(!1)}),a.on("error",()=>r(!1))}))return!0;await new Promise(r=>setTimeout(r,n.checkInterval))}return!1}async function N(e={}){let{env:t,port:o,config:s}=e,i=null;console.log(`Start service ${n.name}...`);try{i=z()}catch(r){return r.code==="EEXIST"?{success:!1,pid:null,message:"Another start operation is in progress, please retry later"}:{success:!1,pid:null,message:`Acquire start lock failed: ${r.message}`}}try{let r=Number(e.timeout)||n.defaultStopTimeout,a=await Y(r);if(!a.success)return{success:!1,pid:null,message:`Service start blocked: ${a.message}`};let u={...process.env};t&&(u.NODE_ENV=t,console.log(`Environment: ${t}`)),o&&(u.PORT=o,console.log(`Port: ${o}`)),s&&(u.CONFIG_FILE=s,console.log(`Configuration file: ${s}`));let l=J(),p=E("node",[l,...[]],{env:u,stdio:["pipe","pipe","pipe"],detached:!0,cwd:process.cwd()});if(p.stdout.on("data",m=>{process.stdout.write(m)}),p.stderr.on("data",m=>{process.stderr.write(m)}),p.on("error",m=>{console.error(`Start service failed: ${m.message}`)}),await new Promise(m=>setTimeout(m,2e3)),!g(p.pid))return{success:!1,pid:null,message:"Service start failed"};let S={pid:p.pid,startedAt:new Date().toISOString(),env:t||process.env.NODE_ENV||"production",port:o||process.env.PORT||"60000",version:W(),platform:process.platform};B(S);let P=Number(e.startTimeout)||n.defaultStartTimeout;return await Z(S.port,P)?(p.unref(),console.log(`Service started (PID: ${p.pid})`),console.log(`Service address: http://localhost:${S.port}`),{success:!0,pid:p.pid,message:"Service started successfully"}):(console.error(`Service health check timeout (${P}ms), stop failed instance...`),await h(p.pid,!0),v(),{success:!1,pid:null,message:`Service health check timeout (${P}ms)`})}finally{Q(i)}}async function T(e={}){let{force:t=!1,timeout:o=n.defaultStopTimeout}=e;console.log(`Stop service ${n.name}...`);let s=x();if(!s)return{success:!1,message:"Service not found"};if(!g(s.pid))return console.log("Service process has stopped, clean PID file..."),v(),{success:!0,message:"Service has stopped (process has exited)"};if(!await h(s.pid,t))return{success:!1,message:"Stop service failed"};let r=await D(s.pid,o);return v(),r?{success:!0,message:"Service has stopped"}:{success:!1,message:"Service stop timeout"}}async function O(e={}){console.log(`Restart service ${n.name}...`);let t=await T(e);!t.success&&t.message!=="Service not found"&&console.warn(`Stop service failed: ${t.message}`),await new Promise(s=>setTimeout(s,2e3));let o=await N(e);return o.success?{success:!0,pid:o.pid,message:"Service has restarted"}:{success:!1,pid:null,message:`Restart failed: ${o.message}`}}function _(){let e=x();return e?g(e.pid)?{running:!0,pidInfo:e,message:"Service running"}:{running:!1,pidInfo:e,message:"Service process does not exist"}:{running:!1,pidInfo:null,message:"Service not running"}}function L(e){try{let t=new Date(e),o=new Date;if(isNaN(t.getTime()))return"unknown";let s=Math.floor((o-t)/1e3),i=Math.floor(s/3600),r=Math.floor(s%3600/60),a=s%60;return i>0?`${i} hours ${r} minutes ${a} seconds`:r>0?`${r} minutes ${a} seconds`:`${a} seconds`}catch{return"unknown"}}var Pe=te(import.meta.url),A="1.3.0-beta.17",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();
@@ -97,7 +97,6 @@ GIT_GITIGNORE_ENTRIES=node_modules/|.pnpm-store/|dist/|build/|.idea/|.vscode/|.D
97
97
  GIT_ENABLED=true
98
98
 
99
99
  # Windows 专用路径(Linux/macOS 留空)
100
- GIT_PATH=
101
100
  BASH_PATH=
102
101
 
103
102
  # zip-workspace 打包时排除的目录(逗号分隔)
@@ -97,7 +97,6 @@ GIT_GITIGNORE_ENTRIES=node_modules/|.pnpm-store/|dist/|build/|.idea/|.vscode/|.D
97
97
  GIT_ENABLED=true
98
98
 
99
99
  # Windows 专用路径(Linux/macOS 留空)
100
- GIT_PATH=
101
100
  BASH_PATH=
102
101
 
103
102
  # zip-workspace 打包时排除的目录(逗号分隔)
@@ -1 +1 @@
1
- import l from"express";import{asyncHandler as n,ValidationError as h}from"../utils/error/errorHandler.js";import r from"../service/gitService.js";import{log as c}from"../utils/log/logUtils.js";import{extractIsolationContext as I}from"../utils/common/projectPathUtils.js";const u=l.Router();function d(e){const{workspaceType:o,projectId:t,userId:s,cId:a}=e||{},p=I(e||{});return{workspaceType:o?String(o):void 0,projectId:t?String(t):void 0,userId:s?String(s):void 0,cId:a?String(a):void 0,isolationContext:p}}const y=[{path:"/init",method:"post",handler:n(async(e,o)=>{const t=d(e.body);c(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git init",t);const s=await r.init(t);o.status(200).json(s)})},{path:"/status",method:"get",handler:n(async(e,o)=>{const t=d(e.query);c(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git status",t);const s=await r.status(t);o.status(200).json(s)})},{path:"/commit",method:"post",handler:n(async(e,o)=>{const{message:t,files:s,authorName:a,authorEmail:p}=e.body||{},i={...d(e.body),message:t,files:s,authorName:a,authorEmail:p};if(!t)throw new h("Commit message cannot be empty",{field:"message"});c(i.projectId||`computer:${i.userId}:${i.cId}`,"INFO","Git commit",{...i,filesCount:s?s.length:"all"});const m=await r.commit(i);o.status(200).json(m)})},{path:"/add",method:"post",handler:n(async(e,o)=>{const{files:t}=e.body||{},s={...d(e.body),files:t};c(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git add",{...s,filesCount:t?t.length:"all"});const a=await r.add(s);o.status(200).json(a)})},{path:"/unstage",method:"post",handler:n(async(e,o)=>{const{files:t}=e.body||{},s={...d(e.body),files:t};c(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git unstage",{...s,filesCount:t?t.length:"all"});const a=await r.unstage(s);o.status(200).json(a)})},{path:"/discard",method:"post",handler:n(async(e,o)=>{const{files:t}=e.body||{},s={...d(e.body),files:t};c(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git discard",{...s,filesCount:t?t.length:"all"});const a=await r.discard(s);o.status(200).json(a)})},{path:"/log",method:"get",handler:n(async(e,o)=>{const{maxCount:t,branch:s,skip:a,filePath:p}=e.query||{},i={...d(e.query),maxCount:t?parseInt(t,10):50,skip:a?parseInt(a,10):0,branch:s,filePath:p};c(i.projectId||`computer:${i.userId}:${i.cId}`,"INFO","Git log",i);const m=await r.logHistory(i);o.status(200).json(m)})},{path:"/diff",method:"post",handler:n(async(e,o)=>{const{from:t,to:s,paths:a,source:p}=e.body||{},i={...d(e.body),from:t,to:s,paths:a,source:p};c(i.projectId||`computer:${i.userId}:${i.cId}`,"INFO","Git diff",{from:t,to:s});const m=await r.diff(i);o.status(200).json(m)})},{path:"/file-content",method:"post",handler:n(async(e,o)=>{const{ref:t,filePath:s}=e.body||{},a={...d(e.body),ref:t,filePath:s};c(a.projectId||`computer:${a.userId}:${a.cId}`,"INFO","Git file content",{ref:t,filePath:s});const p=await r.fileContent(a);o.status(200).json(p)})},{path:"/reset",method:"post",handler:n(async(e,o)=>{const{target:t,mode:s}=e.body||{},a={...d(e.body),target:t,mode:s||"mixed"};if(!t)throw new h("Reset target cannot be empty",{field:"target"});c(a.projectId||`computer:${a.userId}:${a.cId}`,"INFO","Git reset",{target:t,mode:s});const p=await r.reset(a);o.status(200).json(p)})},{path:"/checkout",method:"post",handler:n(async(e,o)=>{const{target:t}=e.body||{},s={...d(e.body),target:t};if(!t)throw new h("Checkout target cannot be empty",{field:"target"});c(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git checkout files",{target:t});const a=await r.checkout(s);o.status(200).json(a)})},{path:"/revert",method:"post",handler:n(async(e,o)=>{const{target:t}=e.body||{},s={...d(e.body),target:t};if(!t)throw new h("Revert target cannot be empty",{field:"target"});c(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git revert",{target:t});const a=await r.revert(s);o.status(200).json(a)})},{path:"/tags",method:"get",handler:n(async(e,o)=>{const t=d(e.query);c(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git list tags",t);const s=await r.listTags(t);o.status(200).json(s)})},{path:"/tag-create",method:"post",handler:n(async(e,o)=>{const{tagName:t,message:s}=e.body||{},a={...d(e.body),tagName:t,message:s};if(!t)throw new h("Tag name cannot be empty",{field:"tagName"});c(a.projectId||`computer:${a.userId}:${a.cId}`,"INFO","Git create tag",{tagName:t});const p=await r.createTag(a);o.status(200).json(p)})},{path:"/tag-delete",method:"post",handler:n(async(e,o)=>{const{tagName:t}=e.body||{},s={...d(e.body),tagName:t};if(!t)throw new h("Tag name cannot be empty",{field:"tagName"});c(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git delete tag",{tagName:t});const a=await r.deleteTag(s);o.status(200).json(a)})},{path:"/branches",method:"get",handler:n(async(e,o)=>{const t=d(e.query);c(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git list branches",t);const s=await r.listBranches(t);o.status(200).json(s)})},{path:"/branch-create",method:"post",handler:n(async(e,o)=>{const{branchName:t,startPoint:s}=e.body||{},a={...d(e.body),branchName:t,startPoint:s};if(!t)throw new h("Branch name cannot be empty",{field:"branchName"});c(a.projectId||`computer:${a.userId}:${a.cId}`,"INFO","Git create branch",{branchName:t,startPoint:s});const p=await r.createBranch(a);o.status(200).json(p)})},{path:"/branch-switch",method:"post",handler:n(async(e,o)=>{const{branchName:t}=e.body||{},s={...d(e.body),branchName:t};if(!t)throw new h("Branch name cannot be empty",{field:"branchName"});c(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git switch branch",{branchName:t});const a=await r.switchBranch(s);o.status(200).json(a)})},{path:"/branch-delete",method:"post",handler:n(async(e,o)=>{const{branchName:t,force:s}=e.body||{},a={...d(e.body),branchName:t,force:s===!0};if(!t)throw new h("Branch name cannot be empty",{field:"branchName"});c(a.projectId||`computer:${a.userId}:${a.cId}`,"INFO","Git delete branch",{branchName:t,force:s});const p=await r.deleteBranch(a);o.status(200).json(p)})},{path:"/stash",method:"post",handler:n(async(e,o)=>{const{message:t,files:s}=e.body||{},a={...d(e.body),message:t,files:s};c(a.projectId||`computer:${a.userId}:${a.cId}`,"INFO","Git stash push",a);const p=await r.stashPush(a);o.status(200).json(p)})},{path:"/stash-pop",method:"post",handler:n(async(e,o)=>{const{index:t,files:s}=e.body||{},a={...d(e.body),index:t,files:s};c(a.projectId||`computer:${a.userId}:${a.cId}`,"INFO","Git stash pop",{index:t});const p=await r.stashPop(a);o.status(200).json(p)})},{path:"/stash-list",method:"get",handler:n(async(e,o)=>{const t=d(e.query);c(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git stash list",t);const s=await r.stashList(t);o.status(200).json(s)})}];y.forEach(e=>{e.middleware?u[e.method](e.path,e.middleware,e.handler):u[e.method](e.path,e.handler)});export default u;
1
+ import l from"express";import{asyncHandler as n,ValidationError as m}from"../utils/error/errorHandler.js";import c from"../service/gitService.js";import{log as r}from"../utils/log/logUtils.js";import{extractIsolationContext as I}from"../utils/common/projectPathUtils.js";const u=l.Router();function d(a){const{workspaceType:o,projectId:t,userId:e,cId:s}=a||{},i=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:i}}const y=[{path:"/init",method:"post",handler:n(async(a,o)=>{const t=d(a.body);r(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git init",t);const e=await c.init(t);o.status(200).json(e)})},{path:"/status",method:"get",handler:n(async(a,o)=>{const t=d(a.query);r(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git status",t);const e=await c.status(t);o.status(200).json(e)})},{path:"/commit",method:"post",handler:n(async(a,o)=>{const{message:t,files:e,authorName:s,authorEmail:i}=a.body||{},p={...d(a.body),message:t,files:e,authorName:s,authorEmail:i};if(!t)throw new m("Commit message cannot be empty",{field:"message"});r(p.projectId||`computer:${p.userId}:${p.cId}`,"INFO","Git commit",{...p,filesCount:e?e.length:"all"});const h=await c.commit(p);o.status(200).json(h)})},{path:"/add",method:"post",handler:n(async(a,o)=>{const{files:t}=a.body||{},e={...d(a.body),files:t};r(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git add",{...e,filesCount:t?t.length:"all"});const s=await c.add(e);o.status(200).json(s)})},{path:"/unstage",method:"post",handler:n(async(a,o)=>{const{files:t}=a.body||{},e={...d(a.body),files:t};r(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git unstage",{...e,filesCount:t?t.length:"all"});const s=await c.unstage(e);o.status(200).json(s)})},{path:"/discard",method:"post",handler:n(async(a,o)=>{const{files:t}=a.body||{},e={...d(a.body),files:t};r(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git discard",{...e,filesCount:t?t.length:"all"});const s=await c.discard(e);o.status(200).json(s)})},{path:"/log",method:"get",handler:n(async(a,o)=>{const{maxCount:t,branch:e,skip:s,filePath:i}=a.query||{},p={...d(a.query),maxCount:t?parseInt(t,10):50,skip:s?parseInt(s,10):0,branch:e,filePath:i};r(p.projectId||`computer:${p.userId}:${p.cId}`,"INFO","Git log",p);const h=await c.logHistory(p);o.status(200).json(h)})},{path:"/diff",method:"post",handler:n(async(a,o)=>{const{from:t,to:e,paths:s,source:i}=a.body||{},p={...d(a.body),from:t,to:e,paths:s,source:i};r(p.projectId||`computer:${p.userId}:${p.cId}`,"INFO","Git diff",{from:t,to:e});const h=await c.diff(p);o.status(200).json(h)})},{path:"/file-content",method:"post",handler:n(async(a,o)=>{const{ref:t,filePath:e}=a.body||{},s={...d(a.body),ref:t,filePath:e};r(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git file content",{ref:t,filePath:e});const i=await c.fileContent(s);o.status(200).json(i)})},{path:"/reset",method:"post",handler:n(async(a,o)=>{const{target:t,mode:e}=a.body||{},s={...d(a.body),target:t,mode:e||"mixed"};if(!t)throw new m("Reset target cannot be empty",{field:"target"});r(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git reset",{target:t,mode:e});const i=await c.reset(s);o.status(200).json(i)})},{path:"/checkout",method:"post",handler:n(async(a,o)=>{const{target:t}=a.body||{},e={...d(a.body),target:t};if(!t)throw new m("Checkout target cannot be empty",{field:"target"});r(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git checkout files",{target:t});const s=await c.checkout(e);o.status(200).json(s)})},{path:"/tags",method:"get",handler:n(async(a,o)=>{const t=d(a.query);r(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git list tags",t);const e=await c.listTags(t);o.status(200).json(e)})},{path:"/tag-create",method:"post",handler:n(async(a,o)=>{const{tagName:t,message:e}=a.body||{},s={...d(a.body),tagName:t,message:e};if(!t)throw new m("Tag name cannot be empty",{field:"tagName"});r(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git create tag",{tagName:t});const i=await c.createTag(s);o.status(200).json(i)})},{path:"/tag-delete",method:"post",handler:n(async(a,o)=>{const{tagName:t}=a.body||{},e={...d(a.body),tagName:t};if(!t)throw new m("Tag name cannot be empty",{field:"tagName"});r(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git delete tag",{tagName:t});const s=await c.deleteTag(e);o.status(200).json(s)})},{path:"/branches",method:"get",handler:n(async(a,o)=>{const t=d(a.query);r(t.projectId||`computer:${t.userId}:${t.cId}`,"INFO","Git list branches",t);const e=await c.listBranches(t);o.status(200).json(e)})},{path:"/branch-create",method:"post",handler:n(async(a,o)=>{const{branchName:t,startPoint:e}=a.body||{},s={...d(a.body),branchName:t,startPoint:e};if(!t)throw new m("Branch name cannot be empty",{field:"branchName"});r(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git create branch",{branchName:t,startPoint:e});const i=await c.createBranch(s);o.status(200).json(i)})},{path:"/branch-switch",method:"post",handler:n(async(a,o)=>{const{branchName:t}=a.body||{},e={...d(a.body),branchName:t};if(!t)throw new m("Branch name cannot be empty",{field:"branchName"});r(e.projectId||`computer:${e.userId}:${e.cId}`,"INFO","Git switch branch",{branchName:t});const s=await c.switchBranch(e);o.status(200).json(s)})},{path:"/branch-delete",method:"post",handler:n(async(a,o)=>{const{branchName:t,force:e}=a.body||{},s={...d(a.body),branchName:t,force:e===!0};if(!t)throw new m("Branch name cannot be empty",{field:"branchName"});r(s.projectId||`computer:${s.userId}:${s.cId}`,"INFO","Git delete branch",{branchName:t,force:e});const i=await c.deleteBranch(s);o.status(200).json(i)})}];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,4 +1,8 @@
1
- import b from"path";import E from"fs";import O from"../appConfig/index.js";import{log as c}from"../utils/log/logUtils.js";import{ValidationError as R,BusinessError as I,SystemError as m,ResourceError as G}from"../utils/error/errorHandler.js";import{resolveProjectPath as st}from"../utils/common/projectPathUtils.js";import{getGitInstance as w,isGitRepo as at,ensureGitRepo as p,ensureGitignore as rt}from"../utils/git/gitUtils.js";function y(i){const{workspaceType:e,projectId:s,isolationContext:a,userId:t,cId:r}=i||{};if(!e||!["pageApp","taskAgent"].includes(e))throw new R("workspaceType is required and must be pageApp or taskAgent",{field:"workspaceType"});if(e==="taskAgent"){if(!t||!r)throw new R("taskAgent mode requires userId and cId",{field:"userId/cId"});const n=b.join(O.COMPUTER_WORKSPACE_DIR,String(t),String(r));if(!E.existsSync(n))throw new G("Computer workspace does not exist",{userId:t,cId:r});return{targetPath:n,logId:`computer:${t}:${r}`}}if(!s)throw new R("pageApp mode requires projectId",{field:"projectId"});const o=st(s,a||{});if(!E.existsSync(o))throw new G("Project does not exist",{projectId:s});return{targetPath:o,logId:s}}async function C(i={}){const{targetPath:e,logId:s}=y(i);if(at(e))return{success:!0,message:"Git repository already initialized",logId:s,alreadyExists:!0};try{const a=w(e);await a.init(),rt(e);const t=O.GIT_DEFAULT_AUTHOR_NAME,r=O.GIT_DEFAULT_AUTHOR_EMAIL;return await a.addConfig("user.name",t,!1,"local"),await a.addConfig("user.email",r,!1,"local"),c(s,"INFO","Git repository initialized",{logId:s,targetPath:e}),{success:!0,message:"Git repository initialized successfully",logId:s,alreadyExists:!1}}catch(a){throw c(s,"ERROR","Failed to initialize Git repository",{logId:s,error:a.message}),new m("Failed to initialize Git repository",{originalError:a.message})}}async function _(i={}){const{targetPath:e,logId:s}=y(i);await p(e);try{const a=w(e),t=await a.status(),r=await a.raw(["status","--porcelain"]),o=[],n=[],u=[],d=[],l=[];if(r)for(const f of r.split(`
2
- `).filter(Boolean)){const F=f[0],h=f[1],g=f.substring(3);F==="M"?o.push(g):F==="A"?(o.push(g),u.push(g)):F==="D"?(o.push(g),d.push(g)):(F==="R"||F==="C")&&o.push(g),h==="M"?n.push(g):h==="D"?(n.push(g),d.push(g)):h==="?"&&l.push(g)}return{success:!0,logId:s,current:t.current,staged:o,modified:n,created:u,deleted:d,untracked:l,conflicted:t.conflicted,ahead:t.ahead,behind:t.behind,tracking:t.tracking}}catch(a){throw c(s,"ERROR","Failed to get Git status",{logId:s,error:a.message}),new m("Failed to get Git status",{originalError:a.message})}}async function S(i={}){const{message:e,files:s,authorName:a,authorEmail:t}=i;if(!e||typeof e!="string")throw new R("Commit message cannot be empty",{field:"message"});const{targetPath:r,logId:o}=y(i);await p(r);try{const n=w(r);if(Array.isArray(s)&&s.length>0){const l=s.filter(f=>E.existsSync(b.join(r,f)));l.length>0&&await n.add(l)}else await n.add("--all");const u=[];if(a||t){const l=a||O.GIT_DEFAULT_AUTHOR_NAME,f=t||O.GIT_DEFAULT_AUTHOR_EMAIL;u.push(`--author=${l} <${f}>`)}const d=await n.commit(e,u);return c(o,"INFO","Git commit successful",{logId:o,commitHash:d.commit,message:e}),{success:!0,message:"Commit successful",logId:o,commit:d.commit,summary:d.summary}}catch(n){if(n.message&&n.message.includes("nothing to commit"))return{success:!0,message:"Nothing to commit",logId:o,nothingToCommit:!0};throw c(o,"ERROR","Failed to commit",{logId:o,error:n.message}),new m("Failed to commit",{originalError:n.message})}}async function B(i={}){const{files:e}=i,{targetPath:s,logId:a}=y(i);await p(s);try{const t=w(s);return Array.isArray(e)&&e.length>0?await t.add(e):await t.add("--all"),c(a,"INFO","Git add successful",{logId:a,filesCount:e?e.length:"all"}),{success:!0,message:"Files staged successfully",logId:a}}catch(t){throw c(a,"ERROR","Failed to add files",{logId:a,error:t.message}),new m("Failed to add files",{originalError:t.message})}}async function $(i={}){const{files:e}=i,{targetPath:s,logId:a}=y(i);await p(s);try{const t=w(s);return Array.isArray(e)&&e.length>0?(await t.raw(["restore","--staged","--",...e]),c(a,"INFO","Git unstage specified files",{logId:a,files:e}),{success:!0,message:"Specified files unstaged successfully",logId:a,files:e}):(await t.raw(["restore","--staged","."]),c(a,"INFO","Git unstage all files",{logId:a}),{success:!0,message:"All files unstaged successfully",logId:a,files:"all"})}catch(t){throw c(a,"ERROR","Failed to unstage",{logId:a,error:t.message}),new m("Failed to unstage files",{originalError:t.message})}}async function v(i,e){const s=new Set;for(const t of e){let r=b.dirname(t);for(;r&&r!==".";)s.add(r),r=b.dirname(r)}const a=[...s].sort((t,r)=>{const o=t.split(/[/\\]/).length;return r.split(/[/\\]/).length-o});for(const t of a){const r=b.join(i,t);try{(await E.promises.readdir(r)).length===0&&await E.promises.rmdir(r)}catch{}}}async function D(i={}){const{files:e}=i,{targetPath:s,logId:a}=y(i);await p(s);try{const t=w(s),r=await t.status(),o=new Set([...r.created]),n=new Set([...r.not_added||[]]),u=Array.isArray(e)&&e.length>0?e:null,d=[],l=[],f=[];if(u)for(const h of u)o.has(h)?d.push(h):n.has(h)?l.push(h):f.push(h);else{for(const g of r.created)d.push(g);const h=new Set([...r.staged,...r.modified,...r.deleted]);for(const g of h)o.has(g)||f.push(g)}if(f.length>0)for(const h of f)try{await t.raw(["restore","--staged","--worktree","--",h])}catch{l.push(h)}if(d.length>0){await t.raw(["rm","--cached","-f","--",...d]);for(const h of d){const g=b.join(s,h);E.existsSync(g)&&await E.promises.unlink(g)}await v(s,d)}if(l.length>0){for(const h of l){const g=b.join(s,h);E.existsSync(g)&&await E.promises.unlink(g)}await v(s,l)}const F=f.length+d.length+l.length;return c(a,"INFO","Git discard",{logId:a,trackedFiles:f.length,newFiles:d.length,untrackedFiles:l.length}),{success:!0,message:"Files discarded successfully",logId:a,discardedCount:F,trackedFiles:f,newFiles:d,untrackedFiles:l}}catch(t){throw c(a,"ERROR","Failed to discard",{logId:a,error:t.message}),new m("Failed to discard files",{originalError:t.message})}}async function L(i={}){const{maxCount:e=50,branch:s,skip:a=0,filePath:t}=i,{targetPath:r,logId:o}=y(i);await p(r);try{const n=w(r),u=Math.min(Math.max(1,e),500),d=Math.max(0,a),l=[];t||l.push("--reflog"),s&&l.push(s),l.push(`--max-count=${u}`),d>0&&l.push(`--skip=${d}`),t&&l.push("--",t);const F=(await n.log(l)).all.map(h=>({hash:h.hash,date:h.date,message:h.message,author_name:h.author_name,author_email:h.author_email}));return{success:!0,logId:o,commits:F,total:F.length}}catch(n){throw c(o,"ERROR","Failed to get Git log",{logId:o,error:n.message}),new m("Failed to get Git log",{originalError:n.message})}}async function M(i={}){const{source:e="worktree",from:s,to:a,paths:t}=i,{targetPath:r,logId:o}=y(i);await p(r);try{const n=w(r);let u=[];switch(e){case"staged":u.push("--cached");break;case"commit":if(s&&a)u.push(`${s}..${a}`);else if(s)if((await n.raw(["log","--format=%P","-1",s])).trim())u.push(`${s}^..${s}`);else{const F=Array.isArray(t)&&t.length>0?["--",...t]:[],[h,g]=await Promise.all([n.raw(["diff-tree","-p",s,...F]),n.raw(["diff-tree","--numstat","-r",s,...F])]),N=[];let x=0,T=0;for(const et of g.split(`
3
- `).filter(Boolean)){const A=et.split(" ");if(A.length===3){const k=parseInt(A[0],10)||0,P=parseInt(A[1],10)||0;N.push({file:A[2],changes:k+P,insertions:k,deletions:P,binary:!1}),x+=k,T+=P}}return{success:!0,logId:o,source:e,diff:h,summary:{files:N,insertions:x,deletions:T}}}break;case"worktree":default:u.push("HEAD");break}Array.isArray(t)&&t.length>0&&u.push("--",...t);const[d,l]=await Promise.all([n.diff(u),n.diffSummary(u)]);return{success:!0,logId:o,source:e,diff:d,summary:{files:l.files.map(f=>({file:f.file,changes:f.changes,insertions:f.insertions,deletions:f.deletions,binary:f.binary})),insertions:l.insertions,deletions:l.deletions}}}catch(n){throw c(o,"ERROR","Failed to get Git diff",{logId:o,error:n.message}),new m("Failed to get Git diff",{originalError:n.message})}}async function j(i={}){const{ref:e="HEAD",filePath:s}=i;if(!s)throw new R("filePath is required",{field:"filePath"});const{targetPath:a,logId:t}=y(i);await p(a);try{const r=w(a);let o;if(e==="worktree"){const n=b.join(a,s);E.existsSync(n)?o=E.readFileSync(n,"utf-8"):o=""}else if(e==="staged"||e==="")try{o=await r.show([`:${s}`])}catch{o=""}else try{o=await r.show([`${e}:${s}`])}catch{o=""}return{success:!0,logId:t,filePath:s,ref:e,content:o}}catch(r){throw r instanceof G?r:(c(t,"ERROR","Failed to get file content",{logId:t,ref:e,filePath:s,error:r.message}),new m("Failed to get file content",{originalError:r.message}))}}async function H(i={}){const{target:e,mode:s="mixed"}=i;if(!e)throw new R("Reset target cannot be empty",{field:"target"});if(!["soft","mixed","hard"].includes(s))throw new R("Mode must be soft, mixed or hard",{field:"mode"});const{targetPath:a,logId:t}=y(i);await p(a);try{const r=w(a),o=await r.log({maxCount:1}),n=o.latest?o.latest.hash:null;return await r.reset([`--${s}`,e]),c(t,"INFO","Git reset successful",{logId:t,target:e,mode:s,previousHead:n}),{success:!0,message:`Reset (${s}) to ${e} successful`,logId:t,target:e,mode:s,previousHead:n}}catch(r){throw c(t,"ERROR","Failed to reset",{logId:t,target:e,mode:s,error:r.message}),new m("Failed to reset",{originalError:r.message})}}async function U(i={}){const{target:e}=i;if(!e)throw new R("Checkout target cannot be empty",{field:"target"});const{targetPath:s,logId:a}=y(i);await p(s);try{return await w(s).raw(["checkout",e,"--","."]),c(a,"INFO","Git checkout files successful",{logId:a,target:e}),{success:!0,message:`Checkout files from ${e} successful`,logId:a,target:e}}catch(t){throw c(a,"ERROR","Failed to checkout files",{logId:a,target:e,error:t.message}),new m("Failed to checkout files",{originalError:t.message})}}async function z(i={}){const{target:e}=i;if(!e)throw new R("Revert target cannot be empty",{field:"target"});const{targetPath:s,logId:a}=y(i);await p(s);try{const t=w(s),r=await t.status();if(!r.isClean())throw new I("\u5DE5\u4F5C\u533A\u6709\u672A\u63D0\u4EA4\u7684\u4FEE\u6539\uFF0C\u8BF7\u5148 commit \u6216 stash \u540E\u518D revert",{staged:r.staged,modified:r.modified});return await t.revert(e),c(a,"INFO","Git revert successful",{logId:a,target:e}),{success:!0,message:`Revert ${e} successful`,logId:a,target:e}}catch(t){if(t instanceof I)throw t;try{await git.raw(["revert","--abort"])}catch{}throw t.message&&t.message.includes("CONFLICT")?new I("Revert \u4EA7\u751F\u51B2\u7A81\uFF0C\u65E0\u6CD5\u81EA\u52A8\u5408\u5E76\uFF0C\u8BE5 commit \u6682\u4E0D\u652F\u6301 revert",{target:e}):(c(a,"ERROR","Failed to revert",{logId:a,target:e,error:t.message}),new m("Failed to revert",{originalError:t.message}))}}async function q(i={}){const{targetPath:e,logId:s}=y(i);await p(e);try{const t=await w(e).tags();return{success:!0,logId:s,tags:t.all,latest:t.latest}}catch(a){throw c(s,"ERROR","Failed to list tags",{logId:s,error:a.message}),new m("Failed to list tags",{originalError:a.message})}}async function W(i={}){const{tagName:e,message:s}=i;if(!e)throw new R("Tag name cannot be empty",{field:"tagName"});const{targetPath:a,logId:t}=y(i);await p(a);try{const r=w(a);return s?await r.addAnnotatedTag(e,s):await r.addTag(e),c(t,"INFO","Git tag created",{logId:t,tagName:e,annotated:!!s}),{success:!0,message:"Tag created successfully",logId:t,tagName:e}}catch(r){throw c(t,"ERROR","Failed to create tag",{logId:t,tagName:e,error:r.message}),new m("Failed to create tag",{originalError:r.message})}}async function K(i={}){const{tagName:e}=i;if(!e)throw new R("Tag name cannot be empty",{field:"tagName"});const{targetPath:s,logId:a}=y(i);await p(s);try{return await w(s).tag(["-d",e]),c(a,"INFO","Git tag deleted",{logId:a,tagName:e}),{success:!0,message:"Tag deleted successfully",logId:a,tagName:e}}catch(t){throw c(a,"ERROR","Failed to delete tag",{logId:a,tagName:e,error:t.message}),new m("Failed to delete tag",{originalError:t.message})}}async function V(i={}){const{targetPath:e,logId:s}=y(i);await p(e);try{const t=await w(e).branch();return{success:!0,logId:s,branches:t.branches,current:t.current}}catch(a){throw c(s,"ERROR","Failed to list branches",{logId:s,error:a.message}),new m("Failed to list branches",{originalError:a.message})}}async function J(i={}){const{branchName:e,startPoint:s}=i;if(!e)throw new R("Branch name cannot be empty",{field:"branchName"});const{targetPath:a,logId:t}=y(i);await p(a);try{const r=w(a);return s?await r.checkoutBranch(e,s):await r.checkoutLocalBranch(e),c(t,"INFO","Git branch created",{logId:t,branchName:e,startPoint:s}),{success:!0,message:"Branch created and switched to",logId:t,branchName:e}}catch(r){throw c(t,"ERROR","Failed to create branch",{logId:t,branchName:e,error:r.message}),new m("Failed to create branch",{originalError:r.message})}}async function Q(i={}){const{branchName:e}=i;if(!e)throw new R("Branch name cannot be empty",{field:"branchName"});const{targetPath:s,logId:a}=y(i);await p(s);try{const t=w(s),r=await t.status();if(!r.isClean())throw new I("Working directory is not clean, please commit or stash your changes before switching branches",{staged:r.staged,modified:r.modified,untracked:r.not_added});return await t.checkout(e),c(a,"INFO","Git branch switched",{logId:a,branchName:e}),{success:!0,message:"Branch switched successfully",logId:a,branchName:e}}catch(t){throw t instanceof I?t:(c(a,"ERROR","Failed to switch branch",{logId:a,branchName:e,error:t.message}),new m("Failed to switch branch",{originalError:t.message}))}}async function X(i={}){const{branchName:e,force:s=!1}=i;if(!e)throw new R("Branch name cannot be empty",{field:"branchName"});const{targetPath:a,logId:t}=y(i);await p(a);try{const r=w(a);if((await r.status()).current===e)throw new I("Cannot delete the current branch, please switch to another branch first");return s?await r.deleteLocalBranch(e,!0):await r.deleteLocalBranch(e),c(t,"INFO","Git branch deleted",{logId:t,branchName:e,force:s}),{success:!0,message:"Branch deleted successfully",logId:t,branchName:e}}catch(r){throw r instanceof I?r:(c(t,"ERROR","Failed to delete branch",{logId:t,branchName:e,error:r.message}),new m("Failed to delete branch",{originalError:r.message}))}}async function Y(i={}){const{message:e,files:s}=i,{targetPath:a,logId:t}=y(i);await p(a);try{const r=w(a),o=["push"];return e&&o.push("-m",e),Array.isArray(s)&&s.length>0&&(await r.add(s),o.push("--",...s)),await r.stash(o),c(t,"INFO","Git stash push",{logId:t,stashMessage:e}),{success:!0,message:"Changes stashed successfully",logId:t}}catch(r){throw c(t,"ERROR","Failed to stash",{logId:t,error:r.message}),new m("Failed to stash changes",{originalError:r.message})}}async function Z(i={}){const{index:e,files:s}=i,{targetPath:a,logId:t}=y(i);await p(a);try{const r=w(a),o=await r.stashList().catch(()=>({all:[],total:0}));if((o.all&&o.all.length||o.total||0)===0)throw new I("No stash entries to restore");const u=e!=null?`stash@{${Number(e)}}`:"stash@{0}";return Array.isArray(s)&&s.length>0?(await r.raw(["checkout",u,"--",...s]),c(t,"INFO","Git stash pop (partial)",{logId:t,index:e,files:s}),{success:!0,message:"Stash restored (partial)",logId:t,index:e,files:s}):(await r.stash(["pop",u]),c(t,"INFO","Git stash pop",{logId:t,index:e}),{success:!0,message:"Stash restored successfully",logId:t})}catch(r){throw r instanceof I?r:(c(t,"ERROR","Failed to pop stash",{logId:t,index:e,error:r.message}),new m("Failed to pop stash",{originalError:r.message}))}}async function tt(i={}){const{targetPath:e,logId:s}=y(i);await p(e);try{const a=w(e),t=await a.stashList(),r=[];for(let o=0;o<t.all.length;o++){const n=t.all[o];let u=[];try{u=(await a.raw(["stash","show",`stash@{${o}}`,"--name-only"])).trim().split(`
4
- `).filter(Boolean)}catch{}r.push({index:o,hash:n.hash,message:n.message,date:n.date,files:u})}return{success:!0,logId:s,stashes:r,total:r.length}}catch(a){throw c(s,"ERROR","Failed to list stashes",{logId:s,error:a.message}),new m("Failed to list stashes",{originalError:a.message})}}export{C as init,_ as status,S as commit,B as add,$ as unstage,D as discard,L as logHistory,M as diff,j as fileContent,H as reset,U as checkout,z as revert,q as listTags,W as createTag,K as deleteTag,V as listBranches,J as createBranch,Q as switchBranch,X as deleteBranch,Y as stashPush,Z as stashPop,tt as stashList};export default{init:C,status:_,commit:S,add:B,unstage:$,discard:D,logHistory:L,diff:M,fileContent:j,reset:H,checkout:U,revert:z,listTags:q,createTag:W,deleteTag:K,listBranches:V,createBranch:J,switchBranch:Q,deleteBranch:X,stashPush:Y,stashPop:Z,stashList:tt};
1
+ import A from"path";import n from"fs";import ce from"crypto";import f from"isomorphic-git";import{createPatch as le}from"diff";import _ from"../appConfig/index.js";import{log as m}from"../utils/log/logUtils.js";import{ValidationError as O,BusinessError as H,SystemError as I,ResourceError as C}from"../utils/error/errorHandler.js";import{resolveProjectPath as fe}from"../utils/common/projectPathUtils.js";import{isGitRepo as de,ensureGitRepo as k,ensureGitignore as ue,addAll as D,getDefaultAuthor as he}from"../utils/git/gitUtils.js";function x(i){const{workspaceType:e,projectId:s,isolationContext:t,userId:a,cId:r}=i||{};if(!e||!["pageApp","taskAgent"].includes(e))throw new O("workspaceType is required and must be pageApp or taskAgent",{field:"workspaceType"});if(e==="taskAgent"){if(!a||!r)throw new O("taskAgent mode requires userId and cId",{field:"userId/cId"});const c=A.join(_.COMPUTER_WORKSPACE_DIR,String(a),String(r));if(!n.existsSync(c))throw new C("Computer workspace does not exist",{userId:a,cId:r});return{targetPath:c,logId:`computer:${a}:${r}`}}if(!s)throw new O("pageApp mode requires projectId",{field:"projectId"});const o=fe(s,t||{});if(!n.existsSync(o))throw new C("Project does not exist",{projectId:s});return{targetPath:o,logId:s}}function j(i){for(let e=0;e<Math.min(i.length,8e3);e++)if(i[e]===0)return!0;return!1}function v(i){const e=Buffer.from(`blob ${i.length}\0`);return ce.createHash("sha1").update(Buffer.concat([e,i])).digest("hex")}function ge(i,e,s,t,a,r,o){const u=le(i,t?e:"",a?s:"",t?`a/${i}`:"/dev/null",a?`b/${i}`:"/dev/null").split(`
2
+ `).slice(4);u.length>0&&u[u.length-1]===""&&u.pop();const h=[];let g=0,d=0;for(const P of u){if(P.startsWith("@@")){const R=P.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);if(R){const $=R[1],N=R[2]!==void 0?parseInt(R[2],10):1,T=R[3],p=R[4]!==void 0?parseInt(R[4],10):1,B=N===1?$:`${$},${N}`,G=p===1?T:`${T},${p}`;h.push(`@@ -${B} +${G} @@`);continue}}P.startsWith("+")&&!P.startsWith("+++")?g++:P.startsWith("-")&&!P.startsWith("---")&&d++,h.push(P)}const l=t&&e!==""&&!e.endsWith(`
3
+ `),y=a&&s!==""&&!s.endsWith(`
4
+ `);if((l||y)&&h.length>0){let P=0;for(let p=0;p<h.length;p++)h[p].startsWith("@@")&&(P=p);let R=-1,$=-1,N=-1;for(let p=h.length-1;p>P;p--){const B=h[p];B.startsWith("-")&&!B.startsWith("---")&&R<0?R=p:B.startsWith("+")&&!B.startsWith("++")&&$<0?$=p:B.startsWith(" ")&&N<0&&(N=p)}const T=Math.max(R,$,N);if(T>=0){const p=h[T];if(p.startsWith(" "))(l||y)&&h.splice(T+1,0,"\");else if(p.startsWith("+")){let B=-1;for(let G=T-1;G>P;G--)if(h[G].startsWith("-")&&!h[G].startsWith("---")){B=G;break}B>=0&&l?(h.splice(B+1,0,"\"),y&&h.splice(T+2,0,"\")):y&&h.splice(T+1,0,"\")}else p.startsWith("-")&&l&&h.splice(T+1,0,"\")}}const S=t&&r?v(r).substring(0,7):"0000000",E=a&&o?v(o).substring(0,7):"0000000",F=[];return F.push(`diff --git a/${i} b/${i}`),t?a?F.push(`index ${S}..${E} 100644`):(F.push("deleted file mode 100644"),F.push(`index ${S}..0000000`)):(F.push("new file mode 100644"),F.push(`index 0000000..${E}`)),F.push(`--- ${t?`a/${i}`:"/dev/null"}`),F.push(`+++ ${a?`b/${i}`:"/dev/null"}`),{diff:[...F,...h].join(`
5
+ `)+`
6
+ `,insertions:g,deletions:d}}async function M(i){return f.resolveRef({fs:n,dir:i,ref:"HEAD"})}async function W(i,e,s){try{const{blob:t}=await f.readBlob({fs:n,dir:i,oid:e,filepath:s}),a=Buffer.from(t);return{content:a.toString("utf8"),buf:a}}catch{return null}}async function L(i={}){const{targetPath:e,logId:s}=x(i);if(de(e))return{success:!0,message:"Git repository already initialized",logId:s,alreadyExists:!0};try{return await f.init({fs:n,dir:e,defaultBranch:"main"}),ue(e),await f.setConfig({fs:n,dir:e,path:"user.name",value:_.GIT_DEFAULT_AUTHOR_NAME}),await f.setConfig({fs:n,dir:e,path:"user.email",value:_.GIT_DEFAULT_AUTHOR_EMAIL}),m(s,"INFO","Git repository initialized",{logId:s,targetPath:e}),{success:!0,message:"Git repository initialized successfully",logId:s,alreadyExists:!1}}catch(t){throw m(s,"ERROR","Failed to initialize Git repository",{logId:s,error:t.message}),new I("Failed to initialize Git repository",{originalError:t.message})}}async function U(i={}){const{targetPath:e,logId:s}=x(i);await k(e);try{const t=await f.currentBranch({fs:n,dir:e,fullname:!1}),a=await f.statusMatrix({fs:n,dir:e}),r=[],o=[],c=[],w=[],u=[];for(const[h,g,d,l]of a)l===3?(r.push(h),c.push(h)):l===2?r.push(h):l===0&&g===1&&(r.push(h),w.push(h)),d===2&&l!==0?o.push(h):d===0&&l!==0&&w.push(h),l===0&&d!==0&&u.push(h);return{success:!0,logId:s,current:t,staged:[...new Set(r)],modified:[...new Set(o)],created:[...new Set(c)],deleted:[...new Set(w)],untracked:[...new Set(u)],conflicted:[],ahead:0,behind:0,tracking:null}}catch(t){throw m(s,"ERROR","Failed to get Git status",{logId:s,error:t.message}),new I("Failed to get Git status",{originalError:t.message})}}async function q(i={}){const{message:e,files:s,authorName:t,authorEmail:a}=i;if(!e||typeof e!="string")throw new O("Commit message cannot be empty",{field:"message"});const{targetPath:r,logId:o}=x(i);await k(r);try{if(Array.isArray(s)&&s.length>0){const g=s.filter(d=>n.existsSync(A.join(r,d)));await Promise.all(g.map(d=>f.add({fs:n,dir:r,filepath:d})))}else await D(r);const w=(await f.statusMatrix({fs:n,dir:r})).some(([,,,g])=>g!==1);if(!w)return{success:!0,message:"Nothing to commit",logId:o,nothingToCommit:!0};const u={name:t||_.GIT_DEFAULT_AUTHOR_NAME,email:a||_.GIT_DEFAULT_AUTHOR_EMAIL},h=await f.commit({fs:n,dir:r,message:e,author:u});return m(o,"INFO","Git commit successful",{logId:o,commitHash:h,message:e}),{success:!0,message:"Commit successful",logId:o,commit:h,summary:{changes:w?1:0}}}catch(c){throw m(o,"ERROR","Failed to commit",{logId:o,error:c.message}),new I("Failed to commit",{originalError:c.message})}}async function z(i={}){const{files:e}=i,{targetPath:s,logId:t}=x(i);await k(s);try{return Array.isArray(e)&&e.length>0?await Promise.all(e.map(a=>f.add({fs:n,dir:s,filepath:a}))):await D(s),m(t,"INFO","Git add successful",{logId:t,filesCount:e?e.length:"all"}),{success:!0,message:"Files staged successfully",logId:t}}catch(a){throw m(t,"ERROR","Failed to add files",{logId:t,error:a.message}),new I("Failed to add files",{originalError:a.message})}}async function K(i={}){const{files:e}=i,{targetPath:s,logId:t}=x(i);await k(s);try{if(Array.isArray(e)&&e.length>0){for(const o of e)await f.resetIndex({fs:n,dir:s,filepath:o});return m(t,"INFO","Git unstage specified files",{logId:t,files:e}),{success:!0,message:"Specified files unstaged successfully",logId:t,files:e}}const r=(await f.statusMatrix({fs:n,dir:s})).filter(([,,,o])=>o!==1).map(([o])=>o);for(const o of r)await f.resetIndex({fs:n,dir:s,filepath:o});return m(t,"INFO","Git unstage all files",{logId:t}),{success:!0,message:"All files unstaged successfully",logId:t,files:"all"}}catch(a){throw m(t,"ERROR","Failed to unstage",{logId:t,error:a.message}),new I("Failed to unstage files",{originalError:a.message})}}async function V(i,e){const s=new Set;for(const a of e){let r=A.dirname(a);for(;r&&r!==".";)s.add(r),r=A.dirname(r)}const t=[...s].sort((a,r)=>{const o=a.split(/[/\\]/).length;return r.split(/[/\\]/).length-o});for(const a of t){const r=A.join(i,a);try{(await n.promises.readdir(r)).length===0&&await n.promises.rmdir(r)}catch{}}}async function J(i={}){const{files:e}=i,{targetPath:s,logId:t}=x(i);await k(s);try{const a=await f.statusMatrix({fs:n,dir:s}),r=Array.isArray(e)&&e.length>0?new Set(e):null,o=[],c=[],w=[];for(const[g,d,l,y]of a)r&&!r.has(g)||(d!==0?(y!==1||l!==1)&&o.push(g):d===0&&y!==0?c.push(g):d===0&&y===0&&l!==0&&w.push(g));const u=await M(s);for(const g of o){const d=await W(s,u,g);if(!d)continue;const l=A.join(s,g);n.mkdirSync(A.dirname(l),{recursive:!0}),n.writeFileSync(l,d.buf),await f.add({fs:n,dir:s,filepath:g})}for(const g of c){try{await f.remove({fs:n,dir:s,filepath:g})}catch{}const d=A.join(s,g);n.existsSync(d)&&await n.promises.unlink(d)}c.length>0&&await V(s,c);for(const g of w){const d=A.join(s,g);n.existsSync(d)&&await n.promises.unlink(d)}w.length>0&&await V(s,w);const h=o.length+c.length+w.length;return m(t,"INFO","Git discard",{logId:t,trackedFiles:o.length,newFiles:c.length,untrackedFiles:w.length}),{success:!0,message:"Files discarded successfully",logId:t,discardedCount:h,trackedFiles:o,newFiles:c,untrackedFiles:w}}catch(a){throw m(t,"ERROR","Failed to discard",{logId:t,error:a.message}),new I("Failed to discard files",{originalError:a.message})}}async function Q(i={}){const{maxCount:e=50,branch:s,skip:t=0,filePath:a}=i,{targetPath:r,logId:o}=x(i);await k(r);try{const c=Math.min(Math.max(1,e),500),w=Math.max(0,t),u={fs:n,dir:r,depth:c+w};s&&(u.ref=s),a&&(u.filepath=a);const d=(await f.log(u)).slice(w,w+c).map(l=>({hash:l.oid,date:new Date(l.commit.author.timestamp*1e3).toISOString(),message:l.commit.message,author_name:l.commit.author.name,author_email:l.commit.author.email}));return{success:!0,logId:o,commits:d,total:d.length}}catch(c){throw m(o,"ERROR","Failed to get Git log",{logId:o,error:c.message}),new I("Failed to get Git log",{originalError:c.message})}}async function X(i={}){const{source:e="worktree",from:s,to:t,paths:a}=i,{targetPath:r,logId:o}=x(i);await k(r);try{const c=Array.isArray(a)&&a.length>0?new Set(a):null;let w="";const u=[];let h=0,g=0;async function d(l,y,S){const E=!!y,F=!!S;if(E&&F&&y.content===S.content)return;let b=!1;if(E&&j(y.buf)&&(b=!0),F&&!b&&j(S.buf)&&(b=!0),b){const N=E?v(y.buf).substring(0,7):"0000000",T=F?v(S.buf).substring(0,7):"0000000",p=[`diff --git a/${l} b/${l}`];E?F?p.push(`index ${N}..${T} 100644`):(p.push("deleted file mode 100644"),p.push(`index ${N}..0000000`)):(p.push("new file mode 100644"),p.push(`index 0000000..${T}`)),p.push(`Binary files ${E?`a/${l}`:"/dev/null"} and ${F?`b/${l}`:"/dev/null"} differ`),w+=p.join(`
7
+ `)+`
8
+ `,u.push({file:l,changes:0,insertions:0,deletions:0,binary:!0});return}const{diff:P,insertions:R,deletions:$}=ge(l,E?y.content:"",F?S.content:"",E,F,E?y.buf:null,F?S.buf:null);w+=P,u.push({file:l,changes:R+$,insertions:R,deletions:$,binary:!1}),h+=R,g+=$}if(e==="commit"){let l,y;if(s&&t)l=s,y=t;else if(s){const b=await f.readCommit({fs:n,dir:r,oid:s});b.commit.parent&&b.commit.parent.length>0?(l=b.commit.parent[0],y=s):(l=null,y=s)}else throw new O("source=commit requires at least 'from'",{field:"from"});const S=l?new Set(await f.listFiles({fs:n,dir:r,ref:l})):new Set,E=new Set(await f.listFiles({fs:n,dir:r,ref:y})),F=new Set([...S,...E]);for(const b of F){if(c&&!c.has(b))continue;const P=S.has(b)?await W(r,l,b):null,R=E.has(b)?await W(r,y,b):null;await d(b,P,R)}}else{const l=await M(r),y=await f.statusMatrix({fs:n,dir:r});for(const[S,E,F,b]of y){if(c&&!c.has(S))continue;if(e==="staged"){if(b===1||E===0&&b===0)continue}else if(E===0&&b===0||E===1&&F===1&&b===1)continue;const P=E!==0?await W(r,l,S):null;let R=null;if(F!==0){const $=A.join(r,S);if(n.existsSync($))try{const N=n.readFileSync($);R={content:N.toString("utf8"),buf:N}}catch{}}await d(S,P,R)}}return{success:!0,logId:o,source:e,diff:w,summary:{files:u,insertions:h,deletions:g}}}catch(c){throw c instanceof O?c:(m(o,"ERROR","Failed to get Git diff",{logId:o,error:c.message}),new I("Failed to get Git diff",{originalError:c.message}))}}async function Y(i={}){const{ref:e="HEAD",filePath:s}=i;if(!s)throw new O("filePath is required",{field:"filePath"});const{targetPath:t,logId:a}=x(i);await k(t);try{let r;if(e==="worktree"||e==="staged"||e===""){const o=A.join(t,s);r=n.existsSync(o)?n.readFileSync(o,"utf8"):""}else try{const o=await f.resolveRef({fs:n,dir:t,ref:e}),{blob:c}=await f.readBlob({fs:n,dir:t,oid:o,filepath:s});r=Buffer.from(c).toString("utf8")}catch{r=""}return{success:!0,logId:a,filePath:s,ref:e,content:r}}catch(r){throw r instanceof C?r:(m(a,"ERROR","Failed to get file content",{logId:a,ref:e,filePath:s,error:r.message}),new I("Failed to get file content",{originalError:r.message}))}}async function Z(i={}){const{target:e,mode:s="mixed"}=i;if(!e)throw new O("Reset target cannot be empty",{field:"target"});if(!["soft","mixed","hard"].includes(s))throw new O("Mode must be soft, mixed or hard",{field:"mode"});const{targetPath:t,logId:a}=x(i);await k(t);try{const r=await f.log({fs:n,dir:t,depth:1}),o=r.length>0?r[0].oid:null,c=await f.currentBranch({fs:n,dir:t,fullname:!1});if(!c)throw new H("Cannot reset: detached HEAD");const w=s==="hard"?new Set(await f.listFiles({fs:n,dir:t,ref:"HEAD"})):null;if(await f.writeRef({fs:n,dir:t,ref:`refs/heads/${c}`,value:e,force:!0}),s==="mixed"||s==="hard"){const u=await f.listFiles({fs:n,dir:t,ref:e}),h=new Set(u);for(const d of u)await f.resetIndex({fs:n,dir:t,filepath:d,ref:e});const g=await f.statusMatrix({fs:n,dir:t});for(const[d,,,l]of g)if(!h.has(d)&&l!==0)try{await f.remove({fs:n,dir:t,filepath:d})}catch{}}if(s==="hard"){const u=await f.listFiles({fs:n,dir:t,ref:e}),h=new Set(u);for(const g of u){const d=await W(t,e,g);if(!d)continue;const l=A.join(t,g);n.mkdirSync(A.dirname(l),{recursive:!0}),n.writeFileSync(l,d.buf)}if(w){for(const g of w)if(!h.has(g)){const d=A.join(t,g);n.existsSync(d)&&n.unlinkSync(d)}}}return m(a,"INFO","Git reset successful",{logId:a,target:e,mode:s,previousHead:o}),{success:!0,message:`Reset (${s}) to ${e} successful`,logId:a,target:e,mode:s,previousHead:o}}catch(r){throw r instanceof H?r:(m(a,"ERROR","Failed to reset",{logId:a,target:e,mode:s,error:r.message}),new I("Failed to reset",{originalError:r.message}))}}async function ee(i={}){const{target:e}=i;if(!e)throw new O("Checkout target cannot be empty",{field:"target"});const{targetPath:s,logId:t}=x(i);await k(s);try{const a=await f.listFiles({fs:n,dir:s,ref:e});for(const r of a){const o=await W(s,e,r);if(!o)continue;const c=A.join(s,r);n.mkdirSync(A.dirname(c),{recursive:!0}),n.writeFileSync(c,o.buf),await f.add({fs:n,dir:s,filepath:r})}return m(t,"INFO","Git checkout files successful",{logId:t,target:e}),{success:!0,message:`Checkout files from ${e} successful`,logId:t,target:e}}catch(a){throw m(t,"ERROR","Failed to checkout files",{logId:t,target:e,error:a.message}),new I("Failed to checkout files",{originalError:a.message})}}async function te(i={}){const{targetPath:e,logId:s}=x(i);await k(e);try{const t=await f.listTags({fs:n,dir:e});return{success:!0,logId:s,tags:t,latest:t.length>0?t[t.length-1]:null}}catch(t){throw m(s,"ERROR","Failed to list tags",{logId:s,error:t.message}),new I("Failed to list tags",{originalError:t.message})}}async function se(i={}){const{tagName:e,message:s}=i;if(!e)throw new O("Tag name cannot be empty",{field:"tagName"});const{targetPath:t,logId:a}=x(i);await k(t);try{return s?await f.annotatedTag({fs:n,dir:t,ref:e,message:s,tagger:he()}):await f.tag({fs:n,dir:t,ref:e}),m(a,"INFO","Git tag created",{logId:a,tagName:e,annotated:!!s}),{success:!0,message:"Tag created successfully",logId:a,tagName:e}}catch(r){throw m(a,"ERROR","Failed to create tag",{logId:a,tagName:e,error:r.message}),new I("Failed to create tag",{originalError:r.message})}}async function ae(i={}){const{tagName:e}=i;if(!e)throw new O("Tag name cannot be empty",{field:"tagName"});const{targetPath:s,logId:t}=x(i);await k(s);try{return await f.deleteRef({fs:n,dir:s,ref:`refs/tags/${e}`}),m(t,"INFO","Git tag deleted",{logId:t,tagName:e}),{success:!0,message:"Tag deleted successfully",logId:t,tagName:e}}catch(a){throw m(t,"ERROR","Failed to delete tag",{logId:t,tagName:e,error:a.message}),new I("Failed to delete tag",{originalError:a.message})}}async function re(i={}){const{targetPath:e,logId:s}=x(i);await k(e);try{const t=await f.listBranches({fs:n,dir:e}),a=await f.currentBranch({fs:n,dir:e,fullname:!1}),r={};for(const o of t)r[o]={name:o,current:o===a};return{success:!0,logId:s,branches:r,current:a}}catch(t){throw m(s,"ERROR","Failed to list branches",{logId:s,error:t.message}),new I("Failed to list branches",{originalError:t.message})}}async function ie(i={}){const{branchName:e,startPoint:s}=i;if(!e)throw new O("Branch name cannot be empty",{field:"branchName"});const{targetPath:t,logId:a}=x(i);await k(t);try{const r=s||"HEAD";return await f.branch({fs:n,dir:t,ref:e,object:r,checkout:!0}),m(a,"INFO","Git branch created",{logId:a,branchName:e,startPoint:s}),{success:!0,message:"Branch created and switched to",logId:a,branchName:e}}catch(r){throw m(a,"ERROR","Failed to create branch",{logId:a,branchName:e,error:r.message}),new I("Failed to create branch",{originalError:r.message})}}async function ne(i={}){const{branchName:e}=i;if(!e)throw new O("Branch name cannot be empty",{field:"branchName"});const{targetPath:s,logId:t}=x(i);await k(s);try{const a=await f.statusMatrix({fs:n,dir:s});if(a.some(([o,c,w,u])=>c===0&&u===0?!1:w!==1||u!==1)){const o=a.filter(([,u])=>u!==0),c=o.filter(([,,u])=>u!==1).map(([u])=>u),w=o.filter(([,,,u])=>u!==1).map(([u])=>u);throw new H("Working directory is not clean, please commit or stash your changes before switching branches",{staged:w,modified:c})}return await f.checkout({fs:n,dir:s,ref:e}),m(t,"INFO","Git branch switched",{logId:t,branchName:e}),{success:!0,message:"Branch switched successfully",logId:t,branchName:e}}catch(a){throw a instanceof H?a:(m(t,"ERROR","Failed to switch branch",{logId:t,branchName:e,error:a.message}),new I("Failed to switch branch",{originalError:a.message}))}}async function oe(i={}){const{branchName:e,force:s=!1}=i;if(!e)throw new O("Branch name cannot be empty",{field:"branchName"});const{targetPath:t,logId:a}=x(i);await k(t);try{if(await f.currentBranch({fs:n,dir:t,fullname:!1})===e)throw new H("Cannot delete the current branch, please switch to another branch first");return await f.deleteBranch({fs:n,dir:t,ref:e,force:s}),m(a,"INFO","Git branch deleted",{logId:a,branchName:e,force:s}),{success:!0,message:"Branch deleted successfully",logId:a,branchName:e}}catch(r){throw r instanceof H?r:(m(a,"ERROR","Failed to delete branch",{logId:a,branchName:e,error:r.message}),new I("Failed to delete branch",{originalError:r.message}))}}export{L as init,U as status,q as commit,z as add,K as unstage,J as discard,Q as logHistory,X as diff,Y as fileContent,Z as reset,ee as checkout,te as listTags,se as createTag,ae as deleteTag,re as listBranches,ie as createBranch,ne as switchBranch,oe as deleteBranch};export default{init:L,status:U,commit:q,add:z,unstage:K,discard:J,logHistory:Q,diff:X,fileContent:Y,reset:Z,checkout:ee,listTags:te,createTag:se,deleteTag:ae,listBranches:re,createBranch:ie,switchBranch:ne,deleteBranch:oe};
@@ -1,5 +1,5 @@
1
- import i from"fs";import r from"path";import{exec as it}from"child_process";import st from"cross-spawn";import ot from"archiver";import v from"../../appConfig/index.js";import{extractZip as H}from"../common/zipUtils.js";import{ValidationError as y,SystemError as W,FileError as Z}from"../error/errorHandler.js";import{log as o}from"../log/logUtils.js";import{ensurePrimaryAgentDirs as Q,syncAgents as X}from"../common/AgentWorkspaceUtils.js";function I(e){if(!e)return[];if(Array.isArray(e))return e.map(t=>typeof t=="string"?t.trim():"").filter(Boolean);if(typeof e=="string"){const t=e.trim();if(!t)return[];try{const s=JSON.parse(t);if(Array.isArray(s))return s.map(c=>typeof c=="string"?c.trim():"").filter(Boolean)}catch{}return[t]}return[]}async function tt(e,t,s){let c;try{c=await fetch(e)}catch(g){throw new Z(`Failed to download skill zip from url: ${e}`,{url:e,reason:g.message})}if(!c.ok)throw new Z(`Failed to download skill zip from url: ${e}`,{url:e,status:c.status,statusText:c.statusText});const n=c.headers.get("content-type")||"";n&&!n.includes("zip")&&!n.includes("octet-stream")&&o(s,"WARN","Downloaded skill url content-type is not typical zip",{url:e,contentType:n});const a=await c.arrayBuffer(),h=Buffer.from(a);await i.promises.writeFile(t,h)}async function b(e="computer"){const t=v.COMPUTER_WORKSPACE_DIR;if(!t)throw new y("COMPUTER_WORKSPACE_DIR configuration does not exist",{field:"COMPUTER_WORKSPACE_DIR"});return i.existsSync(t)||(await i.promises.mkdir(t,{recursive:!0}),o(e,"INFO","Created user workspace root directory",{workspaceRoot:t})),t}async function q(e,t){const s=r.join(e,t);if(i.existsSync(s)&&(await i.promises.lstat(s)).isDirectory())return s;const c=await i.promises.readdir(e,{withFileTypes:!0});for(const n of c){if(!n.isDirectory())continue;const a=r.join(e,n.name),h=r.join(a,t);if(i.existsSync(h)&&(await i.promises.lstat(h)).isDirectory())return h}return null}async function At(e){return q(e,"skills")}const rt=".dynamic_add.lock";function at(e){const t=r.join(e,rt);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 M(e,t){try{await i.promises.rename(e,t)}catch(s){if(s.code==="EXDEV"){async function c(n,a){if((await i.promises.lstat(n)).isDirectory()){await i.promises.mkdir(a,{recursive:!0});const g=await i.promises.readdir(n);for(const u of g)await c(r.join(n,u),r.join(a,u))}else await i.promises.copyFile(n,a)}await c(e,t),await i.promises.rm(e,{recursive:!0,force:!0})}else throw s}}async function nt(e,t,s,c,n,a){const h=t&&t.trim(),g=s&&s.trim(),u=c&&c.trim(),d=Array.isArray(n)&&n.length>0;if(!h&&!g&&!u&&!d)return;const w=r.join(e,".claude");try{if(i.existsSync(w)||await i.promises.mkdir(w,{recursive:!0}),h)try{const p={mcpServers:JSON.parse(t)},S=r.join(e,".mcp.json");await i.promises.writeFile(S,JSON.stringify(p,null,2),"utf-8"),o(a,"INFO","Written .mcp.json to workspace root")}catch(p){o(a,"WARN","Failed to parse/write mcpServersConfig, skipping",{error:p.message})}const f={};if(g)try{f.hooks=JSON.parse(s)}catch(p){o(a,"WARN","Failed to parse hooksConfig, skipping",{error:p.message})}if(u)try{f.permissions=JSON.parse(c)}catch(p){o(a,"WARN","Failed to parse permissionsConfig, skipping",{error:p.message})}if(Object.keys(f).length>0){const p=r.join(w,"settings.json");await i.promises.writeFile(p,JSON.stringify(f,null,2),"utf-8"),o(a,"INFO","Written .claude/settings.json",{keys:Object.keys(f)})}if(d){const p=r.join(w,"hooks");i.existsSync(p)||await i.promises.mkdir(p,{recursive:!0});for(const S of n){if(!S||!S.path||!S.content)continue;const l=r.normalize(S.path);if(l.startsWith("..")||r.isAbsolute(l)){o(a,"WARN","Hook script path contains traversal, skipping",{path:S.path});continue}const x=r.join(w,l),P=r.dirname(x);i.existsSync(P)||await i.promises.mkdir(P,{recursive:!0}),await i.promises.writeFile(x,S.content,"utf-8"),await i.promises.chmod(x,493),o(a,"INFO","Written hook script",{path:S.path})}}}catch(f){o(a,"WARN","Failed to write .claude/settings or .mcp.json or hook scripts, not blocking workspace creation",{error:f.message})}}async function ct(e,t,s,c,n,a,h,g){const u=Date.now(),d=`computer:${e}:${t}`,w=I(c),f=[],p=[],S=new Set;if(!e)throw new y("userId cannot be empty",{field:"userId"});if(!t)throw new y("cId cannot be empty",{field:"cId"});const l=await b(d),x=r.join(l,String(e),String(t),".tmp"),P=r.join(l,String(e),String(t)),{skillsDir:k,agentsDir:R,agentTypes:z}=await Q(P);i.existsSync(P)||await i.promises.mkdir(P,{recursive:!0});const D=r.join(x,`preserved_skills_${Date.now()}_${Math.round(Math.random()*1e6)}`);if(i.existsSync(k)){const N=(await i.promises.readdir(k,{withFileTypes:!0})).filter(O=>O.isDirectory()&&at(r.join(k,O.name)));if(N.length>0){await i.promises.mkdir(D,{recursive:!0});for(const O of N){const j=r.join(k,O.name),E=r.join(D,O.name);await M(j,E)}o(d,"INFO","\u4FDD\u7559\u542B .dynamic_add.lock \u7684 skill",{preserved:N.map(O=>O.name)})}}if(await C(k),await C(R),await i.promises.mkdir(k,{recursive:!0}),await i.promises.mkdir(R,{recursive:!0}),i.existsSync(D)){const m=await i.promises.readdir(D,{withFileTypes:!0});for(const N of m)if(N.isDirectory()){const O=r.join(D,N.name),j=r.join(k,N.name);await M(O,j)}await C(D)}const A=i.existsSync(k),T=i.existsSync(R);o(d,"INFO","Deleted old skills and agents directories completed",{userId:e,cId:t,targetSkillsDir:k,targetAgentsDir:R,agentTypes:z,skillsExists:A,agentsExists:T});const $=r.join(P,".mcp.json");i.existsSync($)&&await i.promises.unlink($);const B=r.join(P,".claude","settings.json");if(i.existsSync(B)&&await i.promises.unlink(B),await nt(P,n,h,a,g,d),!s&&w.length===0)return await X(P),o(d,"INFO","Created workspace (no uploaded file, no skills and agents)",{userId:e,cId:t,workspaceRoot:l,skillsDir:null,agentsDir:null,agentTypes:z,elapsedMs:Date.now()-u}),{message:"Workspace created (no uploaded file, no skills and agents)",workspaceRoot:l};if(s){if(!s.path)throw new y("Uploaded file has no valid path",{field:"file.path"});if(r.extname(s.originalname||s.filename||"").toLowerCase()!==".zip")throw new y("Only zip files are supported",{field:"file",originalName:s.originalname})}o(d,"DEBUG","Start processing workspace resources",{userId:e,cId:t,workspaceRoot:l,hasUploadedZip:!!s,skillUrlsCount:w.length});try{i.existsSync(x)||await i.promises.mkdir(x,{recursive:!0});const m=[];if(s){const j=r.join(x,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);p.push(j),await i.promises.mkdir(j,{recursive:!0}),o(d,"DEBUG","Start extracting uploaded zip file",{extractRoot:j}),await H(s.path,j),o(d,"DEBUG","Uploaded zip file extracted successfully",{extractRoot:j});const E=await q(j,"skills"),U=await q(j,"agents");if(E){await i.promises.mkdir(k,{recursive:!0});const _=await i.promises.readdir(E,{withFileTypes:!0});for(const L of _){if(!L.isDirectory())continue;const G=r.join(E,L.name),K=r.join(k,L.name);i.existsSync(K)&&await C(K),await M(G,K),S.add(L.name)}m.push("skills"),o(d,"INFO","skills updated to workspace",{userId:e,cId:t,workspaceRoot:l,targetSkillsDir:k,agentTypes:z})}else o(d,"INFO","skills directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:j});U?(await C(R),await M(U,R),m.push("agents"),o(d,"INFO","agents updated to workspace",{userId:e,cId:t,workspaceRoot:l,targetAgentsDir:R,agentTypes:z})):o(d,"INFO","agents directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:j})}for(let j=0;j<w.length;j+=1){const E=w[j],U=r.join(x,`skill_url_${Date.now()}_${j}_${Math.round(Math.random()*1e6)}.zip`);f.push(U);const _=r.join(x,`skill_url_extract_${Date.now()}_${j}_${Math.round(Math.random()*1e6)}`);p.push(_),await i.promises.mkdir(_,{recursive:!0}),o(d,"INFO","Start download skill zip from url",{userId:e,cId:t,skillUrl:E}),await tt(E,U,d),o(d,"INFO","Skill zip downloaded, start extracting",{userId:e,cId:t,skillUrl:E,downloadedZipPath:U}),await H(U,_);const G=(await i.promises.readdir(_,{withFileTypes:!0})).filter(F=>F.isDirectory()&&!F.name.startsWith(".")),Y=G.find(F=>F.name==="skills")?(await i.promises.readdir(r.join(_,"skills"),{withFileTypes:!0})).filter(F=>F.isDirectory()&&!F.name.startsWith(".")).map(F=>({name:F.name,sourcePath:r.join(_,"skills",F.name)})):G.map(F=>({name:F.name,sourcePath:r.join(_,F.name)}));if(Y.length===0){o(d,"WARN","No skill directory found after extracting skill url zip",{userId:e,cId:t,skillUrl:E,extractRoot:_});continue}await i.promises.mkdir(k,{recursive:!0});for(const F of Y){const J=r.join(k,F.name);i.existsSync(J)&&await C(J),await M(F.sourcePath,J),S.add(F.name),o(d,"INFO","Skill from url updated to workspace",{userId:e,cId:t,skillUrl:E,skillName:F.name,destSkillPath:J})}m.includes("skills")||m.push("skills")}m.length===0&&o(d,"WARN","No valid skills or agents directories found",{userId:e,cId:t,hasUploadedZip:!!s,skillUrlsCount:w.length});const N=Array.from(S),O=m.length>0?`Workspace created successfully, ${m.join(" and ")} updated`:"Workspace created successfully (skills and agents directories not found)";return o(d,"INFO",O,{userId:e,cId:t,updatedDirs:m,updatedSkills:N,agentTypes:z,elapsedMs:Date.now()-u}),await X(P),{message:O,workspaceRoot:l,updatedSkills:N}}catch(m){throw o(d,"ERROR","Failed to process workspace resources",{userId:e,cId:t,error:m.message,elapsedMs:Date.now()-u}),m instanceof y||m instanceof Z||m instanceof W?m:new W(`Failed to create workspace: ${m.message}`,{userId:e,cId:t})}finally{try{for(const m of p)i.existsSync(m)&&await i.promises.rm(m,{recursive:!0,force:!0})}catch(m){o(d,"WARN","Failed to clean up temporary extracted zip",{error:m.message})}for(const m of f)try{i.existsSync(m)&&await i.promises.unlink(m)}catch(N){o(d,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:m,error:N.message})}try{s&&s.path&&i.existsSync(s.path)&&await i.promises.unlink(s.path)}catch(m){o(d,"WARN","Failed to clean up uploaded zip file",{tempZipPath:s?.path,error:m.message})}}}async function lt(e,t,s,c){const n=Date.now(),a=`computer:${e}:${t}`,h=I(c),g=[],u=[];if(!e)throw new y("userId cannot be empty",{field:"userId"});if(!t)throw new y("cId cannot be empty",{field:"cId"});if(!s&&h.length===0)throw new y("file or skillUrls cannot both be empty",{field:"file|skillUrls"});if(s){if(!s.path)throw new y("Uploaded file has no valid path",{field:"file.path"});if(r.extname(s.originalname||s.filename||"").toLowerCase()!==".zip")throw new y("Only zip files are supported",{field:"file",originalName:s?.originalname})}const d=await b(a),w=r.join(d,String(e),String(t),".tmp"),f=r.join(d,String(e),String(t)),{skillsDir:p,agentTypes:S}=await Q(f);try{i.existsSync(f)||await i.promises.mkdir(f,{recursive:!0}),i.existsSync(p)||await i.promises.mkdir(p,{recursive:!0}),i.existsSync(w)||await i.promises.mkdir(w,{recursive:!0});const l=[],x=new Set;if(s){const k=r.join(w,`skill_push_${Date.now()}_${Math.round(Math.random()*1e6)}`);u.push(k),await i.promises.mkdir(k,{recursive:!0}),o(a,"DEBUG","Start extracting skill zip file",{extractRoot:k}),await H(s.path,k),o(a,"DEBUG","Skill zip file extracted successfully",{extractRoot:k});const R=await q(k,"skills");if(!R)o(a,"WARN","skills directory not found in uploaded zip",{userId:e,cId:t,extractRoot:k});else{const D=(await i.promises.readdir(R,{withFileTypes:!0})).filter(A=>A.isDirectory()&&!A.name.startsWith("."));if(D.length===0)o(a,"WARN","skills directory in uploaded zip has no skill subdirectories",{userId:e,cId:t,skillsDir:R});else for(const A of D){const T=r.join(R,A.name),$=r.join(p,A.name);i.existsSync($)&&await C($),await M(T,$),x.add(A.name),o(a,"INFO","skill pushed to workspace from uploaded zip",{userId:e,cId:t,skillName:A.name,destSkillPath:$})}}}for(let k=0;k<h.length;k+=1){const R=h[k],z=r.join(w,`skill_push_url_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}.zip`);g.push(z);const D=r.join(w,`skill_push_url_extract_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}`);u.push(D),await i.promises.mkdir(D,{recursive:!0}),o(a,"INFO","Start download skill zip for push from url",{userId:e,cId:t,skillUrl:R}),await tt(R,z,a),await H(z,D);const T=(await i.promises.readdir(D,{withFileTypes:!0})).filter(m=>m.isDirectory()&&!m.name.startsWith(".")),B=T.find(m=>m.name==="skills")?(await i.promises.readdir(r.join(D,"skills"),{withFileTypes:!0})).filter(m=>m.isDirectory()&&!m.name.startsWith(".")).map(m=>({name:m.name,sourcePath:r.join(D,"skills",m.name)})):T.map(m=>({name:m.name,sourcePath:r.join(D,m.name)}));if(B.length===0){o(a,"WARN","No skill directory found after extracting push skill url zip",{userId:e,cId:t,skillUrl:R,extractRoot:D});continue}for(const m of B){const N=r.join(p,m.name);i.existsSync(N)&&await C(N),await M(m.sourcePath,N),x.add(m.name),o(a,"INFO","skill pushed to workspace from url zip",{userId:e,cId:t,skillUrl:R,skillName:m.name,destSkillPath:N})}}for(const k of x)l.push(k);const P=l.length>0?`Pushed ${l.length} skills: ${l.join(", ")}`:"No valid skill directories found in file or skillUrls";return o(a,"INFO",P,{userId:e,cId:t,updatedSkills:l,agentTypes:S,elapsedMs:Date.now()-n}),await X(f),{message:P,workspaceRoot:d,updatedSkills:l}}catch(l){throw o(a,"ERROR","Failed to push skill to workspace",{userId:e,cId:t,error:l.message,elapsedMs:Date.now()-n}),l instanceof y||l instanceof Z||l instanceof W?l:new W(`Failed to push skill to workspace: ${l.message}`,{userId:e,cId:t})}finally{try{for(const l of u)i.existsSync(l)&&await i.promises.rm(l,{recursive:!0,force:!0})}catch(l){o(a,"WARN","Failed to clean up temporary extracted zip",{error:l.message})}for(const l of g)try{i.existsSync(l)&&await i.promises.unlink(l)}catch(x){o(a,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:l,error:x.message})}try{s&&s.path&&i.existsSync(s.path)&&await i.promises.unlink(s.path)}catch(l){o(a,"WARN","Failed to clean up uploaded zip file",{tempZipPath:s?.path,error:l.message})}}}async function pt(e,t,s,c){const n=Date.now(),a=`computer:${e}:${t}`;if(!e)throw new y("userId cannot be empty",{field:"userId"});if(!t)throw new y("cId cannot be empty",{field:"cId"});if(!s||!s.path)throw new y("file is required",{field:"file"});if(r.extname(s.originalname||s.filename||"").toLowerCase()!==".zip")throw new y("Only zip files are supported",{field:"file",originalName:s.originalname});const g=await b(a),u=r.join(g,String(e),String(t)),d=r.join(u,".tmp"),w=r.join(d,`template_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{i.existsSync(u)||await i.promises.mkdir(u,{recursive:!0}),i.existsSync(d)||await i.promises.mkdir(d,{recursive:!0}),await i.promises.mkdir(w,{recursive:!0}),o(a,"DEBUG","Start extracting template zip file",{extractRoot:w}),await H(s.path,w),o(a,"DEBUG","Template zip file extracted successfully",{extractRoot:w});const f=await i.promises.readdir(w,{withFileTypes:!0});for(const p of f){const S=r.join(w,p.name),l=r.join(u,p.name);if(p.isDirectory())await M(S,l);else{const x=r.dirname(l);i.existsSync(x)||await i.promises.mkdir(x,{recursive:!0}),await i.promises.copyFile(S,l)}}if(o(a,"INFO","Template files extracted to workspace",{userId:e,cId:t,targetDir:u,fileCount:f.length}),v.GIT_ENABLED&&(c==="true"||c===!0)){const p=await import("../../service/gitService.js");await p.default.init({workspaceType:"taskAgent",userId:e,cId:t}),await p.default.commit({workspaceType:"taskAgent",userId:e,cId:t,message:"Initial commit"}),o(a,"INFO","Git init and initial commit done",{userId:e,cId:t})}return o(a,"INFO","Init project template completed",{userId:e,cId:t,targetDir:u,elapsedMs:Date.now()-n}),{message:"Project template initialized successfully",workspaceRoot:u}}catch(f){throw o(a,"ERROR","Failed to init project template",{userId:e,cId:t,error:f.message,elapsedMs:Date.now()-n}),f instanceof y||f instanceof Z||f instanceof W?f:new W(`Failed to init project template: ${f.message}`,{userId:e,cId:t})}finally{try{i.existsSync(w)&&await i.promises.rm(w,{recursive:!0,force:!0})}catch(f){o(a,"WARN","Failed to clean up temporary extracted zip",{error:f.message})}try{s&&s.path&&i.existsSync(s.path)&&await i.promises.unlink(s.path)}catch(f){o(a,"WARN","Failed to clean up uploaded zip file",{tempZipPath:s?.path,error:f.message})}}}async function mt(e,t,s){if(!e)throw new y("userId cannot be empty",{field:"userId"});if(!t)throw new y("cId cannot be empty",{field:"cId"});if(!s||typeof s!="string"||!s.trim())throw new y("command cannot be empty",{field:"command"});const c=await b("computer"),n=r.join(c,String(e),String(t));if(!i.existsSync(n))throw new y("workspace directory does not exist",{field:"workDir",workDir:n});const a=`computer:${e}:${t}`;o(a,"INFO","Execute command in workspace",{userId:e,cId:t,workDir:n,command:s});const h=10*60*1e3,g={cwd:n,timeout:h,maxBuffer:50*1024*1024};return v.BASH_PATH&&(g.shell=v.BASH_PATH),v.GIT_PATH&&(g.env={...process.env,PATH:`${v.GIT_PATH}${r.delimiter}${process.env.PATH}`}),new Promise((u,d)=>{it(s,g,(w,f,p)=>{const S=w?w.killed?-1:w.code||1:0,l=w&&w.killed?(p||"")+`
2
- Command timed out after ${h/1e3}s`:p||"";o(a,"INFO","Execute command completed",{userId:e,cId:t,exitCode:S,killed:w?w.killed:!1,stdoutLength:f?f.length:0,stderrLength:l.length}),u({stdout:f||"",stderr:l,exitCode:S})})})}async function dt(e,t,s){const c=`computer:${e}:${t}`;if(!e)throw new y("userId cannot be empty",{field:"userId"});if(!t)throw new y("cId cannot be empty",{field:"cId"});const n=await b(c),a=r.join(n,String(e),String(t));if(!i.existsSync(a))throw new y("workspace directory does not exist",{field:"workDir",workDir:a});const h=v.ZIP_WORKSPACE_EXCLUDE_DIRS||[],g=Array.isArray(s)?s:[],u=new Set([...h,...g]),d=`${e}_${t}.zip`,w=Date.now();o(c,"INFO","Zip workspace request",{userId:e,cId:t,targetDir:a,excludeDirs:[...u]});const f=ot("zip",{zlib:{level:9}});return f.directory(a,!1,p=>{const S=p.name||"";if(S.split(/[\/\\]/).filter(Boolean).some(x=>u.has(x)))return!1;try{const x=r.join(a,S);if(i.lstatSync(x).isSymbolicLink())return!1}catch{return!1}return p}),f.on("warning",p=>{p.code==="ENOENT"?o(c,"WARN","Encountered file problem when creating zip",{message:p.message,code:p.code}):o(c,"ERROR","Encountered warning when creating zip",{message:p.message,code:p.code})}),f.on("error",p=>{o(c,"ERROR","Failed to create zip",{message:p.message,elapsedMs:Date.now()-w})}),f.on("end",()=>{o(c,"INFO","Workspace zip created successfully",{targetDir:a,zipFileName:d,elapsedMs:Date.now()-w})}),{archive:f,zipFileName:d}}const ft=new Set([...v.ZIP_WORKSPACE_EXCLUDE_DIRS||[],"dist-packages"]);function V(e){let t;try{t=i.readdirSync(e,{withFileTypes:!0})}catch{return null}const s=r.join(e,"scripts","package-platforms.mjs");if(i.existsSync(s))return e;for(const c of t)if(c.isDirectory()&&!ft.has(c.name)){const n=V(r.join(e,c.name));if(n)return n}return null}function et(e,t,s,c=10*60*1e3){return new Promise(n=>{const a=st(e,t,{...s,timeout:c});let h="",g="";a.stdout?.on("data",u=>{h+=u.toString()}),a.stderr?.on("data",u=>{g+=u.toString()}),a.on("error",u=>{n({stdout:h,stderr:g+`
3
- Failed to spawn ${e}: ${u.message}`,exitCode:1})}),a.on("close",(u,d)=>{n(u===null&&d?{stdout:h,stderr:g+`
4
- Process killed by signal ${d} (timeout after ${c/1e3}s)`,exitCode:-1}:{stdout:h,stderr:g,exitCode:u??1})})})}function ut(e){if(!e)return null;let t=e;t.endsWith(".tar.gz")?t=t.slice(0,-7):t.endsWith(".tar.bz2")?t=t.slice(0,-8):(t.endsWith(".tgz")||t.endsWith(".zip"))&&(t=t.slice(0,-4));const s=t.split("-");if(s.length<4)return null;let c=-1;for(let n=s.length-1;n>=2;n--)if(/^\d+\.\d+\.\d+$/.test(s[n])){c=n;break}return c<3?null:s.slice(2,c).join("-")}async function wt(e,t,s,c){const n=`computer:${e}:${t}`;if(!e)throw new y("userId cannot be empty",{field:"userId"});if(!t)throw new y("cId cannot be empty",{field:"cId"});if(!s)throw new y("agentId cannot be empty",{field:"agentId"});if(!c)throw new y("version cannot be empty",{field:"version"});const a=await b(n),h=r.join(a,String(e),String(t));if(!i.existsSync(h))throw new y("workspace directory does not exist",{field:"workDir",workDir:h});o(n,"INFO","Build agent package request",{userId:e,cId:t,agentId:s,version:c});const g=V(h);if(!g)throw new y("package-platforms.mjs not found in workspace",{field:"script"});o(n,"INFO","Found package script",{projectDir:g});const u={...process.env,CI:"true"};v.GIT_PATH&&(u.PATH=`${v.GIT_PATH}${r.delimiter}${process.env.PATH}`);const d=await et("pnpm",["install"],{cwd:g,env:u});if(o(n,"INFO","pnpm install completed",{exitCode:d.exitCode,stderrLength:d.stderr.length}),d.exitCode!==0)throw o(n,"ERROR","pnpm install failed",{stderr:d.stderr}),new W(`pnpm install failed: ${d.stderr}`);const w=r.join(g,"dist-packages"),f=["scripts/package-platforms.mjs",`agent-${s}`,c,w,"--print-artifacts"],p=await et("node",f,{cwd:g,env:u});if(o(n,"INFO","Build completed",{exitCode:p.exitCode,stdoutLength:p.stdout.length}),p.exitCode!==0)throw o(n,"ERROR","Build failed",{stderr:p.stderr}),new W(`Build failed: ${p.stderr}`);const S=[".tar.gz",".tar.bz2",".zip",".tgz"],l=[],x=p.stdout.split(`
5
- `);for(const P of x){const k=P.trim();if(!k)continue;const R=k.toLowerCase();if(!S.some($=>R.endsWith($)))continue;const z=r.resolve(g,k);let D=r.relative(h,z);D=D.split(r.sep).join("/");const A=r.basename(k),T=ut(A);l.push({path:D,fileName:A,platform:T||""})}return o(n,"INFO","Build agent package completed",{artifactsCount:l.length,artifacts:l}),{artifacts:l}}async function ht(e,t){const s=`computer:${e}:${t}`;if(!e)throw new y("userId cannot be empty",{field:"userId"});if(!t)throw new y("cId cannot be empty",{field:"cId"});const c=await b(s),n=r.join(c,String(e),String(t));if(!i.existsSync(n))return o(s,"WARN","Workspace not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const a=V(n);if(!a)return o(s,"WARN","package-platforms.mjs not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const h=r.join(a,"dist-packages");return i.existsSync(h)?(i.rmSync(h,{recursive:!0,force:!0}),o(s,"INFO","Cleaned dist-packages",{distPackagesDir:h}),{cleaned:!0}):(o(s,"INFO","dist-packages not found, nothing to clean",{distPackagesDir:h}),{cleaned:!1})}async function kt(e,t){const s=`computer:${e}:${t}`;if(!e)throw new y("userId cannot be empty",{field:"userId"});if(!t)throw new y("cId cannot be empty",{field:"cId"});const c=await b(s),n=r.join(c,String(e),String(t));return i.existsSync(n)?(await i.promises.rm(n,{recursive:!0,force:!0}),o(s,"INFO","Workspace deleted",{userId:e,cId:t,targetDir:n})):o(s,"WARN","Workspace not found, skip delete",{userId:e,cId:t,targetDir:n}),{deleted:!0}}export{ct as createWorkspace,lt as pushSkillsToWorkspace,pt as initProjectTemplate,mt as executeCommand,kt as deleteWorkspace,dt as zipWorkspace,wt as buildAgentPackage,ht as cleanupBuildArtifacts};
1
+ import i from"fs";import r from"path";import{exec as it}from"child_process";import st from"cross-spawn";import ot from"archiver";import U from"../../appConfig/index.js";import{extractZip as Z}from"../common/zipUtils.js";import{ValidationError as y,SystemError as v,FileError as G}from"../error/errorHandler.js";import{log as o}from"../log/logUtils.js";import{ensurePrimaryAgentDirs as Q,syncAgents as X}from"../common/AgentWorkspaceUtils.js";function I(e){if(!e)return[];if(Array.isArray(e))return e.map(t=>typeof t=="string"?t.trim():"").filter(Boolean);if(typeof e=="string"){const t=e.trim();if(!t)return[];try{const s=JSON.parse(t);if(Array.isArray(s))return s.map(c=>typeof c=="string"?c.trim():"").filter(Boolean)}catch{}return[t]}return[]}async function tt(e,t,s){let c;try{c=await fetch(e)}catch(g){throw new G(`Failed to download skill zip from url: ${e}`,{url:e,reason:g.message})}if(!c.ok)throw new G(`Failed to download skill zip from url: ${e}`,{url:e,status:c.status,statusText:c.statusText});const n=c.headers.get("content-type")||"";n&&!n.includes("zip")&&!n.includes("octet-stream")&&o(s,"WARN","Downloaded skill url content-type is not typical zip",{url:e,contentType:n});const a=await c.arrayBuffer(),h=Buffer.from(a);await i.promises.writeFile(t,h)}async function T(e="computer"){const t=U.COMPUTER_WORKSPACE_DIR;if(!t)throw new y("COMPUTER_WORKSPACE_DIR configuration does not exist",{field:"COMPUTER_WORKSPACE_DIR"});return i.existsSync(t)||(await i.promises.mkdir(t,{recursive:!0}),o(e,"INFO","Created user workspace root directory",{workspaceRoot:t})),t}async function q(e,t){const s=r.join(e,t);if(i.existsSync(s)&&(await i.promises.lstat(s)).isDirectory())return s;const c=await i.promises.readdir(e,{withFileTypes:!0});for(const n of c){if(!n.isDirectory())continue;const a=r.join(e,n.name),h=r.join(a,t);if(i.existsSync(h)&&(await i.promises.lstat(h)).isDirectory())return h}return null}async function Ot(e){return q(e,"skills")}const rt=".dynamic_add.lock";function at(e){const t=r.join(e,rt);return i.existsSync(t)&&i.statSync(t).isFile()}async function b(e){i.existsSync(e)&&await i.promises.rm(e,{recursive:!0,force:!0})}async function C(e,t){try{await i.promises.rename(e,t)}catch(s){if(s.code==="EXDEV"){async function c(n,a){if((await i.promises.lstat(n)).isDirectory()){await i.promises.mkdir(a,{recursive:!0});const g=await i.promises.readdir(n);for(const w of g)await c(r.join(n,w),r.join(a,w))}else await i.promises.copyFile(n,a)}await c(e,t),await i.promises.rm(e,{recursive:!0,force:!0})}else throw s}}async function nt(e,t,s,c,n,a){const h=t&&t.trim(),g=s&&s.trim(),w=c&&c.trim(),d=Array.isArray(n)&&n.length>0;if(!h&&!g&&!w&&!d)return;const u=r.join(e,".claude");try{if(i.existsSync(u)||await i.promises.mkdir(u,{recursive:!0}),h)try{const p={mcpServers:JSON.parse(t)},S=r.join(e,".mcp.json");await i.promises.writeFile(S,JSON.stringify(p,null,2),"utf-8"),o(a,"INFO","Written .mcp.json to workspace root")}catch(p){o(a,"WARN","Failed to parse/write mcpServersConfig, skipping",{error:p.message})}const f={};if(g)try{f.hooks=JSON.parse(s)}catch(p){o(a,"WARN","Failed to parse hooksConfig, skipping",{error:p.message})}if(w)try{f.permissions=JSON.parse(c)}catch(p){o(a,"WARN","Failed to parse permissionsConfig, skipping",{error:p.message})}if(Object.keys(f).length>0){const p=r.join(u,"settings.json");await i.promises.writeFile(p,JSON.stringify(f,null,2),"utf-8"),o(a,"INFO","Written .claude/settings.json",{keys:Object.keys(f)})}if(d){const p=r.join(u,"hooks");i.existsSync(p)||await i.promises.mkdir(p,{recursive:!0});for(const S of n){if(!S||!S.path||!S.content)continue;const l=r.normalize(S.path);if(l.startsWith("..")||r.isAbsolute(l)){o(a,"WARN","Hook script path contains traversal, skipping",{path:S.path});continue}const x=r.join(u,l),z=r.dirname(x);i.existsSync(z)||await i.promises.mkdir(z,{recursive:!0}),await i.promises.writeFile(x,S.content,"utf-8"),await i.promises.chmod(x,493),o(a,"INFO","Written hook script",{path:S.path})}}}catch(f){o(a,"WARN","Failed to write .claude/settings or .mcp.json or hook scripts, not blocking workspace creation",{error:f.message})}}async function ct(e,t,s,c,n,a,h,g){const w=Date.now(),d=`computer:${e}:${t}`,u=I(c),f=[],p=[],S=new Set;if(!e)throw new y("userId cannot be empty",{field:"userId"});if(!t)throw new y("cId cannot be empty",{field:"cId"});const l=await T(d),x=r.join(l,String(e),String(t),".tmp"),z=r.join(l,String(e),String(t)),{skillsDir:k,agentsDir:R,agentTypes:P}=await Q(z);i.existsSync(z)||await i.promises.mkdir(z,{recursive:!0});const D=r.join(x,`preserved_skills_${Date.now()}_${Math.round(Math.random()*1e6)}`);if(i.existsSync(k)){const N=(await i.promises.readdir(k,{withFileTypes:!0})).filter(A=>A.isDirectory()&&at(r.join(k,A.name)));if(N.length>0){await i.promises.mkdir(D,{recursive:!0});for(const A of N){const j=r.join(k,A.name),E=r.join(D,A.name);await C(j,E)}o(d,"INFO","\u4FDD\u7559\u542B .dynamic_add.lock \u7684 skill",{preserved:N.map(A=>A.name)})}}if(await b(k),await b(R),await i.promises.mkdir(k,{recursive:!0}),await i.promises.mkdir(R,{recursive:!0}),i.existsSync(D)){const m=await i.promises.readdir(D,{withFileTypes:!0});for(const N of m)if(N.isDirectory()){const A=r.join(D,N.name),j=r.join(k,N.name);await C(A,j)}await b(D)}const O=i.existsSync(k),$=i.existsSync(R);o(d,"INFO","Deleted old skills and agents directories completed",{userId:e,cId:t,targetSkillsDir:k,targetAgentsDir:R,agentTypes:P,skillsExists:O,agentsExists:$});const W=r.join(z,".mcp.json");i.existsSync(W)&&await i.promises.unlink(W);const B=r.join(z,".claude","settings.json");if(i.existsSync(B)&&await i.promises.unlink(B),await nt(z,n,h,a,g,d),!s&&u.length===0)return await X(z),o(d,"INFO","Created workspace (no uploaded file, no skills and agents)",{userId:e,cId:t,workspaceRoot:l,skillsDir:null,agentsDir:null,agentTypes:P,elapsedMs:Date.now()-w}),{message:"Workspace created (no uploaded file, no skills and agents)",workspaceRoot:l};if(s){if(!s.path)throw new y("Uploaded file has no valid path",{field:"file.path"});if(r.extname(s.originalname||s.filename||"").toLowerCase()!==".zip")throw new y("Only zip files are supported",{field:"file",originalName:s.originalname})}o(d,"DEBUG","Start processing workspace resources",{userId:e,cId:t,workspaceRoot:l,hasUploadedZip:!!s,skillUrlsCount:u.length});try{i.existsSync(x)||await i.promises.mkdir(x,{recursive:!0});const m=[];if(s){const j=r.join(x,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);p.push(j),await i.promises.mkdir(j,{recursive:!0}),o(d,"DEBUG","Start extracting uploaded zip file",{extractRoot:j}),await Z(s.path,j),o(d,"DEBUG","Uploaded zip file extracted successfully",{extractRoot:j});const E=await q(j,"skills"),M=await q(j,"agents");if(E){await i.promises.mkdir(k,{recursive:!0});const _=await i.promises.readdir(E,{withFileTypes:!0});for(const L of _){if(!L.isDirectory())continue;const K=r.join(E,L.name),H=r.join(k,L.name);i.existsSync(H)&&await b(H),await C(K,H),S.add(L.name)}m.push("skills"),o(d,"INFO","skills updated to workspace",{userId:e,cId:t,workspaceRoot:l,targetSkillsDir:k,agentTypes:P})}else o(d,"INFO","skills directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:j});M?(await b(R),await C(M,R),m.push("agents"),o(d,"INFO","agents updated to workspace",{userId:e,cId:t,workspaceRoot:l,targetAgentsDir:R,agentTypes:P})):o(d,"INFO","agents directory not found in uploaded zip, skipping",{userId:e,cId:t,extractRoot:j})}for(let j=0;j<u.length;j+=1){const E=u[j],M=r.join(x,`skill_url_${Date.now()}_${j}_${Math.round(Math.random()*1e6)}.zip`);f.push(M);const _=r.join(x,`skill_url_extract_${Date.now()}_${j}_${Math.round(Math.random()*1e6)}`);p.push(_),await i.promises.mkdir(_,{recursive:!0}),o(d,"INFO","Start download skill zip from url",{userId:e,cId:t,skillUrl:E}),await tt(E,M,d),o(d,"INFO","Skill zip downloaded, start extracting",{userId:e,cId:t,skillUrl:E,downloadedZipPath:M}),await Z(M,_);const K=(await i.promises.readdir(_,{withFileTypes:!0})).filter(F=>F.isDirectory()&&!F.name.startsWith(".")),Y=K.find(F=>F.name==="skills")?(await i.promises.readdir(r.join(_,"skills"),{withFileTypes:!0})).filter(F=>F.isDirectory()&&!F.name.startsWith(".")).map(F=>({name:F.name,sourcePath:r.join(_,"skills",F.name)})):K.map(F=>({name:F.name,sourcePath:r.join(_,F.name)}));if(Y.length===0){o(d,"WARN","No skill directory found after extracting skill url zip",{userId:e,cId:t,skillUrl:E,extractRoot:_});continue}await i.promises.mkdir(k,{recursive:!0});for(const F of Y){const J=r.join(k,F.name);i.existsSync(J)&&await b(J),await C(F.sourcePath,J),S.add(F.name),o(d,"INFO","Skill from url updated to workspace",{userId:e,cId:t,skillUrl:E,skillName:F.name,destSkillPath:J})}m.includes("skills")||m.push("skills")}m.length===0&&o(d,"WARN","No valid skills or agents directories found",{userId:e,cId:t,hasUploadedZip:!!s,skillUrlsCount:u.length});const N=Array.from(S),A=m.length>0?`Workspace created successfully, ${m.join(" and ")} updated`:"Workspace created successfully (skills and agents directories not found)";return o(d,"INFO",A,{userId:e,cId:t,updatedDirs:m,updatedSkills:N,agentTypes:P,elapsedMs:Date.now()-w}),await X(z),{message:A,workspaceRoot:l,updatedSkills:N}}catch(m){throw o(d,"ERROR","Failed to process workspace resources",{userId:e,cId:t,error:m.message,elapsedMs:Date.now()-w}),m instanceof y||m instanceof G||m instanceof v?m:new v(`Failed to create workspace: ${m.message}`,{userId:e,cId:t})}finally{try{for(const m of p)i.existsSync(m)&&await i.promises.rm(m,{recursive:!0,force:!0})}catch(m){o(d,"WARN","Failed to clean up temporary extracted zip",{error:m.message})}for(const m of f)try{i.existsSync(m)&&await i.promises.unlink(m)}catch(N){o(d,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:m,error:N.message})}try{s&&s.path&&i.existsSync(s.path)&&await i.promises.unlink(s.path)}catch(m){o(d,"WARN","Failed to clean up uploaded zip file",{tempZipPath:s?.path,error:m.message})}}}async function lt(e,t,s,c){const n=Date.now(),a=`computer:${e}:${t}`,h=I(c),g=[],w=[];if(!e)throw new y("userId cannot be empty",{field:"userId"});if(!t)throw new y("cId cannot be empty",{field:"cId"});if(!s&&h.length===0)throw new y("file or skillUrls cannot both be empty",{field:"file|skillUrls"});if(s){if(!s.path)throw new y("Uploaded file has no valid path",{field:"file.path"});if(r.extname(s.originalname||s.filename||"").toLowerCase()!==".zip")throw new y("Only zip files are supported",{field:"file",originalName:s?.originalname})}const d=await T(a),u=r.join(d,String(e),String(t),".tmp"),f=r.join(d,String(e),String(t)),{skillsDir:p,agentTypes:S}=await Q(f);try{i.existsSync(f)||await i.promises.mkdir(f,{recursive:!0}),i.existsSync(p)||await i.promises.mkdir(p,{recursive:!0}),i.existsSync(u)||await i.promises.mkdir(u,{recursive:!0});const l=[],x=new Set;if(s){const k=r.join(u,`skill_push_${Date.now()}_${Math.round(Math.random()*1e6)}`);w.push(k),await i.promises.mkdir(k,{recursive:!0}),o(a,"DEBUG","Start extracting skill zip file",{extractRoot:k}),await Z(s.path,k),o(a,"DEBUG","Skill zip file extracted successfully",{extractRoot:k});const R=await q(k,"skills");if(!R)o(a,"WARN","skills directory not found in uploaded zip",{userId:e,cId:t,extractRoot:k});else{const D=(await i.promises.readdir(R,{withFileTypes:!0})).filter(O=>O.isDirectory()&&!O.name.startsWith("."));if(D.length===0)o(a,"WARN","skills directory in uploaded zip has no skill subdirectories",{userId:e,cId:t,skillsDir:R});else for(const O of D){const $=r.join(R,O.name),W=r.join(p,O.name);i.existsSync(W)&&await b(W),await C($,W),x.add(O.name),o(a,"INFO","skill pushed to workspace from uploaded zip",{userId:e,cId:t,skillName:O.name,destSkillPath:W})}}}for(let k=0;k<h.length;k+=1){const R=h[k],P=r.join(u,`skill_push_url_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}.zip`);g.push(P);const D=r.join(u,`skill_push_url_extract_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}`);w.push(D),await i.promises.mkdir(D,{recursive:!0}),o(a,"INFO","Start download skill zip for push from url",{userId:e,cId:t,skillUrl:R}),await tt(R,P,a),await Z(P,D);const $=(await i.promises.readdir(D,{withFileTypes:!0})).filter(m=>m.isDirectory()&&!m.name.startsWith(".")),B=$.find(m=>m.name==="skills")?(await i.promises.readdir(r.join(D,"skills"),{withFileTypes:!0})).filter(m=>m.isDirectory()&&!m.name.startsWith(".")).map(m=>({name:m.name,sourcePath:r.join(D,"skills",m.name)})):$.map(m=>({name:m.name,sourcePath:r.join(D,m.name)}));if(B.length===0){o(a,"WARN","No skill directory found after extracting push skill url zip",{userId:e,cId:t,skillUrl:R,extractRoot:D});continue}for(const m of B){const N=r.join(p,m.name);i.existsSync(N)&&await b(N),await C(m.sourcePath,N),x.add(m.name),o(a,"INFO","skill pushed to workspace from url zip",{userId:e,cId:t,skillUrl:R,skillName:m.name,destSkillPath:N})}}for(const k of x)l.push(k);const z=l.length>0?`Pushed ${l.length} skills: ${l.join(", ")}`:"No valid skill directories found in file or skillUrls";return o(a,"INFO",z,{userId:e,cId:t,updatedSkills:l,agentTypes:S,elapsedMs:Date.now()-n}),await X(f),{message:z,workspaceRoot:d,updatedSkills:l}}catch(l){throw o(a,"ERROR","Failed to push skill to workspace",{userId:e,cId:t,error:l.message,elapsedMs:Date.now()-n}),l instanceof y||l instanceof G||l instanceof v?l:new v(`Failed to push skill to workspace: ${l.message}`,{userId:e,cId:t})}finally{try{for(const l of w)i.existsSync(l)&&await i.promises.rm(l,{recursive:!0,force:!0})}catch(l){o(a,"WARN","Failed to clean up temporary extracted zip",{error:l.message})}for(const l of g)try{i.existsSync(l)&&await i.promises.unlink(l)}catch(x){o(a,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:l,error:x.message})}try{s&&s.path&&i.existsSync(s.path)&&await i.promises.unlink(s.path)}catch(l){o(a,"WARN","Failed to clean up uploaded zip file",{tempZipPath:s?.path,error:l.message})}}}async function pt(e,t,s,c){const n=Date.now(),a=`computer:${e}:${t}`;if(!e)throw new y("userId cannot be empty",{field:"userId"});if(!t)throw new y("cId cannot be empty",{field:"cId"});if(!s||!s.path)throw new y("file is required",{field:"file"});if(r.extname(s.originalname||s.filename||"").toLowerCase()!==".zip")throw new y("Only zip files are supported",{field:"file",originalName:s.originalname});const g=await T(a),w=r.join(g,String(e),String(t)),d=r.join(w,".tmp"),u=r.join(d,`template_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{i.existsSync(w)||await i.promises.mkdir(w,{recursive:!0}),i.existsSync(d)||await i.promises.mkdir(d,{recursive:!0}),await i.promises.mkdir(u,{recursive:!0}),o(a,"DEBUG","Start extracting template zip file",{extractRoot:u}),await Z(s.path,u),o(a,"DEBUG","Template zip file extracted successfully",{extractRoot:u});const f=await i.promises.readdir(u,{withFileTypes:!0});for(const p of f){const S=r.join(u,p.name),l=r.join(w,p.name);if(p.isDirectory())await C(S,l);else{const x=r.dirname(l);i.existsSync(x)||await i.promises.mkdir(x,{recursive:!0}),await i.promises.copyFile(S,l)}}if(o(a,"INFO","Template files extracted to workspace",{userId:e,cId:t,targetDir:w,fileCount:f.length}),U.GIT_ENABLED&&(c==="true"||c===!0)){const p=await import("../../service/gitService.js");await p.default.init({workspaceType:"taskAgent",userId:e,cId:t}),await p.default.commit({workspaceType:"taskAgent",userId:e,cId:t,message:"Initial commit"}),o(a,"INFO","Git init and initial commit done",{userId:e,cId:t})}return o(a,"INFO","Init project template completed",{userId:e,cId:t,targetDir:w,elapsedMs:Date.now()-n}),{message:"Project template initialized successfully",workspaceRoot:w}}catch(f){throw o(a,"ERROR","Failed to init project template",{userId:e,cId:t,error:f.message,elapsedMs:Date.now()-n}),f instanceof y||f instanceof G||f instanceof v?f:new v(`Failed to init project template: ${f.message}`,{userId:e,cId:t})}finally{try{i.existsSync(u)&&await i.promises.rm(u,{recursive:!0,force:!0})}catch(f){o(a,"WARN","Failed to clean up temporary extracted zip",{error:f.message})}try{s&&s.path&&i.existsSync(s.path)&&await i.promises.unlink(s.path)}catch(f){o(a,"WARN","Failed to clean up uploaded zip file",{tempZipPath:s?.path,error:f.message})}}}async function mt(e,t,s){if(!e)throw new y("userId cannot be empty",{field:"userId"});if(!t)throw new y("cId cannot be empty",{field:"cId"});if(!s||typeof s!="string"||!s.trim())throw new y("command cannot be empty",{field:"command"});const c=await T("computer"),n=r.join(c,String(e),String(t));if(!i.existsSync(n))throw new y("workspace directory does not exist",{field:"workDir",workDir:n});const a=`computer:${e}:${t}`;o(a,"INFO","Execute command in workspace",{userId:e,cId:t,workDir:n,command:s});const h=10*60*1e3,g={cwd:n,timeout:h,maxBuffer:50*1024*1024};return U.BASH_PATH&&(g.shell=U.BASH_PATH),new Promise((w,d)=>{it(s,g,(u,f,p)=>{const S=u?u.killed?-1:u.code||1:0,l=u&&u.killed?(p||"")+`
2
+ Command timed out after ${h/1e3}s`:p||"";o(a,"INFO","Execute command completed",{userId:e,cId:t,exitCode:S,killed:u?u.killed:!1,stdoutLength:f?f.length:0,stderrLength:l.length}),w({stdout:f||"",stderr:l,exitCode:S})})})}async function dt(e,t,s){const c=`computer:${e}:${t}`;if(!e)throw new y("userId cannot be empty",{field:"userId"});if(!t)throw new y("cId cannot be empty",{field:"cId"});const n=await T(c),a=r.join(n,String(e),String(t));if(!i.existsSync(a))throw new y("workspace directory does not exist",{field:"workDir",workDir:a});const h=U.ZIP_WORKSPACE_EXCLUDE_DIRS||[],g=Array.isArray(s)?s:[],w=new Set([...h,...g]),d=`${e}_${t}.zip`,u=Date.now();o(c,"INFO","Zip workspace request",{userId:e,cId:t,targetDir:a,excludeDirs:[...w]});const f=ot("zip",{zlib:{level:9}});return f.directory(a,!1,p=>{const S=p.name||"";if(S.split(/[\/\\]/).filter(Boolean).some(x=>w.has(x)))return!1;try{const x=r.join(a,S);if(i.lstatSync(x).isSymbolicLink())return!1}catch{return!1}return p}),f.on("warning",p=>{p.code==="ENOENT"?o(c,"WARN","Encountered file problem when creating zip",{message:p.message,code:p.code}):o(c,"ERROR","Encountered warning when creating zip",{message:p.message,code:p.code})}),f.on("error",p=>{o(c,"ERROR","Failed to create zip",{message:p.message,elapsedMs:Date.now()-u})}),f.on("end",()=>{o(c,"INFO","Workspace zip created successfully",{targetDir:a,zipFileName:d,elapsedMs:Date.now()-u})}),{archive:f,zipFileName:d}}const ft=new Set([...U.ZIP_WORKSPACE_EXCLUDE_DIRS||[],"dist-packages"]);function V(e){let t;try{t=i.readdirSync(e,{withFileTypes:!0})}catch{return null}const s=r.join(e,"scripts","package-platforms.mjs");if(i.existsSync(s))return e;for(const c of t)if(c.isDirectory()&&!ft.has(c.name)){const n=V(r.join(e,c.name));if(n)return n}return null}function et(e,t,s,c=10*60*1e3){return new Promise(n=>{const a=st(e,t,{...s,timeout:c});let h="",g="";a.stdout?.on("data",w=>{h+=w.toString()}),a.stderr?.on("data",w=>{g+=w.toString()}),a.on("error",w=>{n({stdout:h,stderr:g+`
3
+ Failed to spawn ${e}: ${w.message}`,exitCode:1})}),a.on("close",(w,d)=>{n(w===null&&d?{stdout:h,stderr:g+`
4
+ Process killed by signal ${d} (timeout after ${c/1e3}s)`,exitCode:-1}:{stdout:h,stderr:g,exitCode:w??1})})})}function ut(e){if(!e)return null;let t=e;t.endsWith(".tar.gz")?t=t.slice(0,-7):t.endsWith(".tar.bz2")?t=t.slice(0,-8):(t.endsWith(".tgz")||t.endsWith(".zip"))&&(t=t.slice(0,-4));const s=t.split("-");if(s.length<4)return null;let c=-1;for(let n=s.length-1;n>=2;n--)if(/^\d+\.\d+\.\d+$/.test(s[n])){c=n;break}return c<3?null:s.slice(2,c).join("-")}async function wt(e,t,s,c){const n=`computer:${e}:${t}`;if(!e)throw new y("userId cannot be empty",{field:"userId"});if(!t)throw new y("cId cannot be empty",{field:"cId"});if(!s)throw new y("agentId cannot be empty",{field:"agentId"});if(!c)throw new y("version cannot be empty",{field:"version"});const a=await T(n),h=r.join(a,String(e),String(t));if(!i.existsSync(h))throw new y("workspace directory does not exist",{field:"workDir",workDir:h});o(n,"INFO","Build agent package request",{userId:e,cId:t,agentId:s,version:c});const g=V(h);if(!g)throw new y("package-platforms.mjs not found in workspace",{field:"script"});o(n,"INFO","Found package script",{projectDir:g});const w={...process.env,CI:"true"},d=await et("pnpm",["install"],{cwd:g,env:w});if(o(n,"INFO","pnpm install completed",{exitCode:d.exitCode,stderrLength:d.stderr.length}),d.exitCode!==0)throw o(n,"ERROR","pnpm install failed",{stderr:d.stderr}),new v(`pnpm install failed: ${d.stderr}`);const u=r.join(g,"dist-packages"),f=["scripts/package-platforms.mjs",`agent-${s}`,c,u,"--print-artifacts"],p=await et("node",f,{cwd:g,env:w});if(o(n,"INFO","Build completed",{exitCode:p.exitCode,stdoutLength:p.stdout.length}),p.exitCode!==0)throw o(n,"ERROR","Build failed",{stderr:p.stderr}),new v(`Build failed: ${p.stderr}`);const S=[".tar.gz",".tar.bz2",".zip",".tgz"],l=[],x=p.stdout.split(`
5
+ `);for(const z of x){const k=z.trim();if(!k)continue;const R=k.toLowerCase();if(!S.some(W=>R.endsWith(W)))continue;const P=r.resolve(g,k);let D=r.relative(h,P);D=D.split(r.sep).join("/");const O=r.basename(k),$=ut(O);l.push({path:D,fileName:O,platform:$||""})}return o(n,"INFO","Build agent package completed",{artifactsCount:l.length,artifacts:l}),{artifacts:l}}async function ht(e,t){const s=`computer:${e}:${t}`;if(!e)throw new y("userId cannot be empty",{field:"userId"});if(!t)throw new y("cId cannot be empty",{field:"cId"});const c=await T(s),n=r.join(c,String(e),String(t));if(!i.existsSync(n))return o(s,"WARN","Workspace not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const a=V(n);if(!a)return o(s,"WARN","package-platforms.mjs not found, skip cleanup",{userId:e,cId:t}),{cleaned:!1};const h=r.join(a,"dist-packages");return i.existsSync(h)?(i.rmSync(h,{recursive:!0,force:!0}),o(s,"INFO","Cleaned dist-packages",{distPackagesDir:h}),{cleaned:!0}):(o(s,"INFO","dist-packages not found, nothing to clean",{distPackagesDir:h}),{cleaned:!1})}async function kt(e,t){const s=`computer:${e}:${t}`;if(!e)throw new y("userId cannot be empty",{field:"userId"});if(!t)throw new y("cId cannot be empty",{field:"cId"});const c=await T(s),n=r.join(c,String(e),String(t));return i.existsSync(n)?(await i.promises.rm(n,{recursive:!0,force:!0}),o(s,"INFO","Workspace deleted",{userId:e,cId:t,targetDir:n})):o(s,"WARN","Workspace not found, skip delete",{userId:e,cId:t,targetDir:n}),{deleted:!0}}export{ct as createWorkspace,lt as pushSkillsToWorkspace,pt as initProjectTemplate,mt as executeCommand,kt as deleteWorkspace,dt as zipWorkspace,wt as buildAgentPackage,ht as cleanupBuildArtifacts};
@@ -1,6 +1,6 @@
1
- import f from"path";import o from"fs";import{simpleGit as c}from"simple-git";import r from"../../appConfig/index.js";function m(n){return c(n)}function l(n){return o.existsSync(f.join(n,".git"))}async function d(n){if(!l(n)){const i=c(n);await i.init(["-b","main"]),await i.addConfig("user.name",r.GIT_DEFAULT_AUTHOR_NAME,!1,"local"),await i.addConfig("user.email",r.GIT_DEFAULT_AUTHOR_EMAIL,!1,"local"),await i.addConfig("init.defaultBranch","main",!1,"local"),await i.commit("Initial commit",["--allow-empty"])}g(n)}export{m as getGitInstance,l as isGitRepo,d as ensureGitRepo,g as ensureGitignore,G as autoGitAdd};async function G(n,i){if(l(n))try{const t=m(n);Array.isArray(i)&&i.length>0?await t.add(i):await t.add("--all")}catch{}}function g(n){if(!r.GIT_AUTO_GITIGNORE)return;const i=f.join(n,".gitignore"),t=r.GIT_GITIGNORE_ENTRIES||[];let e=[];o.existsSync(i)&&(e=o.readFileSync(i,"utf8").split(/\r?\n/));const u=new Set(e.map(a=>a.trim()).filter(Boolean)),s=t.filter(a=>!u.has(a));if(s.length>0){const a=e.length>0&&e[e.length-1]!==""?`
2
- `+s.join(`
1
+ import l from"path";import n from"fs";import e from"isomorphic-git";import r from"../../appConfig/index.js";function u(i){return n.existsSync(l.join(i,".git"))}function d(){return{name:r.GIT_DEFAULT_AUTHOR_NAME,email:r.GIT_DEFAULT_AUTHOR_EMAIL}}async function c(i){const t=await e.statusMatrix({fs:n,dir:i});await Promise.all(t.map(([a,s,f])=>{if(f===2)return e.add({fs:n,dir:i,filepath:a}).catch(()=>{});if(s===1&&f===0)return e.remove({fs:n,dir:i,filepath:a}).catch(()=>{})}))}async function A(i){if(u(i))m(i);else{await e.init({fs:n,dir:i,defaultBranch:"main"}),await e.setConfig({fs:n,dir:i,path:"user.name",value:r.GIT_DEFAULT_AUTHOR_NAME}),await e.setConfig({fs:n,dir:i,path:"user.email",value:r.GIT_DEFAULT_AUTHOR_EMAIL}),m(i);const t=l.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(l.join(i,".gitkeep"),""),await e.add({fs:n,dir:i,filepath:".gitkeep"})),await e.commit({fs:n,dir:i,message:"Initial commit",author:d()})}}async function T(i,t){if(u(i))try{Array.isArray(t)&&t.length>0?await Promise.all(t.filter(a=>n.existsSync(l.join(i,a))).map(a=>e.add({fs:n,dir:i,filepath:a}))):await c(i)}catch{}}function m(i){if(!r.GIT_AUTO_GITIGNORE)return;const t=l.join(i,".gitignore"),a=r.GIT_GITIGNORE_ENTRIES||[];let s=[];n.existsSync(t)&&(s=n.readFileSync(t,"utf8").split(/\r?\n/));const f=new Set(s.map(o=>o.trim()).filter(Boolean)),g=a.filter(o=>!f.has(o));if(g.length>0){const o=s.length>0&&s[s.length-1]!==""?`
2
+ `+g.join(`
3
3
  `)+`
4
- `:s.join(`
4
+ `:g.join(`
5
5
  `)+`
6
- `;o.appendFileSync(i,a,"utf8")}}
6
+ `;n.appendFileSync(t,o,"utf8")}}export{u as isGitRepo,A as ensureGitRepo,m as ensureGitignore,T as autoGitAdd,c as addAll,d as getDefaultAuthor};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuwax-file-server",
3
3
  "displayName": "nuwax-file-server",
4
- "version": "1.3.0-beta.15",
4
+ "version": "1.3.0-beta.17",
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,7 +77,8 @@
77
77
  "multer": "^2.0.2",
78
78
  "node-cron": "^3.0.3",
79
79
  "pm2": "^6.0.11",
80
- "simple-git": "^3.36.0",
80
+ "isomorphic-git": "^1.30.1",
81
+ "diff": "^7.0.0",
81
82
  "swagger-jsdoc": "^6.2.8",
82
83
  "swagger-ui-express": "^5.0.0",
83
84
  "tree-kill": "^1.2.2",