nuwax-file-server 1.2.6 → 1.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/appConfig/index.js +1 -1
- package/dist/cli.js +1 -1
- package/dist/env.development +2 -1
- package/dist/env.production +3 -2
- package/dist/routes/codeRoutes.js +1 -1
- package/dist/routes/computerRoutes.js +1 -1
- package/dist/routes/projectRoutes.js +1 -1
- package/dist/service/codeService.js +2 -2
- package/dist/service/projectService.js +1 -1
- package/dist/utils/build/buildProjectUtils.js +4 -4
- package/dist/utils/build/processManager.js +9 -9
- package/dist/utils/build/restartDevUtils.js +1 -1
- package/dist/utils/build/startDevUtils.js +1 -1
- package/dist/utils/common/AgentWorkspaceUtils.js +1 -0
- package/dist/utils/common/projectPathUtils.js +1 -0
- package/dist/utils/computer/computerUtils.js +1 -1
- package/dist/utils/project/copyProjectUtils.js +1 -1
- package/dist/utils/project/frameworkDetectorUtils.js +1 -1
- package/dist/utils/project/getContentUtils.js +1 -1
- package/dist/utils/project/initProjectCleanupUtils.js +1 -1
- package/dist/utils/project/uploadAttachmentFileUtils.js +1 -1
- package/dist/utils/serviceManager.js +1 -3
- package/package.json +1 -1
package/dist/appConfig/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import E from"path";import n from"fs";import o from"os";import{fileURLToPath as L}from"url";import r from"dotenv";const T=E.dirname(L(import.meta.url));r.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 I=process.env.NODE_ENV||"development";function R(e){const _=E.join(T,"..",`env.${e}`);if(n.existsSync(_))r.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)}}R(I);const p={NODE_ENV:I,PORT:parseInt(process.env.PORT),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"};export default p;
|
package/dist/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as C}from"commander";import S from"path";import x from"os";import m from"fs-extra";import{spawn as D}from"cross-spawn";import{fileURLToPath as T}from"url";import{createRequire as N}from"module";var U=S.dirname(T(import.meta.url)),K=N(import.meta.url),R="1.2.6",a=new C,v={name:"nuwax-file-server",pidDir:S.join(x.tmpdir(),"nuwax-file-server"),logDir:S.join(x.tmpdir(),"nuwax-file-server","logs"),pidFile:S.join(x.tmpdir(),"nuwax-file-server","server.pid")},f={reset:"\x1B[0m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",cyan:"\x1B[36m"};function l(e,o="reset"){console.log(`${f[o]}${e}${f.reset}`)}function d(e){console.error(`${f.red}ERROR: ${e}${f.reset}`)}function r(e){console.log(`${f.green}${e}${f.reset}`)}function n(e){console.log(`${f.blue}${e}${f.reset}`)}function $(){return v.pidFile}function E(){try{let e=$();if(m.existsSync(e)){let o=m.readFileSync(e,"utf8");return JSON.parse(o)}}catch{}return null}function L(e){let o=S.dirname($());m.ensureDirSync(o),m.writeFileSync($(),JSON.stringify(e,null,2))}function I(){let e=$();m.existsSync(e)&&m.removeSync(e)}function h(e){try{return process.kill(e,0),!0}catch(o){return o.code==="ESRCH"||o.code==="EPERM",!1}}async function _(e,o=!1){return new Promise(t=>{if(process.platform==="win32"){let i=o?["/F","/PID",String(e)]:["/PID",String(e)],s=D("taskkill",i,{stdio:"pipe"});s.on("error",c=>{d(`Stop process failed: ${c.message}`),t(!1)}),s.on("close",c=>{c===0?(r(`Process ${e} stopped`),t(!0)):o?D("taskkill",["/F","/PID",String(e)],{stdio:"pipe"}).on("close",u=>{t(u===0)}):t(!1)})}else try{process.kill(-e,o?"SIGKILL":"SIGTERM"),r(`Process group ${e} stopped`),t(!0)}catch(i){if(i.code==="ESRCH")r(`Process ${e} does not exist, already stopped`),t(!0);else try{process.kill(e,o?"SIGKILL":"SIGTERM"),r(`Process ${e} stopped`),t(!0)}catch(s){s.code==="ESRCH"?(r(`Process ${e} does not exist, already stopped`),t(!0)):(d(`Stop process failed: ${s.message}`),t(!1))}}})}async function F(e){let{env:o,port:t,config:g}=e,i=new Set(["env","port","config","force","help","version"]);process.argv.slice(2).forEach(p=>{if(p.startsWith("--")&&p.includes("=")){let b=p.indexOf("="),P=p.slice(2,b);if(!i.has(P)){let O=p.slice(b+1);process.env[P]=O,n(`CLI parameter overrides environment variable: ${P}=${O}`)}}}),n(`Start ${v.name} service...`);let s=E();s&&h(s.pid)&&(d(`Service is already running (PID: ${s.pid})`),n("Please use 'nuwax-file-server stop' to stop the existing service before trying again"),process.exit(1));let c=o||"production";process.env.NODE_ENV=c,n(`Use environment: ${c}`),t&&(process.env.PORT=t,n(`Use port: ${t}`)),g&&(process.env.CONFIG_FILE=g,n(`Use configuration file: ${g}`)),m.ensureDirSync(v.logDir);let w=S.join(U,"server.js"),u=D("node",[w],{env:{...process.env,NODE_ENV:c},stdio:"inherit",detached:!0,cwd:process.cwd(),windowsHide:!0});u.on("error",p=>{d(`Start service failed: ${p.message}`),process.exit(1)}),await new Promise(p=>setTimeout(p,2e3)),h(u.pid)||(d("Start service failed"),process.exit(1));let y={pid:u.pid,startedAt:new Date().toISOString(),env:o||"production",port:t||process.env.PORT||"60000",version:R,platform:process.platform};L(y),u.unref(),r(`Service started (PID: ${u.pid})`),l(`Service running on: http://localhost:${y.port}`,"cyan"),l(`Environment: ${y.env}`,"cyan"),l(`Platform: ${y.platform}`,"cyan"),l(`PID file: ${$()}`,"cyan")}async function k(e){let{force:o}=e;n(`Stop ${v.name} service...`);let t=E();t||(d("Service not found"),n("Service may not be running or PID file has been lost"),process.exit(0)),h(t.pid)||(n("Service process has stopped, clean PID file..."),I(),r("Service has stopped"),process.exit(0)),await _(t.pid,o)&&(await new Promise(s=>setTimeout(s,1e3)),h(t.pid)||(I(),r("Service has stopped"),process.exit(0))),o&&(d("Force stop failed, please stop the process manually"),process.exit(1)),n("Try to force stop..."),await _(t.pid,!0)&&(await new Promise(s=>setTimeout(s,1e3)),h(t.pid)||(I(),r("Service has been forced stopped"),process.exit(0))),d("Stop service failed"),process.exit(1)}async function V(e){l(`Restart ${v.name} service...`,"yellow"),n("Stop existing service...");try{await k({force:!1})}catch{n("Service not running or has stopped")}await new Promise(o=>setTimeout(o,2e3)),n("Start service..."),await F(e),r("Service has been restarted")}function j(){n(`${v.name} service status:`);let e=E();e||(l("Service not running","yellow"),process.exit(0));let o=h(e.pid);if(console.log(""),console.log(` Service name: ${v.name}`),console.log(` Running status: ${o?"Running":"Stopped"}`),console.log(` Process ID: ${e.pid}`),console.log(` Environment: ${e.env||"production"}`),console.log(` Port: ${e.port||"60000"}`),console.log(` Version: ${e.version||R}`),console.log(` Platform: ${e.platform||process.platform}`),console.log(` Started at: ${e.startedAt||"Unknown"}`),e.startedAt){let t=new Date(e.startedAt),i=Math.floor((new Date-t)/1e3),s=Math.floor(i/3600),c=Math.floor(i%3600/60),w=i%60;console.log(` Uptime: ${s} hours ${c} minutes ${w} seconds`)}console.log(` PID file: ${$()}`),console.log(""),o?l("Service running normally","green"):(l("Warning: Service process does not exist, but PID file still exists","yellow"),n("Suggest executing stop command to clean up"))}function A(){a.name("nuwax-file-server").description("Cross-platform file service deployment tool, supporting start/stop/restart/status").version(R,"-v, --version","Display version number").helpOption("-h, --help","Display help information"),a.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").action(F),a.command("stop").description("Stop service").option("--force","Force stop").action(k),a.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").action(V),a.command("status").description("View service status").action(j),a.command("help").description("Display help information").action(()=>{a.outputHelp()}),a.parse(process.argv),process.argv.slice(2).length||(a.outputHelp(),process.exit(0))}A();
|
|
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.2.8",d=new ee,w={reset:"\x1B[0m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",cyan:"\x1B[36m"};function j(e){console.error(`${w.red}ERROR: ${e}${w.reset}`)}function b(e){console.log(`${w.green}${e}${w.reset}`)}function oe(e){console.log(`${w.blue}${e}${w.reset}`)}function y(e){return async(...t)=>{try{let o=await e(...t);o&&o.success===!1?(j(o.message||"Command failed"),process.exitCode=1):process.exitCode=0}catch(o){j(o?.message||"Command failed with unexpected error"),process.exitCode=1}}}async function se(e){let t=await N({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(`Service started (PID: ${t.pid})`),t}async function re(e){let t=await T({force:e.force,timeout:Number(e.timeout)});return t.success&&b(t.message||"Service stopped"),t}async function ne(e){let t=await O({env:e.env,port:e.port,config:e.config,timeout:Number(e.timeout),startTimeout:Number(e.startTimeout)});return t.success&&b(t.message||"Service restarted"),t}function ie(){let e=_();return oe(`${n.name} service status:`),console.log(""),console.log(` Service name: ${n.name}`),console.log(` Running status: ${e.running?"Running":"Stopped"}`),console.log(` Message: ${e.message}`),console.log(` PID file: ${$()}`),e.pidInfo&&(console.log(` Process ID: ${e.pidInfo.pid}`),console.log(` Environment: ${e.pidInfo.env||"Unknown"}`),console.log(` Port: ${e.pidInfo.port||"Unknown"}`),console.log(` Version: ${e.pidInfo.version||A}`),console.log(` Platform: ${e.pidInfo.platform||process.platform}`),console.log(` Started at: ${e.pidInfo.startedAt||"Unknown"}`),console.log(` Uptime: ${L(e.pidInfo.startedAt)}`)),console.log(""),{success:e.running,message:e.message}}function ce(){d.name("nuwax-file-server").description("Cross-platform file service deployment tool, supporting start/stop/restart/status").version(A,"-v, --version","Display version number").helpOption("-h, --help","Display help information"),d.command("start").allowUnknownOption().description("Start service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(se)),d.command("stop").description("Stop service").option("--force","Force stop").option("--timeout <ms>","\u505C\u6B62\u670D\u52A1\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).action(y(re)),d.command("restart").allowUnknownOption().description("Restart service").option("--env <environment>","\u73AF\u5883: development|production|test","production").option("--port <port>","Service port").option("--config <path>","Custom configuration file path").option("--timeout <ms>","\u505C\u6B62\u65E7\u8FDB\u7A0B\u7B49\u5F85\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStopTimeout}`).option("--start-timeout <ms>","\u542F\u52A8\u5065\u5EB7\u68C0\u67E5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",`${n.defaultStartTimeout}`).action(y(ne)),d.command("status").description("View service status").action(y(ie)),d.command("help").description("Display help information").action(()=>{d.outputHelp()}),d.parse(process.argv),process.argv.slice(2).length||(d.outputHelp(),process.exitCode=0)}ce();
|
package/dist/env.development
CHANGED
package/dist/env.production
CHANGED
|
@@ -10,7 +10,8 @@ LOG_PREFIX_API=api
|
|
|
10
10
|
LOG_PREFIX_BUILD=build
|
|
11
11
|
|
|
12
12
|
# 系统内置初始化工程
|
|
13
|
-
|
|
13
|
+
INIT_PROJECT_NAME_REACT=react-vite-template
|
|
14
|
+
INIT_PROJECT_NAME_VUE3=vue3-vite-template
|
|
14
15
|
INIT_PROJECT_DIR=/app/project_init
|
|
15
16
|
|
|
16
17
|
#上传的项目压缩包所在路径
|
|
@@ -48,7 +49,7 @@ TRAVERSE_EXCLUDE_DIRS=dist,node_modules,.pnpm-store,__MACOSX,.attachments
|
|
|
48
49
|
BACKUP_TRAVERSE_EXCLUDE_FILES=pnpm-lock.yaml,yarn.lock,package-lock.json
|
|
49
50
|
|
|
50
51
|
# 返回项目内容遍历排除文件
|
|
51
|
-
CONTENT_TRAVERSE_EXCLUDE_FILES=
|
|
52
|
+
CONTENT_TRAVERSE_EXCLUDE_FILES=pnpm-lock.yaml,yarn.lock,package-lock.json
|
|
52
53
|
|
|
53
54
|
# 允许内联返回内容的图片扩展名
|
|
54
55
|
INLINE_IMAGE_EXTENSIONS=.png,.jpg,.jpeg,.gif,.bmp,.svg,.ico,.webp,.avif
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import g from"express";import b from"multer";import{asyncHandler as m,ValidationError as a}from"../utils/error/errorHandler.js";import p from"../service/codeService.js";import{log as c}from"../utils/log/logUtils.js";import w from"../appConfig/index.js";import{extractIsolationContext as h}from"../utils/common/projectPathUtils.js";const y=g.Router(),I=b({storage:b.memoryStorage(),limits:{fileSize:w.UPLOAD_SINGLE_FILE_SIZE_BYTES}}),S=[{path:"/specified-files-update",method:"post",handler:m(async(e,s)=>{const{projectId:o,codeVersion:n,files:t}=e.body||{},l=h(e.body||{});c(o,"INFO","Partial files update",{projectId:o,codeVersion:n,filesCount:t?t.length:0}),t&&Array.isArray(t)&&t.forEach(r=>{if(r&&typeof r.contents=="string"&&r.contents)try{r.contents=decodeURIComponent(r.contents)}catch(f){c(o,"WARN","Decode file content failed",{fileName:r.path,error:f.message})}});const i=await p.specifiedFilesUpdate(String(o),String(n),t,e,l);s.status(200).json(i)})},{path:"/all-files-update",method:"post",handler:m(async(e,s)=>{const{projectId:o,codeVersion:n,files:t,basePath:l,pid:i}=e.body||{},r=h(e.body||{});if(c(o,"INFO","Submit files",{projectId:o,codeVersion:n,basePath:l,pid:i}),!o)throw new a("Project ID cannot be empty",{field:"projectId"});if(n==null)throw new a("codeVersion cannot be empty",{field:"codeVersion"});if(!Array.isArray(t))throw new a("files must be an array",{field:"files"});t&&Array.isArray(t)&&t.forEach(d=>{if(d&&typeof d.contents=="string"&&d.contents)try{d.contents=decodeURIComponent(d.contents)}catch(u){c(o,"WARN","Decode file content failed",{fileName:d.name,error:u.message})}});const f=await p.allFilesUpdate(String(o),String(n),t,e,r);s.status(200).json(f)})},{path:"/upload-single-file",method:"post",middleware:I.single("file"),handler:m(async(e,s)=>{const{projectId:o,codeVersion:n,filePath:t}=e.body||{},l=h(e.body||{}),i=e.file;if(c(o,"INFO","\u4E0A\u4F20\u5355\u4E2A\u6587\u4EF6",{projectId:o,codeVersion:n,filePath:t}),!o)throw new a("Project ID cannot be empty",{field:"projectId"});if(n==null)throw new a("codeVersion cannot be empty",{field:"codeVersion"});if(!i)throw new a("File cannot be empty",{field:"file"});if(!t||typeof t!="string")throw new a("File path cannot be empty",{field:"filePath"});c(o,"INFO","Received file information",{originalname:i.originalname,mimetype:i.mimetype,size:i.size,bufferLength:i.buffer?i.buffer.length:0,bufferIsBuffer:Buffer.isBuffer(i.buffer)});const r={buffer:i.buffer,originalname:i.originalname,mimetype:i.mimetype,size:i.size},f=await p.uploadSingleFile(String(o),String(n),r,t,e,l);s.status(200).json(f)})},{path:"/rollback-version",method:"post",handler:m(async(e,s)=>{const{projectId:o,codeVersion:n,rollbackTo:t}=e.body||{},l=h(e.body||{});if(c(o,"INFO","Rollback version",{projectId:o,codeVersion:n,rollbackTo:t}),!o)throw new a("Project ID cannot be empty",{field:"projectId"});if(n==null)throw new a("codeVersion cannot be empty",{field:"codeVersion"});if(t==null)throw new a("rollbackTo cannot be empty",{field:"rollbackTo"});const i=await p.rollbackVersion(String(o),String(n),String(t),e,l);s.status(200).json(i)})}];S.forEach(e=>{e.middleware?y[e.method](e.path,e.middleware,e.handler):y[e.method](e.path,e.handler)});export default y;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import S from"express";import I from"multer";import f from"fs";import g from"path";import{ValidationError as A,asyncHandler as m}from"../utils/error/errorHandler.js";import{log as d}from"../utils/log/logUtils.js";import w from"../appConfig/index.js";import{createWorkspace as F,pushSkillsToWorkspace as k}from"../utils/computer/computerUtils.js";import{getFileList as z,updateFiles as N,uploadFile as C,uploadFiles as b,downloadAllFiles as E}from"../utils/computer/computerFileUtils.js";const y=S.Router(),u=I({storage:I.diskStorage({destination:(e,o,a)=>{try{const t=w.COMPUTER_WORKSPACE_DIR;if(!t)return a(new Error("COMPUTER_WORKSPACE_DIR is not configured, cannot determine upload temporary directory"));const s=e.body?.userId||"unknown",i=e.body?.cId||"unknown",r=g.join(t,String(s),String(i),".tmp");f.existsSync(r)||f.mkdirSync(r,{recursive:!0}),a(null,r)}catch(t){a(t)}},filename:(e,o,a)=>{const t=g.extname(o.originalname)||".zip",s=g.basename(o.originalname,t),i=`${Date.now()}_${Math.round(Math.random()*1e6)}`;a(null,`${s}_${i}${t}`)}}),limits:{fileSize:w.UPLOAD_MAX_FILE_SIZE_BYTES}});function U(e,o,a,t){if(e&&e.name==="MulterError"||e&&(e.name==="ValidationError"||e instanceof A))return t(e);t(e)}const O=[{path:"/create-workspace",method:"post",middleware:u.single("file"),handler:m(async(e,o)=>{const{userId:a,cId:t}=e.body||{},s=e.file||null,i=`computer:${a}:${t}`;d(i,"INFO","Create workspace request",{userId:a,cId:t,hasFile:!!s,fileName:s?.originalname,fileSize:s?.size});const r=await F(a,t,s);o.status(200).json({success:!0,...r})})},{path:"/create-workspace-v2",method:"post",middleware:u.single("file"),handler:m(async(e,o)=>{const{userId:a,cId:t,skillUrls:s}=e.body||{},i=e.file||null,r=`computer:${a}:${t}`;let n=s;if(typeof s=="string")try{const c=JSON.parse(s);n=Array.isArray(c)?c:[s]}catch{n=[s]}d(r,"INFO","Create workspace v2 request",{userId:a,cId:t,hasFile:!!i,fileName:i?.originalname,fileSize:i?.size,skillUrlsCount:Array.isArray(n)?n.length:0});const l=await F(a,t,i,n);o.status(200).json({success:!0,...l})})},{path:"/push-skills-to-workspace",method:"post",middleware:u.single("file"),handler:m(async(e,o)=>{const{userId:a,cId:t}=e.body||{},s=e.file||null,i=`computer:${a}:${t}`;d(i,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42",{userId:a,cId:t,hasFile:!!s,fileName:s?.originalname,fileSize:s?.size});const r=await k(a,t,s);o.status(200).json({success:!0,...r})})},{path:"/push-skills-to-workspace-v2",method:"post",middleware:u.single("file"),handler:m(async(e,o)=>{const{userId:a,cId:t,skillUrls:s}=e.body||{},i=e.file||null,r=`computer:${a}:${t}`;let n=s;if(typeof s=="string")try{const c=JSON.parse(s);n=Array.isArray(c)?c:[s]}catch{n=[s]}d(r,"INFO","\u63A8\u9001\u6280\u80FD\u5230\u5DE5\u4F5C\u7A7A\u95F4\u8BF7\u6C42(v2)",{userId:a,cId:t,hasFile:!!i,fileName:i?.originalname,fileSize:i?.size,skillUrlsCount:Array.isArray(n)?n.length:0});const l=await k(a,t,i,n);o.status(200).json({success:!0,...l})})},{path:"/get-file-list",method:"get",handler:m(async(e,o)=>{const{userId:a,cId:t,proxyPath:s}=e.query,i=await z(a,t,s);o.status(200).json({success:!0,...i})})},{path:"/files-update",method:"post",handler:m(async(e,o)=>{const{userId:a,cId:t,files:s}=e.body||{},i=`computer:${a}:${t}`;d(i,"INFO","Files update",{userId:a,cId:t,filesCount:s?s.length:0}),s&&Array.isArray(s)&&s.forEach(n=>{if(n&&typeof n.contents=="string"&&n.contents)try{n.contents=decodeURIComponent(n.contents)}catch(l){d(i,"WARN","Decode file content failed",{fileName:n.name,error:l.message})}});const r=await N(a,t,s);o.status(200).json(r)})},{path:"/upload-file",method:"post",middleware:u.single("file"),handler:m(async(e,o)=>{const{userId:a,cId:t,filePath:s}=e.body||{},i=e.file,r=`computer:${a}:${t}`;d(r,"INFO","Upload single file",{userId:a,cId:t,filePath:s});const l={buffer:await f.promises.readFile(i.path),originalname:i.originalname,mimetype:i.mimetype,size:i.size};try{const c=await C(a,t,l,s);o.status(200).json(c)}finally{f.existsSync(i.path)&&await f.promises.unlink(i.path)}})},{path:"/upload-files",method:"post",middleware:u.array("files"),handler:m(async(e,o)=>{const{userId:a,cId:t,filePaths:s}=e.body||{},i=e.files||[],r=`computer:${a}:${t}`,n=Array.isArray(s)?s:typeof s=="string"?[s]:s;d(r,"INFO","Batch upload files request",{userId:a,cId:t,filesCount:i.length,filePathsCount:Array.isArray(n)?n.length:0});const l=[],c=[];try{for(const p of i){c.push(p.path);const $=await f.promises.readFile(p.path);l.push({buffer:$,originalname:p.originalname,mimetype:p.mimetype,size:p.size})}const h=await b(a,t,l,n);o.status(200).json(h)}finally{for(const h of c)if(f.existsSync(h))try{await f.promises.unlink(h)}catch(p){d(r,"WARN","Clean temporary file failed",{tempPath:h,error:p.message})}}})},{path:"/download-all-files",method:"get",handler:m(async(e,o)=>{const{userId:a,cId:t}=e.query||{},s=`computer:${a}:${t}`;d(s,"INFO","Download all files request",{userId:a,cId:t});const{archive:i,zipFileName:r}=await E(a,t);o.setHeader("Content-Type","application/zip");const n=encodeURIComponent(r);o.setHeader("Content-Disposition",`attachment; filename="${n}"; filename*=UTF-8''${n}`),i.on("error",l=>{o.destroy(l)}),i.pipe(o),i.finalize()})}];O.forEach(e=>{if(e.middleware)y[e.method](e.path,e.middleware,e.handler);else if(e.customHandler){const o=[];e.handler&&o.push(e.handler),e.decodeMiddleware&&o.push(e.decodeMiddleware),o.push(U),o.push(e.customHandler),y[e.method](e.path,...o)}else y[e.method](e.path,e.handler)});export default y;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import x from"express";import g from"multer";import u from"path";import d from"fs";import P from"iconv-lite";import m from"../service/projectService.js";import{getProjectContent as b,getProjectContentByVersion as E}from"../utils/project/getContentUtils.js";import{uploadAttachmentFile as D}from"../utils/project/uploadAttachmentFileUtils.js";import{copyProject as C}from"../utils/project/copyProjectUtils.js";import j from"../appConfig/index.js";import{log as h}from"../utils/log/logUtils.js";import{extractIsolationContext as l,resolveProjectPath as T}from"../utils/common/projectPathUtils.js";import{ValidationError as s,SystemError as R,asyncHandler as p}from"../utils/error/errorHandler.js";const w=x.Router(),O=g.diskStorage({destination:function(e,n,t){const o=u.join(j.UPLOAD_PROJECT_DIR,"temp");d.existsSync(o)||d.mkdirSync(o,{recursive:!0}),t(null,o)},filename:function(e,n,t){const o=Date.now()+"-"+Math.round(Math.random()*1e6);t(null,n.fieldname+"-"+o+u.extname(n.originalname))}}),I=g({storage:O,fileFilter:function(e,n,t){const o=u.extname(n.originalname).toLowerCase();j.UPLOAD_ALLOWED_EXTENSIONS.includes(o)?t(null,!0):t(new s("File type not allowed",{fileExtension:o,allowedExtensions:j.UPLOAD_ALLOWED_EXTENSIONS}),!1)},limits:{fileSize:j.UPLOAD_MAX_FILE_SIZE_BYTES}}),_=g.diskStorage({destination:function(e,n,t){const o=u.join(j.UPLOAD_PROJECT_DIR,"temp");d.existsSync(o)||d.mkdirSync(o,{recursive:!0}),t(null,o)},filename:function(e,n,t){const o=Date.now()+"-"+Math.round(Math.random()*1e6);t(null,n.fieldname+"-"+o+u.extname(n.originalname))}}),S=[".pdf",".doc",".docx",".xls",".xlsx",".ppt",".pptx",".txt",".md",".csv",".json",".xml",".png",".jpg",".jpeg",".gif",".bmp",".svg",".ico",".webp",".avif",".zip",".rar",".7z",".tar",".gz",".mp4",".avi",".mov",".wmv",".flv",".mp3",".wav",".ogg",".m4a"],v=g({storage:_,fileFilter:function(e,n,t){const o=u.extname(n.originalname).toLowerCase();S.includes(o)?t(null,!0):t(new s("\u9644\u4EF6\u6587\u4EF6\u7C7B\u578B\u4E0D\u88AB\u5141\u8BB8",{fileExtension:o,allowedExtensions:S}),!1)},limits:{fileSize:j.UPLOAD_MAX_FILE_SIZE_BYTES}});function z(e,n,t){if(e.file&&e.file.originalname)try{const o=e.file.originalname,a=P.decode(Buffer.from(e.file.originalname,"latin1"),"utf8");e.file.originalname=a,h("system","INFO","File name decoded successfully",{before:o,after:a})}catch(o){h("system","WARN","File name decoded failed",{originalName:e.file.originalname,error:o.message})}t()}const A=[{path:"/push-skills-to-workspace",method:"post",handler:I.single("file"),customHandler:p(async(e,n)=>{const{projectId:t,skillUrls:o}=e.body||{},a=e.file||null;if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});const i=l(e.body||{}),r=await m.pushSkillsToWorkspace(String(t),a,o,i);n.status(200).json({success:!0,...r})})},{path:"/create-project",method:"post",handler:p(async(e,n)=>{const{projectId:t,templateType:o}=e.body,a=l(e.body||{});if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});const i=await m.createProject(String(t),o,a);n.status(200).json(i)})},{path:"/upload-project",method:"post",handler:I.single("file"),customHandler:p(async(e,n)=>{const{projectId:t,codeVersion:o,pid:a,basePath:i}=e.body,r=l(e.body||{});if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});if(!o)throw new s("Code version cannot be empty",{field:"codeVersion"});if(!e.file)throw new s("Please upload a zip file",{field:"zipFile"});const c=await m.handleFileUpload(String(t),o,e.file);e.file.path=c.filePath;try{const f=await m.uploadProject(String(t),e.file.path,e,o,a,i,r);n.status(200).json(f)}catch(f){try{await m.cleanupProjectDirectory(String(t),r)}catch(y){h(t,"ERROR","Route layer cleanup project directory failed",{projectId:t,error:y.message})}if(e.file&&d.existsSync(e.file.path))try{d.unlinkSync(e.file.path)}catch(y){h(t,"ERROR","Clean upload file failed",{projectId:t,error:y.message})}throw f}})},{path:"/get-project-content",method:"get",handler:p(async(e,n)=>{const{projectId:t,command:o,proxyPath:a}=e.query,i=l(e.query||{});if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});const r=T(t,i);if(!d.existsSync(r))throw new s("Project does not exist",{field:"projectId"});try{const c=await b(r,o,a);n.status(200).json({success:!0,...c})}catch(c){const f=c?.message||"Query failed";n.status(500).json({success:!1,message:f})}})},{path:"/get-project-content-by-version",method:"get",handler:p(async(e,n)=>{const{projectId:t,codeVersion:o,command:a,proxyPath:i}=e.query,r=l(e.query||{});if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});if(!o)throw new s("Code version cannot be empty",{field:"codeVersion"});try{const c=await E(String(t),o,a,i,r);n.status(200).json({success:!0,...c})}catch(c){const f=c?.message||"Query failed";n.status(500).json({success:!1,message:f})}})},{path:"/backup-current-version",method:"post",handler:p(async(e,n)=>{const{projectId:t,codeVersion:o}=e.body,a=l(e.body||{}),i=await m.backupCurrentVersion(String(t),o,a);n.status(200).json({success:!0,...i})})},{path:"/export-project",method:"post",handler:p(async(e,n)=>{const{projectId:t,codeVersion:o,exportType:a,config:i}=e.body,r=l(e.body||{}),c=await m.exportProject(String(t),o,a,i,r);if(!d.existsSync(c.zipPath))throw new R("Exported zip file does not exist",{zipPath:c.zipPath});const f=u.basename(c.zipPath);n.setHeader("Content-Type","application/zip"),n.setHeader("Content-Disposition",`attachment; filename="${f}"`),n.sendFile(c.zipPath,y=>{y?(h(t,"ERROR","Send zip file failed",{projectId:t,error:y.message}),n.headersSent||n.status(500).json({success:!1,message:"File send failed"})):h(t,"INFO","Zip file send successfully",{projectId:t,zipPath:c.zipPath})})})},{path:"/delete-project",method:"get",handler:p(async(e,n)=>{const{projectId:t,pid:o}=e.query,a=l(e.query||{});if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});const i=await m.deleteProject(String(t),o,e,a);n.status(200).json(i)})},{path:"/upload-attachment-file",method:"post",handler:v.single("file"),decodeMiddleware:z,customHandler:p(async(e,n)=>{const{projectId:t,fileName:o}=e.body,a=l(e.body||{});if(!t)throw new s("Project ID cannot be empty",{field:"projectId"});if(!e.file)throw new s("Please upload a file",{field:"file"});try{const i=await D(String(t),e.file,o,a);n.status(200).json({success:!0,...i})}catch(i){if(e.file&&d.existsSync(e.file.path))try{d.unlinkSync(e.file.path)}catch(r){h(t,"ERROR","Clean upload file failed",{projectId:t,error:r.message})}throw i}})},{path:"/copy-project",method:"post",handler:p(async(e,n)=>{const{sourceProjectId:t,targetProjectId:o}=e.body,a=l({tenantId:e.body?.sourceTenantId||e.body?.tenantId,spaceId:e.body?.sourceSpaceId||e.body?.spaceId,isolationType:e.body?.sourceIsolationType||e.body?.isolationType}),i=l({tenantId:e.body?.targetTenantId||e.body?.tenantId,spaceId:e.body?.targetSpaceId||e.body?.spaceId,isolationType:e.body?.targetIsolationType||e.body?.isolationType});if(!t)throw new s("Source project ID cannot be empty",{field:"sourceProjectId"});if(!o)throw new s("Target project ID cannot be empty",{field:"targetProjectId"});const r=await C(String(t),String(o),{sourceIsolationContext:a,targetIsolationContext:i});n.status(200).json(r)})}];function L(e,n,t,o){if(e instanceof g.MulterError||e.name==="ValidationError"||e instanceof s)return o(e);o(e)}A.forEach(e=>{if(e.customHandler){const n=[];e.handler&&n.push(e.handler),e.decodeMiddleware&&n.push(e.decodeMiddleware),n.push(L),n.push(e.customHandler),w[e.method](e.path,...n)}else w[e.method](e.path,e.handler)});export default w;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import l from"path";import a from"fs";import R from"../appConfig/index.js";import{restartDevServer as B}from"../utils/build/restartDevUtils.js";import{log as i}from"../utils/log/logUtils.js";import{ValidationError as m,SystemError as S,ResourceError as O}from"../utils/error/errorHandler.js";import"../utils/common/sensitiveUtils.js";import{backupProjectToZip as D,restoreProjectFromZip as z,pruneMissingFiles as V,removeEmptyDirectories as C}from"../utils/project/backupUtils.js";import"../utils/common/zipUtils.js";import"../utils/buildJudge/restartJudgeUtils.js";import{resolveProjectPath as x}from"../utils/common/projectPathUtils.js";function L(e,F){const n=e.split(/\r?\n/),d=F.split(/\r?\n/),P=n.length,w=d.length,b=Math.min(P,w);let r=0;for(let o=0;o<b;o++)n[o]!==d[o]&&(n[o]=d[o],r++);if(P>w)for(let o=P-1;o>=w;o--)n.splice(o,1),r++;if(w>P)for(let o=P;o<w;o++)n.push(d[o]),r++;const u=e.includes(`\r
|
|
2
2
|
`)?`\r
|
|
3
3
|
`:`
|
|
4
|
-
`;return{finalContent:n.join(s),changesCount:f}}function I(e,P){return e===P?{finalContent:e,changesCount:0}:{finalContent:P,changesCount:-1}}async function C(e,P,n,F){const b=Date.now();if(!e)throw new u("Project ID cannot be empty",{field:"projectId"});if(P==null)throw new u("codeVersion cannot be empty",{field:"codeVersion"});const m=Number(P);if(!Number.isFinite(m))throw new u("codeVersion must be a number",{field:"codeVersion"});if(!Array.isArray(n))throw new u("files must be an array",{field:"files"});for(let s=0;s<n.length;s++){const h=n[s];if(!h||typeof h.operation!="string")throw new u(`files[${s}].operation cannot be empty`,{field:`files[${s}].operation`});if(!h.name||typeof h.name!="string")throw new u(`files[${s}].name cannot be empty`,{field:`files[${s}].name`});const t=h.operation.toLowerCase();if(!["create","delete","rename","modify"].includes(t))throw new u(`files[${s}].operation must be one of create, delete, rename or modify`,{field:`files[${s}].operation`});if(t==="rename"&&!h.renameFrom)throw new u(`files[${s}].renameFrom cannot be empty (rename operation requires)`,{field:`files[${s}].renameFrom`});if(t==="modify"&&typeof h.contents!="string")throw new u(`files[${s}].contents must be a string (modify operation requires)`,{field:`files[${s}].contents`})}const o=r.join(d.PROJECT_SOURCE_DIR,e);if(!a.existsSync(o))throw i(e,"ERROR","Project does not exist",{projectId:e,projectPath:o}),new O("Project does not exist",{projectId:e});let f="";try{const s=r.join(d.UPLOAD_PROJECT_DIR,e);a.existsSync(s)||a.mkdirSync(s,{recursive:!0});const h=`${e}-v${m}.zip`;f=r.join(s,h),i(e,"DEBUG","Start backing up project",{projectId:e,backupZipPath:f}),await D(e,o,f),i(e,"INFO","Project backed up successfully",{projectId:e,zipPath:f});try{i(e,"DEBUG","Start processing file operations",{projectId:e,filesCount:n.length});for(const t of n){const l=t.operation.toLowerCase(),w=t.name,c=r.normalize(w).replace(/^[\/\\]+/,""),y=r.join(o,c),g=r.resolve(y),R=r.resolve(o);if(!g.startsWith(R+r.sep)&&g!==R){i(e,"WARN","Unsafe file path, skipping",{filePath:c,resolvedPath:g});continue}switch(l){case"create":{await a.promises.mkdir(r.dirname(y),{recursive:!0});const p=t.contents||"";await a.promises.writeFile(y,p,"utf8"),i(e,"INFO","File created successfully",{filePath:c});break}case"delete":{a.existsSync(y)?(await a.promises.unlink(y),i(e,"INFO","File deleted successfully",{filePath:c})):i(e,"WARN","File to delete does not exist",{filePath:c});break}case"rename":{const p=t.renameFrom;if(!p||typeof p!="string"){i(e,"WARN","Rename operation missing renameFrom",{filePath:c});break}const k=r.normalize(p).replace(/^[\/\\]+/,""),v=r.join(o,k),E=r.resolve(v);if(!E.startsWith(R+r.sep)&&E!==R){i(e,"WARN","Unsafe rename source path, skipping",{renameFrom:k,resolvedPath:E});break}a.existsSync(v)?(await a.promises.mkdir(r.dirname(y),{recursive:!0}),await a.promises.rename(v,y),i(e,"INFO","File renamed successfully",{oldPath:k,newPath:c})):i(e,"WARN","File to rename does not exist",{renameFrom:k});break}case"modify":{if(!a.existsSync(y)){i(e,"WARN","File to modify does not exist",{filePath:c});break}const p=await a.promises.readFile(y,"utf8"),k=typeof t.contents=="string"?t.contents:"",{finalContent:v,changesCount:E}=A(p,k);if(E===0){i(e,"INFO","File content unchanged, skipping write",{filePath:c});break}await a.promises.writeFile(y,v,"utf8"),i(e,"INFO","File modified successfully",{filePath:c,changesCount:E});break}default:{i(e,"WARN","Unsupported operation type",{operation:l,filePath:c});break}}}i(e,"DEBUG","Start cleaning empty directories",{projectId:e});try{await x(o,d.TRAVERSE_EXCLUDE_DIRS||[])}catch(t){i(e,"WARN","Failed to clean empty directories",{projectId:e,error:t&&t.message})}return i(e,"INFO","Specified files updated successfully",{projectId:e,filesCount:n.length,elapsedMs:Date.now()-b}),{success:!0,message:"Specified files updated successfully",projectId:e,filesCount:n.length}}catch(t){throw i(e,"ERROR","Failed to process file operations",{projectId:e,error:t&&t.message,elapsedMs:Date.now()-b}),t}}catch(s){throw s.isOperational?s:new S("Failed to backup project",{projectId:e,originalError:s&&s.message})}}async function T(e,P,n,F){const b=Date.now();if(!e)throw new u("Project ID cannot be empty",{field:"projectId"});const m=Number(P);if(!Number.isFinite(m))throw new u("codeVersion must be a number",{field:"codeVersion"});if(!Array.isArray(n))throw new u("files must be an array",{field:"files"});const o=r.join(d.PROJECT_SOURCE_DIR,e);if(!a.existsSync(o))throw i(e,"ERROR","Project does not exist",{projectId:e,projectPath:o}),new O("Project does not exist",{projectId:e});let f="";try{const s=r.join(d.UPLOAD_PROJECT_DIR,e);a.existsSync(s)||a.mkdirSync(s,{recursive:!0});const h=`${e}-v${m}.zip`;f=r.join(s,h),i(e,"DEBUG","Start backing up project",{projectId:e,backupZipPath:f}),await D(e,o,f);try{i(e,"DEBUG","Start writing files",{projectId:e,filesCount:n.length});for(const t of n){if(!t||typeof t.name!="string")continue;const l=r.join(o,t.name);if(t.renameFrom&&typeof t.renameFrom=="string"){const p=r.join(o,t.renameFrom);if(a.existsSync(p)){await a.promises.mkdir(r.dirname(l),{recursive:!0}),await a.promises.rename(p,l),i(e,"INFO","File renamed successfully",{projectId:e,oldPath:t.renameFrom,newPath:t.name});continue}}const w=t.binary===!0,c=t.binary===!1,y=!!t.sizeExceeded,g=typeof t.contents=="string"&&t.contents.length>0;if(w){if(a.existsSync(l)){i(e,"INFO","Binary file already exists, skipping write",{filePath:t.name});continue}if(g)try{await a.promises.mkdir(r.dirname(l),{recursive:!0});const p=Buffer.from(t.contents,"base64");await a.promises.writeFile(l,p),i(e,"INFO","Binary file written successfully",{filePath:t.name})}catch(p){i(e,"ERROR","Failed to write binary file",{filePath:t.name,error:p&&p.message})}else i(e,"WARN","Binary file does not exist and has no content, skipping",{filePath:t.name});continue}c&&(!y||y&&g)&&(await a.promises.mkdir(r.dirname(l),{recursive:!0}),await a.promises.writeFile(l,t.contents||"","utf8"))}}catch(t){throw i(e,"ERROR","Failed to write files",{projectId:e,error:t&&t.message,elapsedMs:Date.now()-b}),t}try{i(e,"DEBUG","Start cleaning missing files and empty directories",{projectId:e});const t=new Set(n.filter(l=>l&&typeof l.name=="string").map(l=>r.normalize(l.name)));await $(o,t,d.TRAVERSE_EXCLUDE_DIRS||[]),await x(o,d.TRAVERSE_EXCLUDE_DIRS||[])}catch(t){throw i(e,"ERROR","Failed to clean missing files, starting rollback",{projectId:e,error:t&&t.message,elapsedMs:Date.now()-b}),t}return i(e,"INFO","Files submitted successfully",{projectId:e,filesCount:n.length,elapsedMs:Date.now()-b}),{success:!0,message:"Files submitted successfully",projectId:e,restarted:!1}}catch(s){throw s.isOperational?s:new S("Failed to backup old version",{projectId:e,originalError:s&&s.message})}}async function z(e,P,n,F,b){const m=Date.now();if(!e)throw new u("Project ID cannot be empty",{field:"projectId"});const o=Number(P);if(!Number.isFinite(o))throw new u("codeVersion must be a number",{field:"codeVersion"});if(!n)throw new u("File cannot be empty",{field:"file"});if(!F||typeof F!="string")throw new u("File path cannot be empty",{field:"filePath"});const f=r.join(d.PROJECT_SOURCE_DIR,e);if(!a.existsSync(f))throw i(e,"ERROR","Project does not exist",{projectId:e,projectPath:f}),new O("Project does not exist",{projectId:e});const s=r.normalize(F).replace(/^[\/\\]+/,""),h=r.join(f,s),t=r.resolve(h),l=r.resolve(f);if(!t.startsWith(l))throw new u("File path is not safe, cannot exceed project directory",{field:"filePath",providedPath:F,resolvedPath:t});let w="";try{const c=r.join(d.UPLOAD_PROJECT_DIR,e);a.existsSync(c)||a.mkdirSync(c,{recursive:!0});const y=`${e}-v${o}.zip`;w=r.join(c,y),i(e,"DEBUG","Start backing up project",{projectId:e,backupZipPath:w}),await D(e,f,w),i(e,"INFO",`Project backed up: ${w}`,{projectId:e,zipPath:w});try{if(i(e,"DEBUG","Start writing uploaded file",{projectId:e,filePath:s}),await a.promises.mkdir(r.dirname(h),{recursive:!0}),!n.buffer)throw new u("File content format is incorrect, missing buffer",{field:"file"});if(i(e,"INFO","Prepare to write file",{targetPath:h,bufferLength:n.buffer.length,expectedSize:n.size,bufferIsBuffer:Buffer.isBuffer(n.buffer),sizeMatch:n.buffer.length===n.size}),await a.promises.writeFile(h,n.buffer),i(e,"INFO","File uploaded successfully",{projectId:e,filePath:s,targetPath:t,fileSize:n.buffer?n.buffer.length:0,elapsedMs:Date.now()-m}),!1)try{const R=await _(b,e);return i(e,"INFO","Restart development server successfully",{projectId:e,pid:R.pid,port:R.port}),{success:!0,message:"File uploaded and restarted development server successfully",projectId:e,filePath:s,targetPath:t,fileSize:n.buffer?n.buffer.length:0,pid:R.pid,port:R.port,restarted:!0}}catch(R){i(e,"ERROR","Failed to restart development server",{projectId:e,filePath:s,error:R&&R.message})}else return i(e,"INFO","File modification does not require restarting development server",{projectId:e,filePath:s}),{success:!0,message:"File uploaded successfully, no need to restart development server",projectId:e,restarted:!1}}catch(g){throw i(e,"ERROR","Failed to write file",{projectId:e,filePath:s,error:g&&g.message,elapsedMs:Date.now()-m}),g}}catch(c){throw c.isOperational?c:new S("Failed to backup project",{projectId:e,filePath:s,originalError:c&&c.message})}}async function U(e,P,n,F){const b=Date.now();if(!e)throw new u("Project ID cannot be empty",{field:"projectId"});const m=Number(P);if(!Number.isFinite(m))throw new u("codeVersion must be a number",{field:"codeVersion"});const o=Number(n);if(!Number.isFinite(o))throw new u("rollbackTo must be a number",{field:"rollbackTo"});if(o<0)throw new u("rollbackTo cannot be less than 0",{field:"rollbackTo"});if(o>=m)throw new u("rollbackTo must be less than current codeVersion",{field:"rollbackTo"});const f=r.join(d.PROJECT_SOURCE_DIR,e);if(!a.existsSync(f))throw i(e,"ERROR","Project does not exist",{projectId:e,projectPath:f}),new O("Project does not exist",{projectId:e});const s=r.join(d.UPLOAD_PROJECT_DIR,e),h=`${e}-v${o}.zip`,t=r.join(s,h);if(!a.existsSync(t))throw i(e,"ERROR","Rollback version backup file does not exist",{projectId:e,rollbackTo:o,zipPath:t}),new O("Rollback version backup file does not exist",{projectId:e,rollbackTo:o});let l="";try{a.existsSync(s)||a.mkdirSync(s,{recursive:!0});const w=`${e}-v${m}.zip`;return l=r.join(s,w),a.existsSync(l)?i(e,"INFO","Current version backup already exists, skipping backup",{projectId:e,zipPath:l}):(await D(e,f,l),i(e,"INFO","Current version backed up",{projectId:e,zipPath:l})),i(e,"DEBUG","Start restoring project from backup",{projectId:e,rollbackToNum:o,rollbackZipPath:t}),await N(e,f,t),i(e,"INFO","Project rolled back successfully",{projectId:e,newVersion:m,toVersion:o,rollbackZipPath:t,elapsedMs:Date.now()-b}),{success:!0,message:"Project rolled back successfully",newVersion:m,rollbackTo:o}}catch(w){if(i(e,"ERROR","Failed to rollback project",{projectId:e,rollbackTo:o,error:w&&w.message,elapsedMs:Date.now()-b}),l&&a.existsSync(l))try{i(e,"INFO","Failed to rollback, trying to restore current version",{projectId:e,backupPath:l}),await N(e,f,l),i(e,"INFO","Current version restored",{projectId:e})}catch(c){i(e,"ERROR","Failed to restore current version",{projectId:e,error:c&&c.message})}throw w.isOperational?w:new S("Failed to rollback project",{projectId:e,rollbackTo:o,originalError:w&&w.message})}}export{C as specifiedFilesUpdate,T as allFilesUpdate,z as uploadSingleFile,U as rollbackVersion};export default{specifiedFilesUpdate:C,allFilesUpdate:T,uploadSingleFile:z,rollbackVersion:U};
|
|
4
|
+
`;return{finalContent:n.join(u),changesCount:r}}function ie(e,F){return e===F?{finalContent:e,changesCount:0}:{finalContent:F,changesCount:-1}}async function T(e,F,n,d,P={}){const w=Date.now();if(!e)throw new m("Project ID cannot be empty",{field:"projectId"});if(F==null)throw new m("codeVersion cannot be empty",{field:"codeVersion"});const b=Number(F);if(!Number.isFinite(b))throw new m("codeVersion must be a number",{field:"codeVersion"});if(!Array.isArray(n))throw new m("files must be an array",{field:"files"});for(let s=0;s<n.length;s++){const o=n[s];if(!o||typeof o.operation!="string")throw new m(`files[${s}].operation cannot be empty`,{field:`files[${s}].operation`});if(!o.name||typeof o.name!="string")throw new m(`files[${s}].name cannot be empty`,{field:`files[${s}].name`});const t=o.operation.toLowerCase();if(!["create","delete","rename","modify"].includes(t))throw new m(`files[${s}].operation must be one of create, delete, rename or modify`,{field:`files[${s}].operation`});if(t==="rename"&&!o.renameFrom)throw new m(`files[${s}].renameFrom cannot be empty (rename operation requires)`,{field:`files[${s}].renameFrom`});if(t==="modify"&&typeof o.contents!="string")throw new m(`files[${s}].contents must be a string (modify operation requires)`,{field:`files[${s}].contents`})}const r=x(e,P);if(!a.existsSync(r))throw i(e,"ERROR","Project does not exist",{projectId:e,projectPath:r}),new O("Project does not exist",{projectId:e});let u="";try{const s=l.join(R.UPLOAD_PROJECT_DIR,e);a.existsSync(s)||a.mkdirSync(s,{recursive:!0});const o=`${e}-v${b}.zip`;u=l.join(s,o),i(e,"DEBUG","Start backing up project",{projectId:e,backupZipPath:u}),await D(e,r,u),i(e,"INFO","Project backed up successfully",{projectId:e,zipPath:u});try{i(e,"DEBUG","Start processing file operations",{projectId:e,filesCount:n.length});for(const t of n){const f=t.operation.toLowerCase(),h=t.name,c=l.normalize(h).replace(/^[\/\\]+/,""),y=l.join(r,c),k=l.resolve(y),g=l.resolve(r);if(!k.startsWith(g+l.sep)&&k!==g){i(e,"WARN","Unsafe file path, skipping",{filePath:c,resolvedPath:k});continue}switch(f){case"create":{await a.promises.mkdir(l.dirname(y),{recursive:!0});const p=t.contents||"";await a.promises.writeFile(y,p,"utf8"),i(e,"INFO","File created successfully",{filePath:c});break}case"delete":{a.existsSync(y)?(await a.promises.unlink(y),i(e,"INFO","File deleted successfully",{filePath:c})):i(e,"WARN","File to delete does not exist",{filePath:c});break}case"rename":{const p=t.renameFrom;if(!p||typeof p!="string"){i(e,"WARN","Rename operation missing renameFrom",{filePath:c});break}const v=l.normalize(p).replace(/^[\/\\]+/,""),N=l.join(r,v),E=l.resolve(N);if(!E.startsWith(g+l.sep)&&E!==g){i(e,"WARN","Unsafe rename source path, skipping",{renameFrom:v,resolvedPath:E});break}a.existsSync(N)?(await a.promises.mkdir(l.dirname(y),{recursive:!0}),await a.promises.rename(N,y),i(e,"INFO","File renamed successfully",{oldPath:v,newPath:c})):i(e,"WARN","File to rename does not exist",{renameFrom:v});break}case"modify":{if(!a.existsSync(y)){i(e,"WARN","File to modify does not exist",{filePath:c});break}const p=await a.promises.readFile(y,"utf8"),v=typeof t.contents=="string"?t.contents:"",{finalContent:N,changesCount:E}=L(p,v);if(E===0){i(e,"INFO","File content unchanged, skipping write",{filePath:c});break}await a.promises.writeFile(y,N,"utf8"),i(e,"INFO","File modified successfully",{filePath:c,changesCount:E});break}default:{i(e,"WARN","Unsupported operation type",{operation:f,filePath:c});break}}}i(e,"DEBUG","Start cleaning empty directories",{projectId:e});try{await C(r,R.TRAVERSE_EXCLUDE_DIRS||[])}catch(t){i(e,"WARN","Failed to clean empty directories",{projectId:e,error:t&&t.message})}return i(e,"INFO","Specified files updated successfully",{projectId:e,filesCount:n.length,elapsedMs:Date.now()-w}),{success:!0,message:"Specified files updated successfully",projectId:e,filesCount:n.length}}catch(t){throw i(e,"ERROR","Failed to process file operations",{projectId:e,error:t&&t.message,elapsedMs:Date.now()-w}),t}}catch(s){throw s.isOperational?s:new S("Failed to backup project",{projectId:e,originalError:s&&s.message})}}async function U(e,F,n,d,P={}){const w=Date.now();if(!e)throw new m("Project ID cannot be empty",{field:"projectId"});const b=Number(F);if(!Number.isFinite(b))throw new m("codeVersion must be a number",{field:"codeVersion"});if(!Array.isArray(n))throw new m("files must be an array",{field:"files"});const r=x(e,P);if(!a.existsSync(r))throw i(e,"ERROR","Project does not exist",{projectId:e,projectPath:r}),new O("Project does not exist",{projectId:e});let u="";try{const s=l.join(R.UPLOAD_PROJECT_DIR,e);a.existsSync(s)||a.mkdirSync(s,{recursive:!0});const o=`${e}-v${b}.zip`;u=l.join(s,o),i(e,"DEBUG","Start backing up project",{projectId:e,backupZipPath:u}),await D(e,r,u);try{i(e,"DEBUG","Start writing files",{projectId:e,filesCount:n.length});for(const t of n){if(!t||typeof t.name!="string")continue;const f=l.join(r,t.name);if(t.renameFrom&&typeof t.renameFrom=="string"){const p=l.join(r,t.renameFrom);if(a.existsSync(p)){await a.promises.mkdir(l.dirname(f),{recursive:!0}),await a.promises.rename(p,f),i(e,"INFO","File renamed successfully",{projectId:e,oldPath:t.renameFrom,newPath:t.name});continue}}const h=t.binary===!0,c=t.binary===!1,y=!!t.sizeExceeded,k=typeof t.contents=="string"&&t.contents.length>0;if(h){if(a.existsSync(f)){i(e,"INFO","Binary file already exists, skipping write",{filePath:t.name});continue}if(k)try{await a.promises.mkdir(l.dirname(f),{recursive:!0});const p=Buffer.from(t.contents,"base64");await a.promises.writeFile(f,p),i(e,"INFO","Binary file written successfully",{filePath:t.name})}catch(p){i(e,"ERROR","Failed to write binary file",{filePath:t.name,error:p&&p.message})}else i(e,"WARN","Binary file does not exist and has no content, skipping",{filePath:t.name});continue}c&&(!y||y&&k)&&(await a.promises.mkdir(l.dirname(f),{recursive:!0}),await a.promises.writeFile(f,t.contents||"","utf8"))}}catch(t){throw i(e,"ERROR","Failed to write files",{projectId:e,error:t&&t.message,elapsedMs:Date.now()-w}),t}try{i(e,"DEBUG","Start cleaning missing files and empty directories",{projectId:e});const t=new Set(n.filter(f=>f&&typeof f.name=="string").map(f=>l.normalize(f.name)));await V(r,t,R.TRAVERSE_EXCLUDE_DIRS||[]),await C(r,R.TRAVERSE_EXCLUDE_DIRS||[])}catch(t){throw i(e,"ERROR","Failed to clean missing files, starting rollback",{projectId:e,error:t&&t.message,elapsedMs:Date.now()-w}),t}return i(e,"INFO","Files submitted successfully",{projectId:e,filesCount:n.length,elapsedMs:Date.now()-w}),{success:!0,message:"Files submitted successfully",projectId:e,restarted:!1}}catch(s){throw s.isOperational?s:new S("Failed to backup old version",{projectId:e,originalError:s&&s.message})}}async function $(e,F,n,d,P,w={}){const b=Date.now();if(!e)throw new m("Project ID cannot be empty",{field:"projectId"});const r=Number(F);if(!Number.isFinite(r))throw new m("codeVersion must be a number",{field:"codeVersion"});if(!n)throw new m("File cannot be empty",{field:"file"});if(!d||typeof d!="string")throw new m("File path cannot be empty",{field:"filePath"});const u=x(e,w);if(!a.existsSync(u))throw i(e,"ERROR","Project does not exist",{projectId:e,projectPath:u}),new O("Project does not exist",{projectId:e});const s=l.normalize(d).replace(/^[\/\\]+/,""),o=l.join(u,s),t=l.resolve(o),f=l.resolve(u);if(!t.startsWith(f))throw new m("File path is not safe, cannot exceed project directory",{field:"filePath",providedPath:d,resolvedPath:t});let h="";try{const c=l.join(R.UPLOAD_PROJECT_DIR,e);a.existsSync(c)||a.mkdirSync(c,{recursive:!0});const y=`${e}-v${r}.zip`;h=l.join(c,y),i(e,"DEBUG","Start backing up project",{projectId:e,backupZipPath:h}),await D(e,u,h),i(e,"INFO",`Project backed up: ${h}`,{projectId:e,zipPath:h});try{if(i(e,"DEBUG","Start writing uploaded file",{projectId:e,filePath:s}),await a.promises.mkdir(l.dirname(o),{recursive:!0}),!n.buffer)throw new m("File content format is incorrect, missing buffer",{field:"file"});if(i(e,"INFO","Prepare to write file",{targetPath:o,bufferLength:n.buffer.length,expectedSize:n.size,bufferIsBuffer:Buffer.isBuffer(n.buffer),sizeMatch:n.buffer.length===n.size}),await a.promises.writeFile(o,n.buffer),i(e,"INFO","File uploaded successfully",{projectId:e,filePath:s,targetPath:t,fileSize:n.buffer?n.buffer.length:0,elapsedMs:Date.now()-b}),!1)try{const g=await B(P,e);return i(e,"INFO","Restart development server successfully",{projectId:e,pid:g.pid,port:g.port}),{success:!0,message:"File uploaded and restarted development server successfully",projectId:e,filePath:s,targetPath:t,fileSize:n.buffer?n.buffer.length:0,pid:g.pid,port:g.port,restarted:!0}}catch(g){i(e,"ERROR","Failed to restart development server",{projectId:e,filePath:s,error:g&&g.message})}else return i(e,"INFO","File modification does not require restarting development server",{projectId:e,filePath:s}),{success:!0,message:"File uploaded successfully, no need to restart development server",projectId:e,restarted:!1}}catch(k){throw i(e,"ERROR","Failed to write file",{projectId:e,filePath:s,error:k&&k.message,elapsedMs:Date.now()-b}),k}}catch(c){throw c.isOperational?c:new S("Failed to backup project",{projectId:e,filePath:s,originalError:c&&c.message})}}async function A(e,F,n,d,P={}){const w=Date.now();if(!e)throw new m("Project ID cannot be empty",{field:"projectId"});const b=Number(F);if(!Number.isFinite(b))throw new m("codeVersion must be a number",{field:"codeVersion"});const r=Number(n);if(!Number.isFinite(r))throw new m("rollbackTo must be a number",{field:"rollbackTo"});if(r<0)throw new m("rollbackTo cannot be less than 0",{field:"rollbackTo"});if(r>=b)throw new m("rollbackTo must be less than current codeVersion",{field:"rollbackTo"});const u=x(e,P);if(!a.existsSync(u))throw i(e,"ERROR","Project does not exist",{projectId:e,projectPath:u}),new O("Project does not exist",{projectId:e});const s=l.join(R.UPLOAD_PROJECT_DIR,e),o=`${e}-v${r}.zip`,t=l.join(s,o);if(!a.existsSync(t))throw i(e,"ERROR","Rollback version backup file does not exist",{projectId:e,rollbackTo:r,zipPath:t}),new O("Rollback version backup file does not exist",{projectId:e,rollbackTo:r});let f="";try{a.existsSync(s)||a.mkdirSync(s,{recursive:!0});const h=`${e}-v${b}.zip`;return f=l.join(s,h),a.existsSync(f)?i(e,"INFO","Current version backup already exists, skipping backup",{projectId:e,zipPath:f}):(await D(e,u,f),i(e,"INFO","Current version backed up",{projectId:e,zipPath:f})),i(e,"DEBUG","Start restoring project from backup",{projectId:e,rollbackToNum:r,rollbackZipPath:t}),await z(e,u,t),i(e,"INFO","Project rolled back successfully",{projectId:e,newVersion:b,toVersion:r,rollbackZipPath:t,elapsedMs:Date.now()-w}),{success:!0,message:"Project rolled back successfully",newVersion:b,rollbackTo:r}}catch(h){if(i(e,"ERROR","Failed to rollback project",{projectId:e,rollbackTo:r,error:h&&h.message,elapsedMs:Date.now()-w}),f&&a.existsSync(f))try{i(e,"INFO","Failed to rollback, trying to restore current version",{projectId:e,backupPath:f}),await z(e,u,f),i(e,"INFO","Current version restored",{projectId:e})}catch(c){i(e,"ERROR","Failed to restore current version",{projectId:e,error:c&&c.message})}throw h.isOperational?h:new S("Failed to rollback project",{projectId:e,rollbackTo:r,originalError:h&&h.message})}}export{T as specifiedFilesUpdate,U as allFilesUpdate,$ as uploadSingleFile,A as rollbackVersion};export default{specifiedFilesUpdate:T,allFilesUpdate:U,uploadSingleFile:$,rollbackVersion:A};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{log as t}from"../utils/log/logUtils.js";import w from"../appConfig/index.js";import c from"path";import s from"fs";import{extractZip as F}from"../utils/common/zipUtils.js";import"../utils/build/startDevUtils.js";import"../utils/build/restartDevUtils.js";import{stopDevServer as d}from"../utils/build/stopDevUtils.js";import{ValidationError as h,BusinessError as C,SystemError as R,FileError as z,ResourceError as P}from"../utils/error/errorHandler.js";import{sanitizeSensitivePaths as N}from"../utils/common/sensitiveUtils.js";import{removeNodeModules as U}from"../utils/buildDependency/dependencyManager.js";import{backupProjectToZip as J,copyDirectoryFiltered as A}from"../utils/project/backupUtils.js";import{createPnpmNpmrc as $}from"../utils/common/npmrcUtils.js";async function v(e){const r=Date.now();if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});const u=w.PROJECT_SOURCE_DIR,i=c.join(u,e);if(s.existsSync(i))throw new C(`Project directory ${e} already exists`,{projectId:e,projectPath:i});try{s.mkdirSync(i,{recursive:!0}),t(e,"INFO",`Project directory created successfully: ${i}`,{projectId:e});const n=w.INIT_PROJECT_DIR,a=c.join(n,`${w.INIT_PROJECT_NAME}.zip`),l=c.join(n,w.INIT_PROJECT_NAME);if(t(e,"DEBUG","Start checking template directory",{templateDir:l,templateZipPath:a}),!s.existsSync(l)){if(!s.existsSync(a))throw t(e,"ERROR",`Initialization template does not exist: ${a}`,{projectId:e,templateZipPath:a}),new P("Initialization template does not exist",{});if(t(e,"INFO",`Template directory does not exist, starting to unzip template: ${a}`,{projectId:e,templateZipPath:a}),await F(a,l),t(e,"INFO","Template unzip completed",{projectId:e}),!s.existsSync(l))throw new R("Template unzip directory still does not exist",{})}t(e,"DEBUG","Start copying template content to project directory",{templateDir:l,projectPath:i});const g=await s.promises.readdir(l,{withFileTypes:!0});for(const f of g){const o=c.join(l,f.name),m=c.join(i,f.name);f.isDirectory()?(await s.promises.mkdir(m,{recursive:!0}),await A(o,m)):f.isFile()&&(await s.promises.mkdir(c.dirname(m),{recursive:!0}),await s.promises.copyFile(o,m))}return t(e,"DEBUG","Start creating .npmrc configuration file",{projectPath:i}),await $(i,e),t(e,"INFO",`Project ${e} initialized successfully`,{projectId:e,elapsedMs:Date.now()-r}),{success:!0,message:`Project ${e} created successfully`,projectPath:i}}catch(n){throw t(e,"ERROR",`Project ${e} initialization failed: ${n.message}`,{projectId:e,elapsedMs:Date.now()-r}),new R(`Project ${e} initialization failed: ${n.message}`,{projectId:e,projectPath:i,originalError:n.message})}}async function B(e){const r=await s.promises.readdir(e,{withFileTypes:!0}),u=w.TOP_LEVEL_NOISE_PATTERNS,i=r.filter(n=>{const a=n.name;return a.startsWith(".")?!1:!u.some(l=>l.endsWith("*")?a.startsWith(l.slice(0,-1)):a===l)});if(i.length===1&&i[0].isDirectory()){const n=c.join(e,i[0].name),a=c.join(e,"..",`temp_${Date.now()}`);await s.promises.rename(n,a);const l=await s.promises.readdir(a);for(const g of l){const f=c.join(a,g),o=c.join(e,g);await s.promises.rename(f,o)}await s.promises.rmdir(a)}}async function p(e){if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});const r=c.join(w.PROJECT_SOURCE_DIR,e);if(s.existsSync(r))try{t(e,"INFO",`Start cleaning project directory: ${r}`,{projectId:e}),await s.promises.rm(r,{recursive:!0,force:!0}),t(e,"INFO",`Project directory cleaned up: ${r}`,{projectId:e})}catch(u){throw t(e,"ERROR",`Failed to clean project directory: ${u.message}`,{projectId:e,projectPath:r,originalError:u.message}),new R(`Failed to clean project directory: ${u.message}`,{projectId:e,projectPath:r,originalError:u.message})}else t(e,"INFO",`Project directory does not exist, no need to clean: ${r}`,{projectId:e})}function M(e){if(!s.existsSync(e))return!0;try{return s.readdirSync(e).filter(i=>!i.startsWith(".")&&i!=="node_modules").length===0}catch(r){const u=c.basename(e);return t(u,"ERROR",`Failed to check if directory is empty: ${r.message}`,{dirPath:e}),!0}}async function S(e,r,u,i,n,a){const l=Date.now(),g=w.PROJECT_SOURCE_DIR,f=c.join(g,e);try{if(M(f))t(e,"INFO","Project directory is empty, directly deploying new project",{projectId:e});else{t(e,"INFO","Project directory is not empty, starting to backup current version",{projectId:e});const m=parseInt(i)-1,O=c.join(w.UPLOAD_PROJECT_DIR,e),y=c.join(O,`${e}-v${m}.zip`);if(s.existsSync(y))t(e,"INFO",`Backup file already exists, skipping backup: ${y}`,{projectId:e});else try{await D(e,m),t(e,"INFO",`Current version backed up: ${y}`,{projectId:e})}catch(E){throw t(e,"ERROR",`Failed to backup current version: ${E.message}`,{projectId:e}),new R(`Failed to backup current version: ${E.message}`,{projectId:e,originalError:E.message})}if(n&&!isNaN(Number(n))){const E=Number(n);t(e,"INFO",`Stopping old version dev server, PID: ${E}`,{projectId:e});try{await d(u,e,E,{strict:!0}),t(e,"INFO","Old version dev server stopped",{projectId:e})}catch(k){t(e,"WARN",`Failed to stop old version dev server: ${k.message}`,{projectId:e,pid:E})}}s.existsSync(f)&&(t(e,"INFO",`Cleaning project directory: ${f}`,{projectId:e}),await s.promises.rm(f,{recursive:!0,force:!0}))}return s.mkdirSync(f,{recursive:!0}),t(e,"INFO",`Project directory created successfully: ${f}`,{projectId:e}),t(e,"DEBUG","Start extracting zip file to project directory",{projectId:e,zipFilePath:r}),await F(r,f),t(e,"DEBUG","Zip file extracted successfully",{projectId:e}),t(e,"DEBUG","Check and remove top level folder",{projectId:e}),await B(f),t(e,"DEBUG","Check and remove node_modules folder",{projectId:e}),await U(f),t(e,"DEBUG","Start creating .npmrc configuration file",{projectId:e}),await $(f,e),t(e,"INFO",`Project ${e} uploaded successfully`,{projectId:e,codeVersion:i,elapsedMs:Date.now()-l}),{success:!0,message:`Project ${e} uploaded successfully`,projectId:e,codeVersion:i}}catch(o){t(e,"ERROR",`Failed to upload project: ${o.message}`,{projectId:e,elapsedMs:Date.now()-l});try{await p(e),t(e,"INFO","Failed to upload project, project directory cleaned up",{projectId:e})}catch(m){t(e,"ERROR",`Failed to clean project directory: ${m.message}`,{projectId:e,originalError:m.message})}throw o.isOperational?o:new R(`Failed to upload project: ${o.message}`,{projectId:e,projectPath:f,zipFilePath:r,originalError:o.message})}}async function D(e,r){if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});if(r==null)throw new h("codeVersion cannot be empty",{field:"codeVersion"});const u=Number(r);if(!Number.isFinite(u))throw new h("codeVersion must be a number",{field:"codeVersion"});const i=c.join(w.PROJECT_SOURCE_DIR,e);if(!s.existsSync(i))throw new P("Project does not exist",{projectId:e});const n=c.join(w.UPLOAD_PROJECT_DIR,e);s.existsSync(n)||s.mkdirSync(n,{recursive:!0});const a=`${e}-v${u}.zip`,l=c.join(n,a);return t(e,"DEBUG","Start backing up project to zip",{projectId:e,versionNum:u,outZipPath:l}),await J(e,i,l)}async function b(e,r,u){if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});if(!r)throw new h("codeVersion cannot be empty",{field:"codeVersion"});if(!u)throw new h("Please upload a zip file",{field:"zipFile"});const i=c.join(w.UPLOAD_PROJECT_DIR,e);s.existsSync(i)||s.mkdirSync(i,{recursive:!0});const n=u.path,a=c.join(i,`${e}-v${r}.zip`);try{return s.renameSync(n,a),t(e,"INFO","File saved successfully",{projectId:e,codeVersion:r,filePath:a}),{success:!0,filePath:a}}catch(l){if(t(e,"ERROR","Failed to move file",{projectId:e,codeVersion:r,error:l.message}),s.existsSync(n))try{s.unlinkSync(n)}catch(g){t(e,"ERROR","Failed to clean temporary file",{projectId:e,error:g.message})}throw new R("Failed to save file",{projectId:e,codeVersion:r,originalError:l.message})}}async function x(e,r,u){const i=Date.now();if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});const n=null;try{if(r&&!isNaN(Number(r))){const o=Number(r);t(n,"INFO",`[delete-project] Stopping development server, PID: ${o}`,{projectId:e,pid:o});try{await d(u,e,o,{strict:!0}),t(n,"INFO","[delete-project] Development server stopped",{projectId:e})}catch(m){t(n,"WARN",`[delete-project] Failed to stop development server: ${m.message}`,{projectId:e,pid:o})}}const a=[c.join(w.UPLOAD_PROJECT_DIR,e),c.join(w.PROJECT_SOURCE_DIR,e),c.join(w.DIST_TARGET_DIR,e),c.join(w.LOG_BASE_DIR,e)],l=[],g=[];for(const o of a)if(s.existsSync(o))try{await s.promises.rm(o,{recursive:!0,force:!0}),l.push(o),t(n,"INFO",`[delete-project] Directory deleted successfully: ${o}`,{projectId:e})}catch(m){g.push({path:o,error:m.message}),t(n,"ERROR",`[delete-project] Directory deleted failed: ${o}`,{projectId:e,error:m.message})}else t(n,"INFO",`[delete-project] Directory does not exist, skipping deletion: ${o}`,{projectId:e});const f={success:!0,message:`Project ${e} deleted successfully`,projectId:e,deletedDirectories:l,failedDirectories:g};return g.length>0&&(f.message+=`, but ${g.length} directories deleted failed`,t(n,"WARN","[delete-project] Some directories deleted failed",{projectId:e,failedDirs:g})),t(n,"INFO",`[delete-project] Project deleted successfully: ${e}`,{projectId:e,elapsedMs:Date.now()-i}),f}catch(a){throw t(n,"ERROR",`[delete-project] Failed to delete project: ${a.message}`,{projectId:e,originalError:a.message,elapsedMs:Date.now()-i}),a.isOperational?a:new R(`Failed to delete project: ${a.message}`,{projectId:e,originalError:a.message})}}async function T(e,r,u,i){const n=Date.now();if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});if(r==null)throw new h("codeVersion cannot be empty",{field:"codeVersion"});const a=Number(r);if(!Number.isFinite(a))throw new h("codeVersion must be a number",{field:"codeVersion"});const l=c.join(w.PROJECT_SOURCE_DIR,e);if(!s.existsSync(l))throw new P("Project does not exist",{projectId:e});const g=c.join(w.UPLOAD_PROJECT_DIR,e),f=`${e}-v${a}.zip`,o=c.join(g,f);if(u!=="LATEST"){if(s.existsSync(o))return t(e,"INFO",`Using existing export file: ${o}`,{projectId:e,zipPath:o}),{success:!0,projectId:e,zipPath:o};throw new P(`Specified version zip file does not exist: ${o}`,{projectId:e,zipPath:o})}const m=c.join(l,"cpage_config.json");let O=!1;try{if(i)try{const E=JSON.stringify(i,null,2);await s.promises.writeFile(m,E,"utf8"),O=!0,t(e,"INFO",`Configuration file created successfully: ${m}`,{projectId:e,configFilePath:m})}catch(E){throw t(e,"ERROR",`Failed to create configuration file: ${E.message}`,{projectId:e,error:E.message}),new z("Failed to create configuration file",{projectId:e,configFilePath:m,originalError:E.message})}t(e,"DEBUG","Start executing export and packaging",{projectId:e,codeVersion:r});const y=await D(e,r);return t(e,"INFO",`Project exported successfully: ${y}`,{projectId:e,zipPath:y,elapsedMs:Date.now()-n}),{success:!0,projectId:e,zipPath:y}}catch(y){throw t(e,"ERROR",`Failed to export project: ${y?.message}`,{projectId:e,elapsedMs:Date.now()-n}),y.isOperational?y:new R("Failed to export project",{projectId:e,originalError:y&&y.message?N(y.message):y&&y.message})}finally{if(O&&s.existsSync(m))try{await s.promises.unlink(m),t(e,"INFO",`Temporary configuration file deleted successfully: ${m}`,{projectId:e,configFilePath:m})}catch(y){t(e,"WARN",`Failed to delete temporary configuration file: ${y.message}`,{projectId:e,error:y.message})}}}async function _(e,r){const u=Date.now();if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});if(r==null)throw new h("codeVersion cannot be empty",{field:"codeVersion"});try{const i=await D(e,r);return t(e,"INFO",`Current version backed up successfully: ${i}`,{projectId:e,zipPath:i,elapsedMs:Date.now()-u}),{success:!0,projectId:e,zipPath:i}}catch(i){throw t(e,"ERROR",`Failed to backup current version: ${i?.message}`,{projectId:e,elapsedMs:Date.now()-u}),i.isOperational?i:new R("Failed to backup current version",{projectId:e,originalError:i&&i.message?N(i.message):i&&i.message})}}export{v as createProject,S as uploadProject,_ as backupCurrentVersion,T as exportProject,D as backupProjectOfVersion,p as cleanupProjectDirectory,b as handleFileUpload,x as deleteProject};export default{createProject:v,uploadProject:S,backupCurrentVersion:_,exportProject:T,backupProjectOfVersion:D,cleanupProjectDirectory:p,handleFileUpload:b,deleteProject:x};
|
|
1
|
+
import{log as s}from"../utils/log/logUtils.js";import P from"../appConfig/index.js";import u from"path";import i from"fs";import{extractZip as T}from"../utils/common/zipUtils.js";import"../utils/build/startDevUtils.js";import"../utils/build/restartDevUtils.js";import{stopDevServer as M}from"../utils/build/stopDevUtils.js";import{ValidationError as h,BusinessError as B,SystemError as R,FileError as b,ResourceError as _}from"../utils/error/errorHandler.js";import{sanitizeSensitivePaths as C}from"../utils/common/sensitiveUtils.js";import{removeNodeModules as Y}from"../utils/buildDependency/dependencyManager.js";import{backupProjectToZip as j,copyDirectoryFiltered as I}from"../utils/project/backupUtils.js";import{createPnpmNpmrc as W}from"../utils/common/npmrcUtils.js";import{resolveProjectPath as v}from"../utils/common/projectPathUtils.js";import{ensurePrimaryAgentDirs as ee,syncAgents as te}from"../utils/common/AgentWorkspaceUtils.js";async function L(e,t="react",l={}){const a=Date.now();if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});const r=["react","vue3"];if(!r.includes(t))throw new h("Template type is invalid, only supports react or vue3",{field:"templateType",templateType:t,allowedValues:r});const c={react:P.INIT_PROJECT_NAME_REACT||"react-vite-template",vue3:P.INIT_PROJECT_NAME_VUE3||"vue3-vite-template"}[t],w=v(e,l);if(i.existsSync(w))throw new B(`Project directory ${e} already exists`,{projectId:e,projectPath:w});try{i.mkdirSync(w,{recursive:!0}),s(e,"INFO",`Project directory created successfully: ${w}`,{projectId:e});const f=P.INIT_PROJECT_DIR,y=u.join(f,`${c}.zip`),m=u.join(f,c);if(s(e,"DEBUG","Start checking template directory",{templateDir:m,templateZipPath:y}),!i.existsSync(m)){if(!i.existsSync(y))throw s(e,"ERROR",`Initialization template does not exist: ${y}`,{projectId:e,templateZipPath:y}),new _("Initialization template does not exist",{});if(s(e,"INFO",`Template directory does not exist, starting to unzip template: ${y}`,{projectId:e,templateZipPath:y}),await T(y,m),s(e,"INFO","Template unzip completed",{projectId:e}),!i.existsSync(m))throw new R("Template unzip directory still does not exist",{})}s(e,"DEBUG","Start copying template content to project directory",{templateDir:m,projectPath:w});const d=await i.promises.readdir(m,{withFileTypes:!0});for(const D of d){const n=u.join(m,D.name),p=u.join(w,D.name);D.isDirectory()?(await i.promises.mkdir(p,{recursive:!0}),await I(n,p)):D.isFile()&&(await i.promises.mkdir(u.dirname(p),{recursive:!0}),await i.promises.copyFile(n,p))}return s(e,"DEBUG","Start creating .npmrc configuration file",{projectPath:w}),await W(w,e),s(e,"INFO",`Project ${e} initialized successfully`,{projectId:e,elapsedMs:Date.now()-a}),{success:!0,message:`Project ${e} created successfully`,projectPath:w}}catch(f){throw s(e,"ERROR",`Project ${e} initialization failed: ${f.message}`,{projectId:e,elapsedMs:Date.now()-a}),new R(`Project ${e} initialization failed: ${f.message}`,{projectId:e,projectPath:w,originalError:f.message})}}async function ie(e){const t=await i.promises.readdir(e,{withFileTypes:!0}),l=P.TOP_LEVEL_NOISE_PATTERNS,a=t.filter(r=>{const o=r.name;return o.startsWith(".")?!1:!l.some(c=>c.endsWith("*")?o.startsWith(c.slice(0,-1)):o===c)});if(a.length===1&&a[0].isDirectory()){const r=u.join(e,a[0].name),o=u.join(e,"..",`temp_${Date.now()}`);await i.promises.rename(r,o);const c=await i.promises.readdir(o);for(const w of c){const f=u.join(o,w),y=u.join(e,w);await i.promises.rename(f,y)}await i.promises.rmdir(o)}}async function A(e,t={}){if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});const l=v(e,t);if(i.existsSync(l))try{s(e,"INFO",`Start cleaning project directory: ${l}`,{projectId:e}),await i.promises.rm(l,{recursive:!0,force:!0}),s(e,"INFO",`Project directory cleaned up: ${l}`,{projectId:e})}catch(a){throw s(e,"ERROR",`Failed to clean project directory: ${a.message}`,{projectId:e,projectPath:l,originalError:a.message}),new R(`Failed to clean project directory: ${a.message}`,{projectId:e,projectPath:l,originalError:a.message})}else s(e,"INFO",`Project directory does not exist, no need to clean: ${l}`,{projectId:e})}function se(e){if(!i.existsSync(e))return!0;try{return i.readdirSync(e).filter(a=>!a.startsWith(".")&&a!=="node_modules").length===0}catch(t){const l=u.basename(e);return s(l,"ERROR",`Failed to check if directory is empty: ${t.message}`,{dirPath:e}),!0}}async function G(e,t,l,a,r,o,c={}){const w=Date.now(),f=v(e,c);try{if(se(f))s(e,"INFO","Project directory is empty, directly deploying new project",{projectId:e});else{s(e,"INFO","Project directory is not empty, starting to backup current version",{projectId:e});const m=parseInt(a)-1,d=u.join(P.UPLOAD_PROJECT_DIR,e),D=u.join(d,`${e}-v${m}.zip`);if(i.existsSync(D))s(e,"INFO",`Backup file already exists, skipping backup: ${D}`,{projectId:e});else try{await $(e,m,c),s(e,"INFO",`Current version backed up: ${D}`,{projectId:e})}catch(n){throw s(e,"ERROR",`Failed to backup current version: ${n.message}`,{projectId:e}),new R(`Failed to backup current version: ${n.message}`,{projectId:e,originalError:n.message})}if(r&&!isNaN(Number(r))){const n=Number(r);s(e,"INFO",`Stopping old version dev server, PID: ${n}`,{projectId:e});try{await M(l,e,n,{strict:!0}),s(e,"INFO","Old version dev server stopped",{projectId:e})}catch(p){s(e,"WARN",`Failed to stop old version dev server: ${p.message}`,{projectId:e,pid:n})}}i.existsSync(f)&&(s(e,"INFO",`Cleaning project directory: ${f}`,{projectId:e}),await i.promises.rm(f,{recursive:!0,force:!0}))}return i.mkdirSync(f,{recursive:!0}),s(e,"INFO",`Project directory created successfully: ${f}`,{projectId:e}),s(e,"DEBUG","Start extracting zip file to project directory",{projectId:e,zipFilePath:t}),await T(t,f),s(e,"DEBUG","Zip file extracted successfully",{projectId:e}),s(e,"DEBUG","Check and remove top level folder",{projectId:e}),await ie(f),s(e,"DEBUG","Check and remove node_modules folder",{projectId:e}),await Y(f),s(e,"DEBUG","Start creating .npmrc configuration file",{projectId:e}),await W(f,e),s(e,"INFO",`Project ${e} uploaded successfully`,{projectId:e,codeVersion:a,elapsedMs:Date.now()-w}),{success:!0,message:`Project ${e} uploaded successfully`,projectId:e,codeVersion:a}}catch(y){s(e,"ERROR",`Failed to upload project: ${y.message}`,{projectId:e,elapsedMs:Date.now()-w});try{await A(e,c),s(e,"INFO","Failed to upload project, project directory cleaned up",{projectId:e})}catch(m){s(e,"ERROR",`Failed to clean project directory: ${m.message}`,{projectId:e,originalError:m.message})}throw y.isOperational?y:new R(`Failed to upload project: ${y.message}`,{projectId:e,projectPath:f,zipFilePath:t,originalError:y.message})}}async function $(e,t,l={}){if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});if(t==null)throw new h("codeVersion cannot be empty",{field:"codeVersion"});const a=Number(t);if(!Number.isFinite(a))throw new h("codeVersion must be a number",{field:"codeVersion"});const r=v(e,l);if(!i.existsSync(r))throw new _("Project does not exist",{projectId:e});const o=u.join(P.UPLOAD_PROJECT_DIR,e);i.existsSync(o)||i.mkdirSync(o,{recursive:!0});const c=`${e}-v${a}.zip`,w=u.join(o,c);return s(e,"DEBUG","Start backing up project to zip",{projectId:e,versionNum:a,outZipPath:w}),await j(e,r,w)}async function J(e,t,l){if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});if(!t)throw new h("codeVersion cannot be empty",{field:"codeVersion"});if(!l)throw new h("Please upload a zip file",{field:"zipFile"});const a=u.join(P.UPLOAD_PROJECT_DIR,e);i.existsSync(a)||i.mkdirSync(a,{recursive:!0});const r=l.path,o=u.join(a,`${e}-v${t}.zip`);try{return i.renameSync(r,o),s(e,"INFO","File saved successfully",{projectId:e,codeVersion:t,filePath:o}),{success:!0,filePath:o}}catch(c){if(s(e,"ERROR","Failed to move file",{projectId:e,codeVersion:t,error:c.message}),i.existsSync(r))try{i.unlinkSync(r)}catch(w){s(e,"ERROR","Failed to clean temporary file",{projectId:e,error:w.message})}throw new R("Failed to save file",{projectId:e,codeVersion:t,originalError:c.message})}}function re(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 l=JSON.parse(t);if(Array.isArray(l))return l.map(a=>typeof a=="string"?a.trim():"").filter(Boolean)}catch{}return[t]}return[]}async function Z(e){i.existsSync(e)&&await i.promises.rm(e,{recursive:!0,force:!0})}async function V(e,t){try{await i.promises.rename(e,t)}catch(l){if(l.code==="EXDEV"){async function a(r,o){if((await i.promises.lstat(r)).isDirectory()){await i.promises.mkdir(o,{recursive:!0});const w=await i.promises.readdir(r);for(const f of w)await a(u.join(r,f),u.join(o,f))}else await i.promises.copyFile(r,o)}await a(e,t),await i.promises.rm(e,{recursive:!0,force:!0})}else throw l}}async function ae(e,t){const l=u.join(e,t);if(i.existsSync(l)&&(await i.promises.lstat(l)).isDirectory())return l;const a=await i.promises.readdir(e,{withFileTypes:!0});for(const r of a){if(!r.isDirectory())continue;const o=u.join(e,r.name,t);if(i.existsSync(o)&&(await i.promises.lstat(o)).isDirectory())return o}return null}async function ne(e,t,l){let a;try{a=await fetch(e)}catch(w){throw new b(`Failed to download skill zip from url: ${e}`,{url:e,reason:w.message})}if(!a.ok)throw new b(`Failed to download skill zip from url: ${e}`,{url:e,status:a.status,statusText:a.statusText});const r=a.headers.get("content-type")||"";r&&!r.includes("zip")&&!r.includes("octet-stream")&&s(l,"WARN","Downloaded skill url content-type is not typical zip",{url:e,contentType:r});const o=await a.arrayBuffer(),c=Buffer.from(o);await i.promises.writeFile(t,c)}async function X(e,t,l,a={}){const r=String(e),o=Date.now(),c=re(l),w=[],f=[];if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});if(!t&&c.length===0)throw new h("file or skillUrls cannot both be empty",{field:"file|skillUrls"});if(t){if(!t.path)throw new h("Uploaded file has no valid path",{field:"file.path"});if(u.extname(t.originalname||t.filename||"").toLowerCase()!==".zip")throw new h("Only zip files are supported",{field:"file",originalName:t?.originalname})}const y=v(String(e),a);if(!i.existsSync(y))throw new h("Project does not exist",{field:"projectId"});const{skillsDir:m,agentTypes:d}=await ee(y),D=u.join(P.UPLOAD_PROJECT_DIR,"temp");try{await i.promises.mkdir(m,{recursive:!0}),await i.promises.mkdir(D,{recursive:!0});const n=new Set;if(t){const k=u.join(D,`project_skill_push_${Date.now()}_${Math.round(Math.random()*1e6)}`);f.push(k),await i.promises.mkdir(k,{recursive:!0}),await T(t.path,k);const N=await ae(k,"skills");if(!N)s(r,"WARN","skills directory not found in uploaded zip",{projectId:e,extractRoot:k});else{const E=(await i.promises.readdir(N,{withFileTypes:!0})).filter(F=>F.isDirectory()&&!F.name.startsWith("."));for(const F of E){const S=u.join(N,F.name),x=u.join(m,F.name);i.existsSync(x)&&await Z(x),await V(S,x),n.add(F.name)}}}for(let k=0;k<c.length;k+=1){const N=c[k],O=u.join(D,`project_skill_url_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}.zip`);w.push(O);const E=u.join(D,`project_skill_url_extract_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}`);f.push(E),await i.promises.mkdir(E,{recursive:!0}),await ne(N,O,r),await T(O,E);const S=(await i.promises.readdir(E,{withFileTypes:!0})).filter(g=>g.isDirectory()&&!g.name.startsWith(".")),U=S.find(g=>g.name==="skills")?(await i.promises.readdir(u.join(E,"skills"),{withFileTypes:!0})).filter(g=>g.isDirectory()&&!g.name.startsWith(".")).map(g=>({name:g.name,sourcePath:u.join(E,"skills",g.name)})):S.map(g=>({name:g.name,sourcePath:u.join(E,g.name)}));if(U.length===0){s(r,"WARN","No skill directory found after extracting skill url zip",{projectId:e,skillUrl:N,extractRoot:E});continue}for(const g of U){const z=u.join(m,g.name);i.existsSync(z)&&await Z(z),await V(g.sourcePath,z),n.add(g.name)}}const p=Array.from(n),Q=p.length>0?`Pushed ${p.length} skills: ${p.join(", ")}`:"No valid skill directories found in file or skillUrls";return s(r,"INFO","Push skills to project workspace completed",{projectId:e,updatedSkills:p,skillUrlsCount:c.length,hasFile:!!t,agentTypes:d,elapsedMs:Date.now()-o}),await te(y),{message:Q,projectPath:y,updatedSkills:p}}catch(n){throw s(r,"ERROR","Push skills to project workspace failed",{projectId:e,error:n.message,elapsedMs:Date.now()-o}),n instanceof h||n instanceof b||n instanceof B||n instanceof R?n:new R(`Failed to push skills to project workspace: ${n.message}`,{projectId:e})}finally{for(const n of f)try{i.existsSync(n)&&await i.promises.rm(n,{recursive:!0,force:!0})}catch(p){s(r,"WARN","Failed to clean extracted skill temp dir",{extractRoot:n,error:p.message})}for(const n of w)try{i.existsSync(n)&&await i.promises.unlink(n)}catch(p){s(r,"WARN","Failed to clean downloaded skill zip",{zipPath:n,error:p.message})}try{t?.path&&i.existsSync(t.path)&&await i.promises.unlink(t.path)}catch(n){s(r,"WARN","Failed to clean uploaded skill zip",{tempZipPath:t?.path,error:n.message})}}}async function q(e,t,l,a={}){const r=Date.now();if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});const o=null;try{if(t&&!isNaN(Number(t))){const m=Number(t);s(o,"INFO",`[delete-project] Stopping development server, PID: ${m}`,{projectId:e,pid:m});try{await M(l,e,m,{strict:!0}),s(o,"INFO","[delete-project] Development server stopped",{projectId:e})}catch(d){s(o,"WARN",`[delete-project] Failed to stop development server: ${d.message}`,{projectId:e,pid:m})}}const c=[u.join(P.UPLOAD_PROJECT_DIR,e),v(e,a),u.join(P.DIST_TARGET_DIR,e),u.join(P.LOG_BASE_DIR,e)],w=[],f=[];for(const m of c)if(i.existsSync(m))try{await i.promises.rm(m,{recursive:!0,force:!0}),w.push(m),s(o,"INFO",`[delete-project] Directory deleted successfully: ${m}`,{projectId:e})}catch(d){f.push({path:m,error:d.message}),s(o,"ERROR",`[delete-project] Directory deleted failed: ${m}`,{projectId:e,error:d.message})}else s(o,"INFO",`[delete-project] Directory does not exist, skipping deletion: ${m}`,{projectId:e});const y={success:!0,message:`Project ${e} deleted successfully`,projectId:e,deletedDirectories:w,failedDirectories:f};return f.length>0&&(y.message+=`, but ${f.length} directories deleted failed`,s(o,"WARN","[delete-project] Some directories deleted failed",{projectId:e,failedDirs:f})),s(o,"INFO",`[delete-project] Project deleted successfully: ${e}`,{projectId:e,elapsedMs:Date.now()-r}),y}catch(c){throw s(o,"ERROR",`[delete-project] Failed to delete project: ${c.message}`,{projectId:e,originalError:c.message,elapsedMs:Date.now()-r}),c.isOperational?c:new R(`Failed to delete project: ${c.message}`,{projectId:e,originalError:c.message})}}async function H(e,t,l,a,r={}){const o=Date.now();if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});if(t==null)throw new h("codeVersion cannot be empty",{field:"codeVersion"});const c=Number(t);if(!Number.isFinite(c))throw new h("codeVersion must be a number",{field:"codeVersion"});const w=v(e,r);if(!i.existsSync(w))throw new _("Project does not exist",{projectId:e});const f=u.join(P.UPLOAD_PROJECT_DIR,e),y=`${e}-v${c}.zip`,m=u.join(f,y);if(l!=="LATEST"){if(i.existsSync(m))return s(e,"INFO",`Using existing export file: ${m}`,{projectId:e,zipPath:m}),{success:!0,projectId:e,zipPath:m};throw new _(`Specified version zip file does not exist: ${m}`,{projectId:e,zipPath:m})}const d=u.join(w,"cpage_config.json");let D=!1;try{if(a)try{const p=JSON.stringify(a,null,2);await i.promises.writeFile(d,p,"utf8"),D=!0,s(e,"INFO",`Configuration file created successfully: ${d}`,{projectId:e,configFilePath:d})}catch(p){throw s(e,"ERROR",`Failed to create configuration file: ${p.message}`,{projectId:e,error:p.message}),new b("Failed to create configuration file",{projectId:e,configFilePath:d,originalError:p.message})}s(e,"DEBUG","Start executing export and packaging",{projectId:e,codeVersion:t});const n=await $(e,t,r);return s(e,"INFO",`Project exported successfully: ${n}`,{projectId:e,zipPath:n,elapsedMs:Date.now()-o}),{success:!0,projectId:e,zipPath:n}}catch(n){throw s(e,"ERROR",`Failed to export project: ${n?.message}`,{projectId:e,elapsedMs:Date.now()-o}),n.isOperational?n:new R("Failed to export project",{projectId:e,originalError:n&&n.message?C(n.message):n&&n.message})}finally{if(D&&i.existsSync(d))try{await i.promises.unlink(d),s(e,"INFO",`Temporary configuration file deleted successfully: ${d}`,{projectId:e,configFilePath:d})}catch(n){s(e,"WARN",`Failed to delete temporary configuration file: ${n.message}`,{projectId:e,error:n.message})}}}async function K(e,t,l={}){const a=Date.now();if(!e)throw new h("Project ID cannot be empty",{field:"projectId"});if(t==null)throw new h("codeVersion cannot be empty",{field:"codeVersion"});try{const r=await $(e,t,l);return s(e,"INFO",`Current version backed up successfully: ${r}`,{projectId:e,zipPath:r,elapsedMs:Date.now()-a}),{success:!0,projectId:e,zipPath:r}}catch(r){throw s(e,"ERROR",`Failed to backup current version: ${r?.message}`,{projectId:e,elapsedMs:Date.now()-a}),r.isOperational?r:new R("Failed to backup current version",{projectId:e,originalError:r&&r.message?C(r.message):r&&r.message})}}export{L as createProject,G as uploadProject,K as backupCurrentVersion,H as exportProject,$ as backupProjectOfVersion,A as cleanupProjectDirectory,J as handleFileUpload,X as pushSkillsToWorkspace,q as deleteProject};export default{createProject:L,uploadProject:G,backupCurrentVersion:K,exportProject:H,backupProjectOfVersion:$,cleanupProjectDirectory:A,handleFileUpload:J,pushSkillsToWorkspace:X,deleteProject:q};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{exec as
|
|
2
|
-
`);return}if(
|
|
3
|
-
`)}catch(
|
|
4
|
-
`),
|
|
1
|
+
import{exec as P}from"child_process";import p from"path";import n from"fs";import{log as f,logBuild as w}from"../log/logUtils.js";import O from"../error/buildErrorParser.js";import b from"../../appConfig/index.js";import{BusinessError as S,SystemError as v,FileError as T,ResourceError as k}from"../error/errorHandler.js";import{installDependencies as U}from"../buildDependency/dependencyManager.js";import{extractIsolationContext as W,resolveProjectPath as L}from"../common/projectPathUtils.js";const X=b.DIST_TARGET_DIR,R=new Set;let x=0;async function B({req:t,projectPath:i,projectId:c,outStream:r}){try{const o=p.join(i,"dist"),l=X,a=p.join(l,c,"dist");if(!n.existsSync(o)){const e=`dist directory not found: ${o}`;f(c,"WARN",e,{projectId:c}),r&&r.write(`${e}
|
|
2
|
+
`);return}if(n.existsSync(l)||n.mkdirSync(l,{recursive:!0}),n.existsSync(a)&&await n.promises.rm(a,{recursive:!0,force:!0}),n.promises.cp)await n.promises.cp(o,a,{recursive:!0});else{const e=(u,s)=>{if(n.statSync(u).isDirectory()){n.existsSync(s)||n.mkdirSync(s,{recursive:!0});for(const g of n.readdirSync(u))e(p.join(u,g),p.join(s,g))}else n.copyFileSync(u,s)};e(o,a)}const m=`dist directory copied to: ${a}`;f(c,"INFO",m,{projectId:c}),r&&r.write(`${m}
|
|
3
|
+
`)}catch(o){const l=`Failed to copy dist directory: ${o.message}`;throw f(c,"ERROR",l,{projectId:c}),r&&r.write(`${l}
|
|
4
|
+
`),o}}function C(t,i,c,r=[]){return new Promise((o,l)=>{const a=Array.isArray(r)&&r.length>0?" -- "+r.map(e=>String(e)).join(" "):"",m=`cd ${i} && pnpm run ${c}${a}`;w(t,"INFO","Execute command",{command:m});try{f(t,"INFO","Execute build script",{command:m,cwd:i})}catch{}P(m,{env:process.env,maxBuffer:10*1024*1024},(e,u,s)=>{if(e){w(t,"ERROR","Execution error",{error:e.message,stderr:s});const y=new O,g=s||e.message,d=y.parseBuildError(g,t),E=new v(d,{originalError:e.message,command:m});return l(E)}w(t,"INFO","Script execution completed",{stdout:u}),o(u)})})}async function Y(t,i){const c=W(t?.query||{}),r=L(i,c),o=p.join(r,"package.json");if(!n.existsSync(o))throw new k("Project missing package.json file",{projectId:i,projectPath:r});let a;try{a=JSON.parse(n.readFileSync(o,"utf8"))}catch(s){throw new T("package.json file format error",{projectId:i,jsonFilePath:o,originalError:s.message})}const e=a.scripts.build;if(!e)throw f(i,"WARN","Project missing build script",{projectId:i,requestId:t.requestId}),new S("Project missing build script",{projectId:i});if(R.has(i))throw new S("This project is being built",{projectId:i});const u=Number.isFinite(b.MAX_BUILD_CONCURRENCY)?b.MAX_BUILD_CONCURRENCY:20;if(x>=u)throw new S("Concurrency is full, please try again later",{currentBuilds:x,maxConcurrency:u});R.add(i),x+=1;try{let s="";t&&t.query&&typeof t.query.basePath=="string"&&(s=t.query.basePath),s&&(s.startsWith("/")||(s="/"+s),s.endsWith("/")||(s=s+"/"));const y=[];if(typeof e=="string"&&e.includes("vite")&&s&&y.push("--base",s),typeof e=="string"&&e.includes("vite")&&y.push("--debug"),f(i,"INFO","Start installing dependencies",{projectId:i}),await U(t,i,r),typeof e=="string"&&e.includes("vite")){const g=["exec","vite","build",...y,"--debug"],d=`cd ${r} && pnpm ${g.join(" ")}`;w(i,"INFO","Execute command(direct vite)",{command:d});try{f(i,"INFO","Execute build script(direct vite)",{command:d,cwd:r})}catch{}await new Promise((E,$)=>{P(d,(h,F,N)=>{if(h){w(i,"ERROR","Execution error",{error:h.message,stderr:N});const D=new O,M=N||h.message,_=D.parseBuildError(M,i),A=new v(_,{originalError:h.message,command:d});return $(A)}w(i,"INFO","Script execution completed",{stdout:F}),E(F)})})}else f(i,"INFO","Start synchronously executing build script",{projectId:i}),await C(i,r,"build",y);return await B({req:t,projectPath:r,projectId:i}),{success:!0,message:"Build completed",projectId:i}}finally{R.delete(i),x-=1}}export{Y as buildProject,B as copyBuildOutputToTarget,C as runBuildScript};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import{spawn as Z}from"child_process";import C from"fs";import I from"path";import{log as n,getLogDir as oe,getCSTDateString as ae,getCSTTimestampString as le}from"../log/logUtils.js";import j from"../log/logCacheManager.js";import{BusinessError as ce}from"../error/errorHandler.js";import de from"../error/errorCodes.js";import{sanitizeSensitivePaths as ue}from"../common/sensitiveUtils.js";import ee from"../../appConfig/index.js";import"../buildArg/portUtils.js";import me from"../buildArg/extraArgsUtils.js";import{ensureDevBinariesExecutable as pe}from"../buildPermission/permissionManager.js";import{installDependencies as ge}from"../buildDependency/dependencyManager.js";import fe from"../buildArg/portPool.js";import{isProjectAlive as Ee}from"../buildJudge/aliveJudgeUtils.js";function Je(s,e,i){let o="\u5F00\u53D1\u670D\u52A1\u5668\u542F\u52A8\u5931\u8D25";try{if(C.existsSync(s)){const E=C.readFileSync(s,"utf8"),
|
|
2
|
-
`);let y=-1;for(let f=0;f<
|
|
3
|
-
`).trim(),o||(o="\u547D\u4EE4\u5DF2\u6267\u884C\uFF0C\u4F46\u65E0\u8F93\u51FA\u4FE1\u606F")):o=E.trim(),o=ue(o),o.length>1e3&&(o=o.substring(o.length-1e3))}}catch(E){n(e,"WARN","\u8BFB\u53D6\u65E5\u5FD7\u6587\u4EF6\u5931\u8D25",{projectId:e,pid:i,error:E.message})}return o}const w=new Map,H=new Set;function Re(s){return w.get(s)||null}function he(s,e){w.set(s,e)}function ye(s){w.delete(s)}function xe(s){return H.has(s)}function Pe(s){H.add(s)}function we(s){H.delete(s)}async function Ne({req:s,projectId:e,projectPath:i,devScript:o}){const E=(o||"").toLowerCase(),
|
|
4
|
-
`;
|
|
5
|
-
`;
|
|
6
|
-
`;
|
|
7
|
-
`;throw
|
|
8
|
-
`;
|
|
9
|
-
`;
|
|
1
|
+
import{spawn as Z}from"child_process";import C from"fs";import I from"path";import{log as n,getLogDir as oe,getCSTDateString as ae,getCSTTimestampString as le}from"../log/logUtils.js";import j from"../log/logCacheManager.js";import{BusinessError as ce}from"../error/errorHandler.js";import de from"../error/errorCodes.js";import{sanitizeSensitivePaths as ue}from"../common/sensitiveUtils.js";import ee from"../../appConfig/index.js";import"../buildArg/portUtils.js";import me from"../buildArg/extraArgsUtils.js";import{ensureDevBinariesExecutable as pe}from"../buildPermission/permissionManager.js";import{installDependencies as ge}from"../buildDependency/dependencyManager.js";import fe from"../buildArg/portPool.js";import{isProjectAlive as Ee}from"../buildJudge/aliveJudgeUtils.js";function Je(s,e,i){let o="\u5F00\u53D1\u670D\u52A1\u5668\u542F\u52A8\u5931\u8D25";try{if(C.existsSync(s)){const E=C.readFileSync(s,"utf8"),c=E.split(`
|
|
2
|
+
`);let y=-1;for(let f=0;f<c.length;f++){const r=c[f].trim();if(r.match(/^>\s*(vite|webpack|next|nuxt|rollup|parcel|esbuild|tsc|ts-node|astro|svelte|remix)/)||r.match(/^>\s*(pnpm|npm|yarn|bun)\s+(run\s+)?(dev|start|serve|build|watch|start:dev)/)||r.match(/^>\s*node\s+\S+\.(js|ts|mjs)$/)||r.includes("\u5F00\u59CB\u6267\u884C\u811A\u672C: dev")||r.includes("\u5F00\u59CB\u6267\u884C\u811A\u672C: start")||r.includes("\u5F00\u59CB\u6267\u884C\u811A\u672C: serve")||r.includes("\u5F00\u59CB\u6267\u884C\u811A\u672C: build")){y=f;break}}if(y===-1)for(let f=0;f<c.length;f++){const r=c[f].trim();if(r.includes("VITE")||r.includes("Webpack")||r.includes("Next.js")||r.includes("Nuxt")||r.includes("Rollup")||r.includes("Parcel")||r.includes("Astro")||r.includes("Svelte")||r.includes("Remix")||r.includes("Local:")||r.includes("Network:")||r.includes("ready in")||r.includes("compiled successfully")||r.includes("dev server running")||r.includes("server started")){y=f;break}}if(y===-1)for(let f=0;f<c.length;f++){const r=c[f].trim();if(r.includes("Error")||r.includes("error")||r.includes("failed")||r.includes("Cannot find")||r.includes("ERR_")||r.includes("Command failed")||r.includes("ELIFECYCLE")||r.includes("npm ERR!")||r.includes("pnpm ERR!")){y=f;break}}y>=0?(o=c.slice(y).join(`
|
|
3
|
+
`).trim(),o||(o="\u547D\u4EE4\u5DF2\u6267\u884C\uFF0C\u4F46\u65E0\u8F93\u51FA\u4FE1\u606F")):o=E.trim(),o=ue(o),o.length>1e3&&(o=o.substring(o.length-1e3))}}catch(E){n(e,"WARN","\u8BFB\u53D6\u65E5\u5FD7\u6587\u4EF6\u5931\u8D25",{projectId:e,pid:i,error:E.message})}return o}const w=new Map,H=new Set;function Re(s){return w.get(s)||null}function he(s,e){w.set(s,e)}function ye(s){w.delete(s)}function xe(s){return H.has(s)}function Pe(s){H.add(s)}function we(s){H.delete(s)}async function Ne({req:s,projectId:e,projectPath:i,devScript:o}){const E=(o||"").toLowerCase(),c=E.includes("vite"),y=E.includes("next");if(!c&&!y)throw new ce("\u4E0D\u652F\u6301\u7684\u811A\u672C\u7C7B\u578B"+o+"\uFF0C\u8BF7\u4F7F\u7528vite\u6216next\u811A\u672C",{projectId:e,code:de.INVALID_SCRIPT_TYPE});const f=oe(e);C.existsSync(f)||C.mkdirSync(f,{recursive:!0});const r=ae(),W=I.join(f,`dev-${r}.log`),U=I.join(f,`dev-temp-${Date.now().toString()}.log`);let m,p,d,b=!1,O=!1,_=null,G=!1;const u=(l,N,x,v=!1)=>{if(!l||l.destroyed)return l&&l.destroyed&&n(e,"DEBUG",`${x}\u6D41\u5DF2\u9500\u6BC1\uFF0C\u8DF3\u8FC7\u5199\u5165`,{destroyed:!0}),!1;if(b)return!1;try{const h=`[${le()}] `+N,L=l.write(h);return v&&typeof l.cork=="function"&&typeof l.uncork=="function"&&(l.cork(),setImmediate(()=>{try{l.destroyed||l.uncork()}catch{}})),j.isEnabled()&&!G&&(j.delete(String(e)),G=!0),L}catch(S){if(n(e,"WARN",`${x}\u5199\u5165\u9519\u8BEF`,{error:S.message,code:S.code,streamName:x}),S.code==="ERR_STREAM_WRITE_AFTER_END"||S.code==="EPIPE")try{l&&!l.destroyed&&l.end()}catch(h){n(e,"WARN",`\u5173\u95ED${x}\u6D41\u65F6\u51FA\u9519`,{error:h.message})}return!1}},F=()=>{if(!b){b=!0;try{m&&!m.destroyed&&m.end()}catch(l){n(e,"WARN","\u5173\u95ED\u4E3B\u65E5\u5FD7\u6D41\u65F6\u51FA\u9519",{error:l.message})}try{p&&!p.destroyed&&p.end()}catch(l){n(e,"WARN","\u5173\u95ED\u4E34\u65F6\u65E5\u5FD7\u6D41\u65F6\u51FA\u9519",{error:l.message})}}},z=(l,N)=>{n(e,"WARN",`${l}\u6D41\u9519\u8BEF`,{error:N.message}),(N.code==="ERR_STREAM_WRITE_AFTER_END"||N.code==="EPIPE")&&O&&F()};try{try{await pe(i)}catch{}m=C.createWriteStream(W,{flags:"a"}),p=C.createWriteStream(U,{flags:"a"});const l="set +e ; pnpm dlx @xagi/dev-inject@latest install --framework ; pnpm dlx @xagi/vite-plugin-design-mode@next install ; set -e";u(m,l,"Main log"),u(p,l,"Temp log"),await new Promise(t=>{try{const a=Z("bash",["-lc",l],{cwd:i,env:{...process.env},stdio:["ignore","pipe","pipe"]});a.stdout.on("data",g=>{const R=g.toString();u(m,R,"Main log"),u(p,R,"Temp log")}),a.stderr.on("data",g=>{const R=g.toString();u(m,R,"Main log"),u(p,R,"Temp log")}),a.on("error",g=>{const R=`\u9884\u5904\u7406\u547D\u4EE4\u6267\u884C\u51FA\u9519\uFF08\u5FFD\u7565\u5E76\u7EE7\u7EED\u540E\u7EED\u6D41\u7A0B\uFF09: ${g.message}
|
|
4
|
+
`;u(m,R,"Main log",!0),u(p,R,"Temp log",!0),t()}),a.on("close",g=>{if(g!==0){const R=`Preprocessing command exit code is ${g} (ignore and continue subsequent process)
|
|
5
|
+
`;u(m,R,"\u4E3B\u65E5\u5FD7",!0),u(p,R,"\u4E34\u65F6\u65E5\u5FD7",!0)}t()})}catch(a){const g=`\u9884\u5904\u7406\u547D\u4EE4\u542F\u52A8\u5931\u8D25\uFF08\u5FFD\u7565\u5E76\u7EE7\u7EED\u540E\u7EED\u6D41\u7A0B\uFF09: ${a.message}
|
|
6
|
+
`;u(m,g,"Main log",!0),u(p,g,"Temp log",!0),t()}});try{await ge(s,e,i,{outStream:m,tempOutStream:p,safeWrite:u})}catch(t){const a=`Dependency installation failed: ${t.message}
|
|
7
|
+
`;throw u(m,a,"Main log",!0),u(p,a,"Temp log",!0),t}const N=`Start executing script: dev
|
|
8
|
+
`;u(m,N,"Main log"),u(p,N,"Temp log");const x=[],{extraArgs:v,envExtra:S,port:h}=await me.processExtraArgs({devScript:o,projectId:e,req:s});_=h,v&&v.length>0&&x.push(...v);const L=t=>`'${String(t).replace(/'/g,"'\\''")}'`,P=x&&x.length>0?x.map(L).join(" "):"";P&&n(e,"INFO","Generated extra arguments",{extraArgs:v,npmArgs:x,extraArgsEscaped:P});const K=t=>{if(!t||typeof t!="string")return!1;const a=t.trim();if(a.includes("/")||a.includes("\\"))return!1;const g=a.split(/\s+/)[0];return["vite","next","webpack","rollup","parcel","esbuild","tsc","ts-node","astro","svelte","remix","nuxt"].includes(g)};let T,A="";if(c){const a=((D,se)=>{if(!D||typeof D!="string")return D;let $=D;for(const B of se){const re=new RegExp(`${B.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&")}=\\S+`,"g");$=$.replace(re,"");const ne=new RegExp(`${B.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&")}s+([^s-][^s]*)`,"g");$=$.replace(ne,"");const ie=new RegExp(`${B.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&")}\b`,"g");$=$.replace(ie,"")}return $.replace(/\s{2,}/g," ").trim()})(o,["--host","--base"]),g=P?` ${P}`:"",R=K(a)?"npx":"";A=R?`${R} ${a}${g}`:`${a}${g}`,T=`exec ${A}`}else if(y){const t=P?` ${P}`:"",a=K(o)?"npx":"";A=a?`${a} ${o}${t}`:`${o}${t}`,T=`exec ${A}`}else A=`pnpm run dev${P?` -- ${P}`:""}`,T=`exec ${A}`;if(A){const t=`> ${A}
|
|
9
|
+
`;u(m,t,"Main log"),u(p,t,"Temp log")}try{n(e,"INFO","Start child process, sequentially execute preprocessing and start dev:",{command:T,cwd:i,devScript:o,extraArgs:v,envExtraKeys:Object.keys(S||{})})}catch{}d=Z("sh",["-c",T],{cwd:i,env:{PATH:process.env.PATH,NODE_ENV:"development",...S},detached:!0,stdio:["ignore","pipe","pipe"]}),m.on("error",t=>{n(e,"ERROR","Main log stream error",{error:t.message,code:t.code,path:W,destroyed:m.destroyed}),z("Main log",t)}),p.on("error",t=>{n(e,"ERROR","Temp log stream error",{error:t.message,code:t.code,path:U,destroyed:p.destroyed}),z("Temp log",t)}),d.stdout&&(d.stdout.on("data",t=>{const a=u(m,t,"Main log"),g=u(p,t,"Temp log");(!a||!g)&&n(e,"DEBUG","Log writing status",{mainWriteOk:a,tempWriteOk:g,mainDestroyed:m?m.destroyed:null,tempDestroyed:p?p.destroyed:null,streamsClosed:b})}),d.stdout.on("error",t=>{n(e,"WARN","Child process stdout error",{error:t.message})}),d.stdout.on("end",()=>{n(e,"INFO","\u5B50\u8FDB\u7A0Bstdout\u6D41\u5DF2\u7ED3\u675F",{pid:d.pid})})),d.stderr&&(d.stderr.on("data",t=>{const a=u(m,t,"Main log"),g=u(p,t,"Temp log");(!a||!g)&&n(e,"DEBUG","Log writing status(stderr)",{mainWriteOk:a,tempWriteOk:g,mainDestroyed:m?m.destroyed:null,tempDestroyed:p?p.destroyed:null,streamsClosed:b})}),d.stderr.on("error",t=>{n(e,"WARN","Child process stderr error",{error:t.message})}),d.stderr.on("end",()=>{n(e,"INFO","Child process stderr stream ended",{pid:d.pid})})),d.on("exit",(t,a)=>{O=!0,n(e,"INFO","Child process exited",{pid:d.pid,code:t,signal:a}),setTimeout(()=>{F()},200)}),d.on("error",t=>{n(e,"WARN","Child process error",{error:t.message}),O=!0,n(e,"ERROR","Child process startup failed",{pid:d.pid,error:t.message}),F()}),w.set(e,{pid:d.pid,logPath:W,startedAt:Date.now()}),d.unref(),T&&T.includes("exec ")&&await new Promise(t=>setTimeout(t,200)),n(e,"INFO","Using port pool allocated port and current process ID",{projectId:e,pid:d.pid,port:h});const Y=s&&s.query&&s.query.basePath||s&&s.body&&s.body.basePath||void 0,X=Y||"/",q=3e4,J=1e3,te=1500;let V=!1;const Q=Date.now();for(await new Promise(t=>setTimeout(t,J));Date.now()-Q<q&&(V=await Ee(e,h,Y,{timeoutMs:te}),!V);)await new Promise(t=>setTimeout(t,J));if(V)n(e,"INFO","Project accessibility verification passed",{port:h,basePath:X,elapsedMs:Date.now()-Q});else{const t=Math.round(q/1e3);n(e,"WARN","Development server is not accessible within the limited time",{port:h,pid:d.pid,basePath:X,waitSeconds:t})}const M=w.get(e);return M&&(M.port=h,M.pid=d.pid,w.set(e,M)),n(e,"INFO","Development server startup successfully",{projectId:e,pid:d.pid,port:h}),{pid:d.pid,port:h}}catch(l){throw _&&(fe.release(String(e)),n(e,"INFO","Startup failed, port released",{port:_,error:l.message})),l}finally{(O||!d||!d.pid)&&F()}}async function Se(s,e){const i=Number(e);if(!Number.isFinite(i))return!1;if(!k(i))return n(s,"INFO","Process does not exist",{pid:i}),w.delete(s),!0;let o=!1,E="";try{process.kill(-i),o=!0,E="Process group",n(s,"INFO","\u901A\u8FC7\u8FDB\u7A0B\u7EC4\u6740\u6B7B\u8FDB\u7A0B",{pid:i})}catch(c){c&&(c.code==="ESRCH"||c.errno==="ESRCH")?(o=!1,E="Process group(not exist)",n(s,"INFO","Process group does not exist, back to try single process kill",{pid:i})):n(s,"WARN","Failed to kill process group",{pid:i,error:c.message})}if(!o)try{process.kill(i),o=!0,E="\u5355\u4E2A\u8FDB\u7A0B",n(s,"INFO","Kill process through single process",{pid:i})}catch(c){c&&(c.code==="ESRCH"||c.errno==="ESRCH")?(o=!0,E="Single process(not exist)",n(s,"INFO","Process does not exist,\u89C6\u4E3A\u5DF2\u505C\u6B62",{pid:i})):n(s,"ERROR","Failed to kill process",{pid:i,error:c.message})}return o&&(await new Promise(c=>setTimeout(c,100)),k(i)?(n(s,"WARN","Process still exists, kill may have failed",{pid:i,method:E}),!1):(n(s,"INFO","Process stopped successfully",{pid:i,method:E}),w.delete(s),!0))}function k(s){try{return process.kill(s,0),!0}catch(e){return e&&(e.code==="EPERM"||e.code==="EACCES")?!0:(e&&e.code==="ESRCH",!1)}}async function Ae(s,e){let i=0;const o=ee.DEV_SERVER_STOP_MAX_ATTEMPTS;for(;k(e)&&i<o;)await new Promise(c=>setTimeout(c,ee.DEV_SERVER_STOP_CHECK_INTERVAL)),i++;return{stopped:!k(e),attempts:i}}function ve(){return Array.from(w.entries()).map(([e,i])=>({projectId:e,pid:i.pid,type:i.type,startedAt:i.startedAt,port:i.port}))}export{Re as getRunningProcess,he as setRunningProcess,ye as deleteRunningProcess,xe as isProjectStarting,Pe as addStartingProject,we as removeStartingProject,Ne as startDev_NonBlocking,Se as killProcess,k as isProcessRunning,Ae as waitForProcessStop,ve as listRunningProcesses};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import c from"path";import l from"fs";import{log as r}from"../log/logUtils.js";import{BusinessError as v,FileError as g,ResourceError as S}from"../error/errorHandler.js";import{addStartingProject as p,removeStartingProject as P,startDev_NonBlocking as w}from"./processManager.js";import"../error/errorCodes.js";import{stopDevServer as N}from"./stopDevUtils.js";import{removeNodeModules as y}from"../buildDependency/dependencyManager.js";import{createPnpmNpmrc as F}from"../common/npmrcUtils.js";import{extractIsolationContext as d,resolveProjectPath as h}from"../common/projectPathUtils.js";async function O(e,t){r(t,"INFO","Start restarting development server",{projectId:t,requestId:e.requestId});const f=d(e?.query||{}),s=h(t,f),o=c.join(s,"package.json");if(!l.existsSync(o))throw r(t,"WARN","Project missing package.json file",{projectId:t,requestId:e.requestId}),new S("Project missing package.json file",{projectId:t,projectPath:s});let n;try{n=JSON.parse(l.readFileSync(o,"utf8"))}catch(i){throw new g("package.json file format error",{projectId:t,jsonFilePath:o,originalError:i.message})}const a=n.scripts.dev;if(!a)throw r(t,"WARN","Project missing dev script",{projectId:t,requestId:e.requestId}),new v("Project missing dev script",{projectId:t});p(t);try{const i=e.query.pid;await N(e,t,i,{strict:!1,waitForStop:!0}),r(t,"INFO","Start deleting node_modules and lock file",{projectId:t,requestId:e.requestId}),await y(s,t),r(t,"INFO","Create .npmrc file",{projectId:t,requestId:e.requestId}),await F(s,t),r(t,"INFO","Start starting dev server",{projectId:t,requestId:e.requestId});const{pid:m,port:u}=await w({req:e,projectId:t,projectPath:s,devScript:a});return r(t,"INFO","Dev server restart completed",{projectId:t,pid:m,port:u,requestId:e.requestId}),{success:!0,message:"Development server restart successfully",projectId:t,pid:m,port:u}}finally{P(t)}}export{O as restartDevServer};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import v from"path";import i from"fs";import{log as r}from"../log/logUtils.js";import{BusinessError as P,FileError as y,ResourceError as L}from"../error/errorHandler.js";import"../error/errorCodes.js";import{addStartingProject as N,removeStartingProject as w,startDev_NonBlocking as A}from"./processManager.js";import{removeNodeModules as D}from"../buildDependency/dependencyManager.js";import{extractIsolationContext as E,resolveProjectPath as _}from"../common/projectPathUtils.js";async function k(s,e){N(e);try{r(e,"INFO","Start starting development server",{projectId:e,requestId:s.requestId});const d=E(s?.query||{}),o=_(e,d),a=v.join(o,"package.json");if(!i.existsSync(a))throw r(e,"WARN","Project missing package.json file",{projectId:e,requestId:s.requestId}),new L("Project missing package.json file",{projectId:e,projectPath:o});let u;try{u=JSON.parse(i.readFileSync(a,"utf8"))}catch(t){throw new y("package.json file format error",{projectId:e,jsonFilePath:a,originalError:t.message})}const m=u.scripts.dev;if(!m)throw r(e,"WARN","Project missing dev script",{projectId:e,requestId:s.requestId}),new P("Project missing dev script, please add dev script in package.json",{projectId:e});try{if(process.platform==="linux"){const c=typeof process.report?.getReport=="function"?process.report.getReport():null,p=c&&c.header&&c.header.glibcVersionRuntime,l=!p,g=v.join(o,"node_modules",".pnpm");if(i.existsSync(g)){const f=await i.promises.readdir(g,{withFileTypes:!0}),S=f.some(n=>n.isDirectory()&&(n.name||"").includes("@rollup+rollup-linux-x64-gnu")),x=f.some(n=>n.isDirectory()&&(n.name||"").includes("@rollup+rollup-linux-x64-musl"));(l&&S||!l&&x)&&(r(e,"WARN","Detected Rollup native package does not match libc, clean dependencies and reinstall",{projectId:e,isMusl:l,glibcVersion:p||null}),await D(o,e))}}}catch(t){r(e,"WARN","Linux native package matching detection failed (ignore continue)",{error:t&&t.message})}try{process.env.ROLLUP_WASM=process.env.ROLLUP_WASM||"1",process.env.ROLLUP_DISABLE_NATIVE=process.env.ROLLUP_DISABLE_NATIVE||"1"}catch{}r(e,"INFO","Start executing dev script in non-blocking mode",{projectId:e,requestId:s.requestId});const{pid:h,port:R}=await A({req:s,projectId:e,projectPath:o,devScript:m});return{success:!0,message:"Development server started",projectId:e,pid:h,port:R}}finally{w(e)}}export{k as startDevServer};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import n from"fs";import i from"path";const m={agents:".agents",claudecode:".claude",opencode:".opencode",codex:".codex"},u=Object.keys(m),g="agents";async function d(s){n.existsSync(s)&&await n.promises.rm(s,{recursive:!0,force:!0})}async function a(s){n.existsSync(s)||await n.promises.mkdir(s,{recursive:!0})}async function p(s,t){if((await n.promises.lstat(s)).isDirectory()){await a(t);const e=await n.promises.readdir(s);for(const r of e)await p(i.join(s,r),i.join(t,r))}else await a(i.dirname(t)),await n.promises.copyFile(s,t)}async function j(s){const t=i.join(s,m[g]),o=i.join(t,"skills"),e=i.join(t,"agents");return await a(t),await a(o),await a(e),{rootDir:t,skillsDir:o,agentsDir:e,agentTypes:u}}async function T(s){const t=await j(s);for(const o of u){if(o===g)continue;const e=i.join(s,m[o]),r=i.join(e,"skills"),y=i.join(e,"agents");if(await a(e),await d(r),await a(r),n.existsSync(t.skillsDir)){const f=await n.promises.readdir(t.skillsDir,{withFileTypes:!0});for(const c of f){const w=i.join(t.skillsDir,c.name),l=i.join(r,c.name);await p(w,l)}}if(await d(y),await a(y),n.existsSync(t.agentsDir)){const f=await n.promises.readdir(t.agentsDir,{withFileTypes:!0});for(const c of f){const w=i.join(t.agentsDir,c.name),l=i.join(y,c.name);await p(w,l)}}}return{agentTypes:u}}export{j as ensurePrimaryAgentDirs,T as syncAgents};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import o from"path";import i from"../../appConfig/index.js";function t(n){return n==null?"":String(n).trim()}function I(n={}){return{tenantId:t(n.tenantId),spaceId:t(n.spaceId),isolationType:t(n.isolationType)}}function a(n={}){const e=t(n.tenantId),r=t(n.spaceId),c=t(n.isolationType);return!!(e&&r&&c)}function d(n,e={}){const r=t(n);return r?a(e)?o.join(i.PROJECT_SOURCE_DIR,t(e.tenantId),t(e.spaceId),r):o.join(i.PROJECT_SOURCE_DIR,r):o.join(i.PROJECT_SOURCE_DIR,"")}export{I as extractIsolationContext,a as shouldUseIsolationPath,d as resolveProjectPath};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"fs";import a from"path";import M from"../../appConfig/index.js";import{extractZip as W}from"../common/zipUtils.js";import{ValidationError as g,SystemError as j,FileError as _}from"../error/errorHandler.js";import{log as o}from"../log/logUtils.js";async function A(t="computer"){const i=M.COMPUTER_WORKSPACE_DIR;if(!i)throw new g("COMPUTER_WORKSPACE_DIR configuration does not exist",{field:"COMPUTER_WORKSPACE_DIR"});return e.existsSync(i)||(await e.promises.mkdir(i,{recursive:!0}),o(t,"INFO","Created user workspace root directory",{workspaceRoot:i})),i}async function v(t,i){const r=a.join(t,i);if(e.existsSync(r)&&(await e.promises.lstat(r)).isDirectory())return r;const y=await e.promises.readdir(t,{withFileTypes:!0});for(const s of y){if(!s.isDirectory())continue;const f=a.join(t,s.name),w=a.join(f,i);if(e.existsSync(w)&&(await e.promises.lstat(w)).isDirectory())return w}return null}async function V(t){return v(t,"skills")}const $=".dynamic_add.lock";function C(t){const i=a.join(t,$);return e.existsSync(i)&&e.statSync(i).isFile()}async function D(t){e.existsSync(t)&&await e.promises.rm(t,{recursive:!0,force:!0})}async function R(t,i){try{await e.promises.rename(t,i)}catch(r){if(r.code==="EXDEV"){async function y(s,f){if((await e.promises.lstat(s)).isDirectory()){await e.promises.mkdir(f,{recursive:!0});const S=await e.promises.readdir(s);for(const k of S)await y(a.join(s,k),a.join(f,k))}else await e.promises.copyFile(s,f)}await y(t,i),await e.promises.rm(t,{recursive:!0,force:!0})}else throw r}}async function T(t,i,r){const y=Date.now(),s=`computer:${t}:${i}`;if(!t)throw new g("userId cannot be empty",{field:"userId"});if(!i)throw new g("cId cannot be empty",{field:"cId"});const f=await A(s),w=a.join(f,String(t),String(i),".tmp"),S=a.join(f,String(t),String(i)),k=a.join(S,".claude"),d=a.join(k,"skills"),x=a.join(k,"agents");e.existsSync(S)||await e.promises.mkdir(S,{recursive:!0}),e.existsSync(k)||await e.promises.mkdir(k,{recursive:!0});const u=a.join(w,`preserved_skills_${Date.now()}_${Math.round(Math.random()*1e6)}`);if(e.existsSync(d)){const l=(await e.promises.readdir(d,{withFileTypes:!0})).filter(p=>p.isDirectory()&&C(a.join(d,p.name)));if(l.length>0){await e.promises.mkdir(u,{recursive:!0});for(const p of l){const h=a.join(d,p.name),F=a.join(u,p.name);await R(h,F)}o(s,"INFO","\u4FDD\u7559\u542B .dynamic_add.lock \u7684 skill",{preserved:l.map(p=>p.name)})}}if(await D(d),await D(x),e.existsSync(u)){await e.promises.mkdir(d,{recursive:!0});const n=await e.promises.readdir(u,{withFileTypes:!0});for(const l of n)if(l.isDirectory()){const p=a.join(u,l.name),h=a.join(d,l.name);await R(p,h)}await D(u)}const m=e.existsSync(d),N=e.existsSync(x);if(o(s,"INFO","Deleted old skills and agents directories completed",{userId:t,cId:i,targetSkillsDir:d,targetAgentsDir:x,skillsExists:m,agentsExists:N}),!r)return o(s,"INFO","Created workspace (no uploaded file, no skills and agents)",{userId:t,cId:i,workspaceRoot:f,claudeDir:k,skillsDir:null,agentsDir:null,elapsedMs:Date.now()-y}),{message:"Workspace created (no uploaded file, no skills and agents)",workspaceRoot:f};if(!r.path)throw new g("Uploaded file has no valid path",{field:"file.path"});if(a.extname(r.originalname||r.filename||"").toLowerCase()!==".zip")throw new g("Only zip files are supported",{field:"file",originalName:r.originalname});o(s,"DEBUG","Start processing uploaded zip file",{userId:t,cId:i,workspaceRoot:f,tempZipPath:r.path});const c=a.join(w,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{e.existsSync(w)||await e.promises.mkdir(w,{recursive:!0}),await e.promises.mkdir(c,{recursive:!0}),o(s,"DEBUG","Start extracting zip file",{extractRoot:c}),await W(r.path,c),o(s,"DEBUG","Zip file extracted successfully",{extractRoot:c});const n=await v(c,"skills"),l=await v(c,"agents"),p=[];if(n){await e.promises.mkdir(d,{recursive:!0});const F=await e.promises.readdir(n,{withFileTypes:!0});for(const z of F){if(!z.isDirectory())continue;const P=a.join(n,z.name),O=a.join(d,z.name);e.existsSync(O)&&await D(O),await R(P,O)}p.push("skills"),o(s,"INFO","skills updated to workspace",{userId:t,cId:i,workspaceRoot:f,claudeDir:k,targetSkillsDir:d})}else o(s,"INFO","skills directory not found in zip, skipping",{userId:t,cId:i,extractRoot:c});l?(await R(l,x),p.push("agents"),o(s,"INFO","agents updated to workspace",{userId:t,cId:i,workspaceRoot:f,claudeDir:k,targetAgentsDir:x})):o(s,"INFO","agents directory not found in zip, skipping",{userId:t,cId:i,extractRoot:c}),p.length===0&&o(s,"WARN","skills and agents directories not found in zip",{userId:t,cId:i,extractRoot:c});const h=p.length>0?`Workspace created successfully, ${p.join(" and ")} updated`:"Workspace created successfully (skills and agents directories not found in zip)";return o(s,"INFO",h,{userId:t,cId:i,updatedDirs:p,elapsedMs:Date.now()-y}),{message:h,workspaceRoot:f}}catch(n){throw o(s,"ERROR","Failed to process uploaded zip file",{userId:t,cId:i,error:n.message,elapsedMs:Date.now()-y}),n instanceof g||n instanceof _||n instanceof j?n:new j(`Failed to create workspace: ${n.message}`,{userId:t,cId:i})}finally{try{e.existsSync(c)&&await e.promises.rm(c,{recursive:!0,force:!0})}catch(n){o(s,"WARN","Failed to clean up temporary extracted zip",{extractRoot:c,error:n.message})}try{r&&r.path&&e.existsSync(r.path)&&await e.promises.unlink(r.path)}catch(n){o(s,"WARN","Failed to clean up uploaded zip file",{tempZipPath:r?.path,error:n.message})}}}async function U(t,i,r){const y=Date.now(),s=`computer:${t}:${i}`;if(!t)throw new g("userId cannot be empty",{field:"userId"});if(!i)throw new g("cId cannot be empty",{field:"cId"});if(!r||!r.path)throw new g("Uploaded file has no valid path",{field:"file.path"});if(a.extname(r.originalname||r.filename||"").toLowerCase()!==".zip")throw new g("Only zip files are supported",{field:"file",originalName:r?.originalname});const w=await A(s),S=a.join(w,String(t),String(i),".tmp"),k=a.join(w,String(t),String(i)),d=a.join(k,".claude"),x=a.join(d,"skills"),u=a.join(S,`skill_push_${Date.now()}_${Math.round(Math.random()*1e6)}`);try{e.existsSync(k)||await e.promises.mkdir(k,{recursive:!0}),e.existsSync(d)||await e.promises.mkdir(d,{recursive:!0}),e.existsSync(x)||await e.promises.mkdir(x,{recursive:!0}),e.existsSync(S)||await e.promises.mkdir(S,{recursive:!0}),await e.promises.mkdir(u,{recursive:!0}),o(s,"DEBUG","Start extracting skill zip file",{extractRoot:u}),await W(r.path,u),o(s,"DEBUG","Skill zip file extracted successfully",{extractRoot:u});const m=await v(u,"skills");if(!m)return o(s,"WARN","skills directory not found in zip",{userId:t,cId:i,extractRoot:u}),{message:"skills directory not found in zip",workspaceRoot:w,updatedSkills:[]};const E=(await e.promises.readdir(m,{withFileTypes:!0})).filter(l=>l.isDirectory()&&!l.name.startsWith("."));if(E.length===0)return o(s,"WARN","skills directory in zip has no skill subdirectories",{userId:t,cId:i,skillsDir:m}),{message:"skills directory in zip has no skill subdirectories",workspaceRoot:w,updatedSkills:[]};const c=[];for(const l of E){const p=a.join(m,l.name),h=a.join(x,l.name);e.existsSync(h)&&await D(h),await R(p,h),c.push(l.name),o(s,"INFO","skill pushed to workspace",{userId:t,cId:i,skillName:l.name,destSkillPath:h})}const n=c.length>0?`Pushed ${c.length} skills: ${c.join(", ")}`:"skills directory not found in zip";return o(s,"INFO",n,{userId:t,cId:i,updatedSkills:c,elapsedMs:Date.now()-y}),{message:n,workspaceRoot:w,updatedSkills:c}}catch(m){throw o(s,"ERROR","Failed to push skill to workspace",{userId:t,cId:i,error:m.message,elapsedMs:Date.now()-y}),m instanceof g||m instanceof _||m instanceof j?m:new j(`Failed to push skill to workspace: ${m.message}`,{userId:t,cId:i})}finally{try{e.existsSync(u)&&await e.promises.rm(u,{recursive:!0,force:!0})}catch(m){o(s,"WARN","Failed to clean up temporary extracted zip",{extractRoot:u,error:m.message})}try{r&&r.path&&e.existsSync(r.path)&&await e.promises.unlink(r.path)}catch(m){o(s,"WARN","Failed to clean up uploaded zip file",{tempZipPath:r?.path,error:m.message})}}}export{T as createWorkspace,U as pushSkillsToWorkspace};
|
|
1
|
+
import t from"fs";import o from"path";import J from"../../appConfig/index.js";import{extractZip as M}from"../common/zipUtils.js";import{ValidationError as S,SystemError as U,FileError as C}from"../error/errorHandler.js";import{log as n}from"../log/logUtils.js";import{ensurePrimaryAgentDirs as G,syncAgents as B}from"../common/AgentWorkspaceUtils.js";function K(e){if(!e)return[];if(Array.isArray(e))return e.map(i=>typeof i=="string"?i.trim():"").filter(Boolean);if(typeof e=="string"){const i=e.trim();if(!i)return[];try{const a=JSON.parse(i);if(Array.isArray(a))return a.map(f=>typeof f=="string"?f.trim():"").filter(Boolean)}catch{}return[i]}return[]}async function L(e,i,a){let f;try{f=await fetch(e)}catch(E){throw new C(`Failed to download skill zip from url: ${e}`,{url:e,reason:E.message})}if(!f.ok)throw new C(`Failed to download skill zip from url: ${e}`,{url:e,status:f.status,statusText:f.statusText});const u=f.headers.get("content-type")||"";u&&!u.includes("zip")&&!u.includes("octet-stream")&&n(a,"WARN","Downloaded skill url content-type is not typical zip",{url:e,contentType:u});const s=await f.arrayBuffer(),y=Buffer.from(s);await t.promises.writeFile(i,y)}async function V(e="computer"){const i=J.COMPUTER_WORKSPACE_DIR;if(!i)throw new S("COMPUTER_WORKSPACE_DIR configuration does not exist",{field:"COMPUTER_WORKSPACE_DIR"});return t.existsSync(i)||(await t.promises.mkdir(i,{recursive:!0}),n(e,"INFO","Created user workspace root directory",{workspaceRoot:i})),i}async function Z(e,i){const a=o.join(e,i);if(t.existsSync(a)&&(await t.promises.lstat(a)).isDirectory())return a;const f=await t.promises.readdir(e,{withFileTypes:!0});for(const u of f){if(!u.isDirectory())continue;const s=o.join(e,u.name),y=o.join(s,i);if(t.existsSync(y)&&(await t.promises.lstat(y)).isDirectory())return y}return null}async function ot(e){return Z(e,"skills")}const X=".dynamic_add.lock";function Y(e){const i=o.join(e,X);return t.existsSync(i)&&t.statSync(i).isFile()}async function A(e){t.existsSync(e)&&await t.promises.rm(e,{recursive:!0,force:!0})}async function T(e,i){try{await t.promises.rename(e,i)}catch(a){if(a.code==="EXDEV"){async function f(u,s){if((await t.promises.lstat(u)).isDirectory()){await t.promises.mkdir(s,{recursive:!0});const E=await t.promises.readdir(u);for(const N of E)await f(o.join(u,N),o.join(s,N))}else await t.promises.copyFile(u,s)}await f(e,i),await t.promises.rm(e,{recursive:!0,force:!0})}else throw a}}async function q(e,i,a,f){const u=Date.now(),s=`computer:${e}:${i}`,y=K(f),E=[],N=[],P=new Set;if(!e)throw new S("userId cannot be empty",{field:"userId"});if(!i)throw new S("cId cannot be empty",{field:"cId"});const x=await V(s),D=o.join(x,String(e),String(i),".tmp"),_=o.join(x,String(e),String(i)),{skillsDir:g,agentsDir:l,agentTypes:R}=await G(_);t.existsSync(_)||await t.promises.mkdir(_,{recursive:!0});const v=o.join(D,`preserved_skills_${Date.now()}_${Math.round(Math.random()*1e6)}`);if(t.existsSync(g)){const p=(await t.promises.readdir(g,{withFileTypes:!0})).filter(m=>m.isDirectory()&&Y(o.join(g,m.name)));if(p.length>0){await t.promises.mkdir(v,{recursive:!0});for(const m of p){const d=o.join(g,m.name),h=o.join(v,m.name);await T(d,h)}n(s,"INFO","\u4FDD\u7559\u542B .dynamic_add.lock \u7684 skill",{preserved:p.map(m=>m.name)})}}if(await A(g),await A(l),await t.promises.mkdir(g,{recursive:!0}),await t.promises.mkdir(l,{recursive:!0}),t.existsSync(v)){const r=await t.promises.readdir(v,{withFileTypes:!0});for(const p of r)if(p.isDirectory()){const m=o.join(v,p.name),d=o.join(g,p.name);await T(m,d)}await A(v)}const k=t.existsSync(g),F=t.existsSync(l);if(n(s,"INFO","Deleted old skills and agents directories completed",{userId:e,cId:i,targetSkillsDir:g,targetAgentsDir:l,agentTypes:R,skillsExists:k,agentsExists:F}),!a&&y.length===0)return await B(_),n(s,"INFO","Created workspace (no uploaded file, no skills and agents)",{userId:e,cId:i,workspaceRoot:x,skillsDir:null,agentsDir:null,agentTypes:R,elapsedMs:Date.now()-u}),{message:"Workspace created (no uploaded file, no skills and agents)",workspaceRoot:x};if(a){if(!a.path)throw new S("Uploaded file has no valid path",{field:"file.path"});if(o.extname(a.originalname||a.filename||"").toLowerCase()!==".zip")throw new S("Only zip files are supported",{field:"file",originalName:a.originalname})}n(s,"DEBUG","Start processing workspace resources",{userId:e,cId:i,workspaceRoot:x,hasUploadedZip:!!a,skillUrlsCount:y.length});try{t.existsSync(D)||await t.promises.mkdir(D,{recursive:!0});const r=[];if(a){const d=o.join(D,`skill_extract_${Date.now()}_${Math.round(Math.random()*1e6)}`);N.push(d),await t.promises.mkdir(d,{recursive:!0}),n(s,"DEBUG","Start extracting uploaded zip file",{extractRoot:d}),await M(a.path,d),n(s,"DEBUG","Uploaded zip file extracted successfully",{extractRoot:d});const h=await Z(d,"skills"),j=await Z(d,"agents");if(h){await t.promises.mkdir(g,{recursive:!0});const c=await t.promises.readdir(h,{withFileTypes:!0});for(const z of c){if(!z.isDirectory())continue;const $=o.join(h,z.name),O=o.join(g,z.name);t.existsSync(O)&&await A(O),await T($,O),P.add(z.name)}r.push("skills"),n(s,"INFO","skills updated to workspace",{userId:e,cId:i,workspaceRoot:x,targetSkillsDir:g,agentTypes:R})}else n(s,"INFO","skills directory not found in uploaded zip, skipping",{userId:e,cId:i,extractRoot:d});j?(await A(l),await T(j,l),r.push("agents"),n(s,"INFO","agents updated to workspace",{userId:e,cId:i,workspaceRoot:x,targetAgentsDir:l,agentTypes:R})):n(s,"INFO","agents directory not found in uploaded zip, skipping",{userId:e,cId:i,extractRoot:d})}for(let d=0;d<y.length;d+=1){const h=y[d],j=o.join(D,`skill_url_${Date.now()}_${d}_${Math.round(Math.random()*1e6)}.zip`);E.push(j);const c=o.join(D,`skill_url_extract_${Date.now()}_${d}_${Math.round(Math.random()*1e6)}`);N.push(c),await t.promises.mkdir(c,{recursive:!0}),n(s,"INFO","Start download skill zip from url",{userId:e,cId:i,skillUrl:h}),await L(h,j,s),n(s,"INFO","Skill zip downloaded, start extracting",{userId:e,cId:i,skillUrl:h,downloadedZipPath:j}),await M(j,c);const $=(await t.promises.readdir(c,{withFileTypes:!0})).filter(w=>w.isDirectory()&&!w.name.startsWith(".")),b=$.find(w=>w.name==="skills")?(await t.promises.readdir(o.join(c,"skills"),{withFileTypes:!0})).filter(w=>w.isDirectory()&&!w.name.startsWith(".")).map(w=>({name:w.name,sourcePath:o.join(c,"skills",w.name)})):$.map(w=>({name:w.name,sourcePath:o.join(c,w.name)}));if(b.length===0){n(s,"WARN","No skill directory found after extracting skill url zip",{userId:e,cId:i,skillUrl:h,extractRoot:c});continue}await t.promises.mkdir(g,{recursive:!0});for(const w of b){const W=o.join(g,w.name);t.existsSync(W)&&await A(W),await T(w.sourcePath,W),P.add(w.name),n(s,"INFO","Skill from url updated to workspace",{userId:e,cId:i,skillUrl:h,skillName:w.name,destSkillPath:W})}r.includes("skills")||r.push("skills")}r.length===0&&n(s,"WARN","No valid skills or agents directories found",{userId:e,cId:i,hasUploadedZip:!!a,skillUrlsCount:y.length});const p=Array.from(P),m=r.length>0?`Workspace created successfully, ${r.join(" and ")} updated`:"Workspace created successfully (skills and agents directories not found)";return n(s,"INFO",m,{userId:e,cId:i,updatedDirs:r,updatedSkills:p,agentTypes:R,elapsedMs:Date.now()-u}),await B(_),{message:m,workspaceRoot:x,updatedSkills:p}}catch(r){throw n(s,"ERROR","Failed to process workspace resources",{userId:e,cId:i,error:r.message,elapsedMs:Date.now()-u}),r instanceof S||r instanceof C||r instanceof U?r:new U(`Failed to create workspace: ${r.message}`,{userId:e,cId:i})}finally{try{for(const r of N)t.existsSync(r)&&await t.promises.rm(r,{recursive:!0,force:!0})}catch(r){n(s,"WARN","Failed to clean up temporary extracted zip",{error:r.message})}for(const r of E)try{t.existsSync(r)&&await t.promises.unlink(r)}catch(p){n(s,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:r,error:p.message})}try{a&&a.path&&t.existsSync(a.path)&&await t.promises.unlink(a.path)}catch(r){n(s,"WARN","Failed to clean up uploaded zip file",{tempZipPath:a?.path,error:r.message})}}}async function H(e,i,a,f){const u=Date.now(),s=`computer:${e}:${i}`,y=K(f),E=[],N=[];if(!e)throw new S("userId cannot be empty",{field:"userId"});if(!i)throw new S("cId cannot be empty",{field:"cId"});if(!a&&y.length===0)throw new S("file or skillUrls cannot both be empty",{field:"file|skillUrls"});if(a){if(!a.path)throw new S("Uploaded file has no valid path",{field:"file.path"});if(o.extname(a.originalname||a.filename||"").toLowerCase()!==".zip")throw new S("Only zip files are supported",{field:"file",originalName:a?.originalname})}const P=await V(s),x=o.join(P,String(e),String(i),".tmp"),D=o.join(P,String(e),String(i)),{skillsDir:_,agentTypes:g}=await G(D);try{t.existsSync(D)||await t.promises.mkdir(D,{recursive:!0}),t.existsSync(_)||await t.promises.mkdir(_,{recursive:!0}),t.existsSync(x)||await t.promises.mkdir(x,{recursive:!0});const l=[],R=new Set;if(a){const k=o.join(x,`skill_push_${Date.now()}_${Math.round(Math.random()*1e6)}`);N.push(k),await t.promises.mkdir(k,{recursive:!0}),n(s,"DEBUG","Start extracting skill zip file",{extractRoot:k}),await M(a.path,k),n(s,"DEBUG","Skill zip file extracted successfully",{extractRoot:k});const F=await Z(k,"skills");if(!F)n(s,"WARN","skills directory not found in uploaded zip",{userId:e,cId:i,extractRoot:k});else{const p=(await t.promises.readdir(F,{withFileTypes:!0})).filter(m=>m.isDirectory()&&!m.name.startsWith("."));if(p.length===0)n(s,"WARN","skills directory in uploaded zip has no skill subdirectories",{userId:e,cId:i,skillsDir:F});else for(const m of p){const d=o.join(F,m.name),h=o.join(_,m.name);t.existsSync(h)&&await A(h),await T(d,h),R.add(m.name),n(s,"INFO","skill pushed to workspace from uploaded zip",{userId:e,cId:i,skillName:m.name,destSkillPath:h})}}}for(let k=0;k<y.length;k+=1){const F=y[k],r=o.join(x,`skill_push_url_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}.zip`);E.push(r);const p=o.join(x,`skill_push_url_extract_${Date.now()}_${k}_${Math.round(Math.random()*1e6)}`);N.push(p),await t.promises.mkdir(p,{recursive:!0}),n(s,"INFO","Start download skill zip for push from url",{userId:e,cId:i,skillUrl:F}),await L(F,r,s),await M(r,p);const d=(await t.promises.readdir(p,{withFileTypes:!0})).filter(c=>c.isDirectory()&&!c.name.startsWith(".")),j=d.find(c=>c.name==="skills")?(await t.promises.readdir(o.join(p,"skills"),{withFileTypes:!0})).filter(c=>c.isDirectory()&&!c.name.startsWith(".")).map(c=>({name:c.name,sourcePath:o.join(p,"skills",c.name)})):d.map(c=>({name:c.name,sourcePath:o.join(p,c.name)}));if(j.length===0){n(s,"WARN","No skill directory found after extracting push skill url zip",{userId:e,cId:i,skillUrl:F,extractRoot:p});continue}for(const c of j){const z=o.join(_,c.name);t.existsSync(z)&&await A(z),await T(c.sourcePath,z),R.add(c.name),n(s,"INFO","skill pushed to workspace from url zip",{userId:e,cId:i,skillUrl:F,skillName:c.name,destSkillPath:z})}}for(const k of R)l.push(k);const v=l.length>0?`Pushed ${l.length} skills: ${l.join(", ")}`:"No valid skill directories found in file or skillUrls";return n(s,"INFO",v,{userId:e,cId:i,updatedSkills:l,agentTypes:g,elapsedMs:Date.now()-u}),await B(D),{message:v,workspaceRoot:P,updatedSkills:l}}catch(l){throw n(s,"ERROR","Failed to push skill to workspace",{userId:e,cId:i,error:l.message,elapsedMs:Date.now()-u}),l instanceof S||l instanceof C||l instanceof U?l:new U(`Failed to push skill to workspace: ${l.message}`,{userId:e,cId:i})}finally{try{for(const l of N)t.existsSync(l)&&await t.promises.rm(l,{recursive:!0,force:!0})}catch(l){n(s,"WARN","Failed to clean up temporary extracted zip",{error:l.message})}for(const l of E)try{t.existsSync(l)&&await t.promises.unlink(l)}catch(R){n(s,"WARN","Failed to clean up downloaded skill zip file",{downloadedZipPath:l,error:R.message})}try{a&&a.path&&t.existsSync(a.path)&&await t.promises.unlink(a.path)}catch(l){n(s,"WARN","Failed to clean up uploaded zip file",{tempZipPath:a?.path,error:l.message})}}}export{q as createWorkspace,H as pushSkillsToWorkspace};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import s from"fs";import m from"path";import
|
|
1
|
+
import s from"fs";import m from"path";import{log as t}from"../log/logUtils.js";import{copyDirectoryFiltered as $}from"./backupUtils.js";import{ValidationError as l,BusinessError as y,SystemError as x}from"../error/errorHandler.js";import{createPnpmNpmrc as E}from"../common/npmrcUtils.js";import{resolveProjectPath as w}from"../common/projectPathUtils.js";async function F(e,i,c={}){if(!e)throw new l("Source project ID cannot be empty",{field:"sourceProjectId"});if(!i)throw new l("Target project ID cannot be empty",{field:"targetProjectId"});const u=c.sourceIsolationContext||{},h=c.targetIsolationContext||{},a=w(e,u),r=w(i,h);if(!s.existsSync(a))throw new y(`Source project ${e} does not exist`,{sourceProjectId:e,sourceProjectPath:a});if(s.existsSync(r))throw new y(`Target project ${i} already exists`,{targetProjectId:i,targetProjectPath:r});try{t(i,"INFO",`Start copying project from ${e} to ${i}`,{sourceProjectId:e,targetProjectId:i}),s.mkdirSync(r,{recursive:!0}),t(i,"INFO",`Target project directory created successfully: ${r}`,{targetProjectId:i});const o=await s.promises.readdir(a,{withFileTypes:!0});for(const n of o){const f=m.join(a,n.name),p=m.join(r,n.name);n.isDirectory()?(await s.promises.mkdir(p,{recursive:!0}),await $(f,p)):n.isFile()&&(await s.promises.mkdir(m.dirname(p),{recursive:!0}),await s.promises.copyFile(f,p))}return t(i,"INFO",`Project copied successfully: ${i}`,{sourceProjectId:e,targetProjectId:i}),await E(r,i),{success:!0,message:`Project ${e} successfully copied to ${i}`,sourceProjectId:e,targetProjectId:i,targetProjectPath:r}}catch(o){if(t(i,"ERROR",`Copy project failed: ${o.message}`,{sourceProjectId:e,targetProjectId:i}),s.existsSync(r))try{await s.promises.rm(r,{recursive:!0,force:!0}),t(i,"INFO","Copy failed, target project directory cleaned",{targetProjectId:i})}catch(n){t(i,"ERROR",`Clean target project directory failed: ${n.message}`,{targetProjectId:i,originalError:n.message})}throw o.isOperational?o:new x(`Copy project failed: ${o.message}`,{sourceProjectId:e,targetProjectId:i,sourceProjectPath:a,targetProjectPath:r,originalError:o.message})}}export{F as copyProject};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import i from"fs";import s from"path";import{log as a}from"../log/logUtils.js";function u(t){try{const n=s.join(t,"package.json");if(i.existsSync(n)){const r=JSON.parse(i.readFileSync(n,"utf-8")),e={...r.dependencies,...r.devDependencies};if(e.react||e["react-dom"])return"react";if(e.vue||e["vue-router"]||e["@vue/cli-service"]){const o=[e.vue,e["vue-router"],e["@vue/cli-service"]];for(const c of o){if(typeof c!="string")continue;const f=d(c);if(Number.isFinite(f))return`vue${f}`}return"vue"}}return"other"}catch(n){return a(null,"WARN",`Detect frontend framework failed: ${n.message}`,{projectPath:t,error:n.message}),"other"}}function d(t){if(!t)return null;let n=String(t).trim().toLowerCase();if(!n)return null;if(n.startsWith("npm:")){const e=n.match(/^npm:[^@]+@(.+)$/);e&&e[1]&&(n=e[1])}const r=n.match(/(?:^|[^\d])v?(\d+)(?:\.|x|\b)/);if(r){const e=Number(r[1]);return Number.isFinite(e)?e:null}return null}function m(t){try{const n=["next.config.js","next.config.ts","next.config.mjs","next.config.cjs"];for(const e of n){const o=s.join(t,e);if(i.existsSync(o))return"nextjs"}const r=["vite.config.js","vite.config.ts","vite.config.mjs","vite.config.cjs"];for(const e of r){const o=s.join(t,e);if(i.existsSync(o))return"vite"}return"other"}catch(n){return a(null,"WARN",`Detect development framework failed: ${n.message}`,{projectPath:t,error:n.message}),"other"}}function g(t){const n=u(t),r=m(t);return{frontendFramework:n,devFramework:r}}export{u as detectFrontendFramework,m as detectDevFramework,g as getFrameworkInfo};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import l from"fs";import m from"path";import y from"../../appConfig/index.js";import{log as o}from"../log/logUtils.js";import{ValidationError as C,SystemError as R,ResourceError as v}from"../error/errorHandler.js";import{extractZip as b}from"../common/zipUtils.js";import{getFrameworkInfo as h}from"./frameworkDetectorUtils.js";import{resolveProjectPath as x,shouldUseIsolationPath as $}from"../common/projectPathUtils.js";async function p(e){try{const s=l.readFileSync(e);if(s.includes(0))return!0;const c=s.toString("utf-8");for(let f=0;f<c.length;f++){const i=c.charCodeAt(f);if(i<32&&i!==9&&i!==10&&i!==13)return!0}return!1}catch{return!1}}function _(e){const s=m.extname(e).toLowerCase();return[".jpg",".jpeg",".png",".gif",".bmp",".webp",".svg"].includes(s)}async function D(e,s,c,f){const i=[],a=await l.promises.readdir(e,{withFileTypes:!0});a.sort((r,n)=>r.isDirectory()&&!n.isDirectory()?-1:!r.isDirectory()&&n.isDirectory()?1:r.name.toLowerCase().localeCompare(n.name.toLowerCase()));for(const r of a){const n=m.join(e,r.name);if(!(r.name.startsWith(".")||(y.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(r.name))&&!(r.isDirectory()&&y.TRAVERSE_EXCLUDE_DIRS.includes(r.name)))if(r.isDirectory()){const t=await D(n,s,c,f);i.push(...t)}else try{const t=await l.promises.stat(n),g=s||x(c),E=m.relative(g,n),d=await p(n),S=`${f}/${E}`,w={name:E,binary:d,sizeExceeded:t.size>y.MAX_INLINE_FILE_SIZE_BYTES,contents:"",fileProxyUrl:S};if(!w.sizeExceeded)if(w.binary){if(_(n)){const F=l.readFileSync(n);w.contents=F.toString("base64")}}else w.contents=l.readFileSync(n,"utf-8");i.push(w)}catch(t){o(c,"WARN",`Process file failed: ${n} - ${t.message}`,{filePath:n,error:t.message})}}return i}async function N(e,s,c){const f=Date.now(),i=m.basename(e);try{o(i,"INFO","Start getting project content",{projectPath:e,command:s}),o(i,"DEBUG","Start traversing project directory",{projectPath:e});const a=await D(e,e,i,c);o(i,"DEBUG","Project directory traversal completed",{projectPath:e,fileCount:a.length});let r=a;s!=="cpage_config"&&(r=a.filter(t=>t.name!=="cpage_config.json")),o(i,"DEBUG","Start detecting framework information",{projectPath:e});const n=h(e),u={files:r,...n};return o(i,"INFO",`Project content obtained, total ${r.length} files`,{projectPath:e,fileCount:r.length,command:s,elapsedMs:Date.now()-f}),u}catch(a){throw o(i,"ERROR",`Get project content failed: ${a.message}`,{projectPath:e,originalError:a.message,elapsedMs:Date.now()-f}),new R(`Get project content failed: ${a.message}`,{projectPath:e,originalError:a.message})}}async function U(e,s,c,f,i={}){const a=Date.now(),r=Number(s);if(!Number.isFinite(r))throw new C("Code version must be a number",{field:"codeVersion"});const n=m.join(y.UPLOAD_PROJECT_DIR,e),u=m.join(n,`${e}-v${r}.zip`);if(!l.existsSync(u))throw new v(`Backup file for version ${r} does not exist`,{projectId:e,codeVersion:r,backupZipPath:u});const t=$(i)?m.join(y.PROJECT_SOURCE_DIR,String(i.tenantId),String(i.spaceId),"_his",e):m.join(y.PROJECT_SOURCE_DIR,"_his",e);try{l.existsSync(t)&&await l.promises.rm(t,{recursive:!0,force:!0}),await l.promises.mkdir(t,{recursive:!0}),o(e,"INFO",`Start extracting backup file for version ${r}`,{projectId:e,codeVersion:r,backupZipPath:u,tempExtractDir:t}),o(e,"DEBUG","Start extracting version backup file",{projectId:e,backupZipPath:u,tempExtractDir:t}),await b(u,t),o(e,"DEBUG",`Version ${r} backup file extraction completed`,{projectId:e,codeVersion:r,tempExtractDir:t}),o(e,"DEBUG","Start traversing version directory",{projectId:e,tempExtractDir:t});const g=await D(t,t,e,f);let E=g;c!=="cpage_config"&&(E=g.filter(S=>S.name!=="cpage_config.json"));const d={files:E};return o(e,"INFO",`Version ${r} project content obtained`,{projectId:e,codeVersion:r,fileCount:d.files?d.files.length:0,command:c,elapsedMs:Date.now()-a}),d}finally{try{l.existsSync(t)&&(await l.promises.rm(t,{recursive:!0,force:!0}),o(e,"INFO",`Temporary directory cleaned: ${t}`,{projectId:e,codeVersion:r}))}catch(g){o(e,"WARN",`Clean temporary directory failed: ${g.message}`,{projectId:e,codeVersion:r,tempExtractDir:t,error:g.message})}}}export{N as getProjectContent,U as getProjectContentByVersion,D as traverseDirectory,p as isBinaryFile,_ as isImageFile};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import o from"fs";import
|
|
1
|
+
import o from"fs";import c from"path";import{log as t}from"../log/logUtils.js";async function s(a,r){try{const e=c.join(a,r);return o.existsSync(e)?o.statSync(e).isDirectory()?(o.rmSync(e,{recursive:!0,force:!0}),t("default","INFO",`Successfully deleted initialization project directory: ${e}`),!0):(t("default","WARN",`Target path is not a directory: ${e}`),!1):(t("default","INFO",`Initialization project directory does not exist: ${e}`),!0)}catch(e){return t("default","ERROR",`Delete initialization project directory failed: ${e.message}`),t("default","ERROR",`Target path: ${c.join(a,r)}`),!1}}async function u(a){try{const{INIT_PROJECT_DIR:r,INIT_PROJECT_NAME_REACT:e,INIT_PROJECT_NAME_VUE3:l}=a;if(!r)return t("default","WARN","INIT_PROJECT_DIR configuration missing"),!1;const i=new Set([e,l].filter(Boolean));if(i.size===0)return t("default","WARN","Initialization template name configuration missing"),!1;t("default","INFO","Start cleaning initialization project directory..."),t("default","INFO",`Target directory: ${r}`),t("default","INFO",`Template names: ${Array.from(i).join(", ")}`);let n=!0;for(const f of i)await s(r,f)||(n=!1);return n?t("default","INFO","Initialization project directory cleanup completed"):t("default","ERROR","Initialization project directory cleanup failed"),n}catch(r){return t("default","ERROR",`Clean initialization project directory failed: ${r.message}`),!1}}export{s as deleteInitProjectFolder,u as cleanupInitProjectOnStartup};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import r from"fs";import s from"path";import"../../appConfig/index.js";import{log as m}from"../log/logUtils.js";import{ValidationError as h,SystemError as w,FileError as u}from"../error/errorHandler.js";import{resolveProjectPath as d}from"../common/projectPathUtils.js";async function F(t,a,c=null,y={}){try{if(!t)throw new h("Project ID cannot be empty",{field:"projectId"});if(!a)throw new h("File cannot be empty",{field:"file"});if(!a.path)throw new h("File path is invalid",{field:"file.path"});m(t,"INFO","Start uploading attachment file",{projectId:t,fileName:c,originalName:a.originalname,tempPath:a.path});const e=d(t,y);if(!r.existsSync(e))throw new h("Project does not exist",{field:"projectId"});const n=s.join(e,".attachments");r.existsSync(n)||(await r.promises.mkdir(n,{recursive:!0}),m(t,"INFO","Create attachment directory",{attachmentsDir:n}));let i;c?i=c:i=a.originalname;let o=s.join(n,i);if(r.existsSync(o)){const l=Date.now(),g=Math.round(Math.random()*1e6),f=s.extname(i);i=`${s.basename(i,f)}_${l}_${g}${f}`,o=s.join(n,i)}try{await r.promises.rename(a.path,o)}catch(l){if(l.code==="EXDEV")m(t,"INFO","Cross-device move, using copy method",{tempPath:a.path,finalPath:o}),await r.promises.copyFile(a.path,o),await r.promises.unlink(a.path);else throw l}const p=s.relative(e,o);return m(t,"INFO","Attachment file uploaded successfully",{projectId:t,originalName:a.originalname,fileName:i,relativePath:p,finalFilePath:o}),{fileName:i,relativePath:p}}catch(e){if(a&&a.path&&r.existsSync(a.path))try{await r.promises.unlink(a.path),m(t,"INFO","Clean temporary file",{tempPath:a.path})}catch(n){m(t,"WARN","Clean temporary file failed",{tempPath:a.path,error:n.message})}throw m(t,"ERROR","Upload attachment file failed",{projectId:t,originalName:a?.originalname,error:e.message}),e instanceof h||e instanceof w||e instanceof u?e:new w(`Upload attachment file failed: ${e.message}`,{projectId:t,originalError:e.message})}}export{F as uploadAttachmentFile};
|
|
@@ -1,3 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
${c.name} Service status:
|
|
3
|
-
`),console.log(` Running status: ${e.running?"Running":"Stopped"}`),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||"Unknown"}`),console.log(` Platform: ${e.pidInfo.platform||"Unknown"}`),console.log(` Started at: ${e.pidInfo.startedAt||"Unknown"}`),console.log(` Uptime: ${R(e.pidInfo.startedAt)}`)),console.log(` PID file: ${d()}`),console.log(""),process.exit(e.running?0:1)}
|
|
1
|
+
import f from"path";import M from"os";import i from"fs-extra";import{spawn as N}from"cross-spawn";import _ from"tree-kill";import{fileURLToPath as G}from"url";import{execFileSync as T}from"child_process";import q from"http";import{createRequire as J}from"module";const k=f.dirname(G(import.meta.url)),V=J(import.meta.url),c={name:"nuwax-file-server",pidDir:f.join(M.tmpdir(),"nuwax-file-server"),pidFileName:"server.pid",lockFileName:"start.lock",defaultStopTimeout:3e4,defaultStartTimeout:3e4,staleLockTimeout:12e4,checkInterval:500};function w(){return f.join(c.pidDir,c.pidFileName)}function I(){return f.join(c.pidDir,c.lockFileName)}function x(){const e=[f.join(k,"..","server.js"),f.join(k,"server.js")];for(const t of e)if(i.existsSync(t))return t;return e[0]}function F(){const e=["../../package.json","../package.json"];for(const t of e)try{const s=V(t);if(s?.version)return s.version}catch{}return"unknown"}function y(){try{const e=w();if(!i.existsSync(e))return null;const t=i.readFileSync(e,"utf8"),s=JSON.parse(t);return!s||typeof s.pid!="number"?null:s}catch(e){return e.code!=="ENOENT"&&console.error(`Read PID file failed: ${e.message}`),null}}function E(e){try{const t=w();i.ensureDirSync(c.pidDir),i.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 g(){try{const e=w();i.existsSync(e)&&(i.removeSync(e),console.debug(`PID file deleted: ${e}`))}catch(e){console.error(`Delete PID file failed: ${e.message}`)}}function p(e){try{return process.kill(e,0),!0}catch(t){return t.code!=="ESRCH"}}function H(e){try{return v()?T("powershell",["-NoProfile","-Command",`(Get-CimInstance Win32_Process -Filter "ProcessId = ${e}").CommandLine`],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim():T("ps",["-p",String(e),"-o","command="],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return""}}function b(e){if(!p(e))return!1;const t=H(e);if(!t)return!1;const s=t.toLowerCase(),r=f.basename(f.join(k,"..","server.js")).toLowerCase(),n=c.name.toLowerCase();return s.includes(r)||s.includes(n)}function R(){i.ensureDirSync(c.pidDir);const e=I(),t=`${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2,10)}`,s=JSON.stringify({pid:process.pid,token:t,createdAt:new Date().toISOString()},null,2);try{const r=i.openSync(e,"wx");return i.writeSync(r,s),{fd:r,token:t}}catch(r){if(r.code!=="EEXIST")throw r;try{const n=i.readFileSync(e,"utf8"),o=JSON.parse(n),a=Number(o?.pid),l=o?.createdAt?new Date(o.createdAt).getTime():0,u=Date.now()-l,D=Number.isFinite(a)&&a>0&&p(a),d=!l||Number.isNaN(u)||u>c.staleLockTimeout;if(!D||d){i.removeSync(e);const S=i.openSync(e,"wx");return i.writeSync(S,s),console.warn("Detected stale start lock, auto cleaned"),{fd:S,token:t}}}catch{i.removeSync(e);const o=i.openSync(e,"wx");return i.writeSync(o,s),console.warn("Detected invalid start lock, auto cleaned"),{fd:o,token:t}}throw r}}function C(e){const t=e?.fd,s=e?.token;try{t!=null&&i.closeSync(t)}catch{}try{const r=I();if(i.existsSync(r)){let n=!1;if(!s)n=!0;else try{const o=i.readFileSync(r,"utf8");n=JSON.parse(o)?.token===s}catch{n=!0}n&&i.removeSync(r)}}catch{}}function v(){return process.platform==="win32"}async function O(e=c.defaultStopTimeout){const t=y();return t?b(t.pid)?(console.log(`Existing service process detected (PID: ${t.pid}), stopping before start...`),await h(t.pid,!1)&&await P(t.pid,e)?(g(),{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 P(t.pid,e)?(g(),{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...`),g(),{success:!0,message:"Stale PID file cleaned"}):{success:!0,message:"No existing service process"}}async function h(e,t=!1){return new Promise(s=>{if(!p(e)){console.debug(`Process ${e} does not exist, already stopped`),s(!0);return}const r=t?"SIGKILL":"SIGTERM",n=v()?"taskkill":"tree-kill";if(console.debug(`Use ${n} to stop process ${e} (signal: ${r})`),v()){const o=t?["/F","/PID",String(e)]:["/PID",String(e)],a=N("taskkill",o,{stdio:["ignore","pipe","pipe"],windowsHide:!0});let l="";a.stdout.on("data",u=>{l+=u.toString()}),a.stderr.on("data",u=>{l+=u.toString()}),a.on("error",u=>{console.error(`Stop process failed: ${u.message}`),s(!1)}),a.on("close",u=>{u===0?(console.debug(`Process ${e} stopped`),s(!0)):(console.warn(`taskkill exit code: ${u}, output: ${l}`),t?s(!1):h(e,!0).then(s))})}else _(e,r,o=>{o?o.code==="ESRCH"?(console.debug(`Process ${e} does not exist`),s(!0)):(console.error(`Stop process failed: ${o.message}`),s(!1)):(console.debug(`Process ${e} stopped`),s(!0))})})}async function P(e,t=c.defaultStopTimeout){const s=Date.now();for(;p(e);){if(Date.now()-s>t)return console.warn(`Wait for process ${e} to stop timeout (${t}ms)`),!1;await new Promise(n=>setTimeout(n,c.checkInterval))}const r=Date.now()-s;return console.debug(`Process ${e} stopped after ${r}ms`),!0}async function L(e,t=c.defaultStartTimeout){const s=Number(e);if(!Number.isFinite(s)||s<=0)return!1;const r=Date.now();for(;Date.now()-r<=t;){if(await new Promise(o=>{const a=q.get({host:"127.0.0.1",port:s,path:"/health",timeout:Math.min(2e3,c.checkInterval*4)},l=>{o(l.statusCode>=200&&l.statusCode<300),l.resume()});a.on("timeout",()=>{a.destroy(),o(!1)}),a.on("error",()=>o(!1))}))return!0;await new Promise(o=>setTimeout(o,c.checkInterval))}return!1}async function j(e={}){const{env:t,port:s,config:r}=e;let n=null;console.log(`Start service ${c.name}...`);try{n=R()}catch(o){return o.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: ${o.message}`}}try{const o=Number(e.timeout)||c.defaultStopTimeout,a=await O(o);if(!a.success)return{success:!1,pid:null,message:`Service start blocked: ${a.message}`};const l={...process.env};t&&(l.NODE_ENV=t,console.log(`Environment: ${t}`)),s&&(l.PORT=s,console.log(`Port: ${s}`)),r&&(l.CONFIG_FILE=r,console.log(`Configuration file: ${r}`));const u=x(),d=N("node",[u,...[]],{env:l,stdio:["pipe","pipe","pipe"],detached:!0,cwd:process.cwd()});if(d.stdout.on("data",m=>{process.stdout.write(m)}),d.stderr.on("data",m=>{process.stderr.write(m)}),d.on("error",m=>{console.error(`Start service failed: ${m.message}`)}),await new Promise(m=>setTimeout(m,2e3)),!p(d.pid))return{success:!1,pid:null,message:"Service start failed"};const S={pid:d.pid,startedAt:new Date().toISOString(),env:t||process.env.NODE_ENV||"production",port:s||process.env.PORT||"60000",version:F(),platform:process.platform};E(S);const $=Number(e.startTimeout)||c.defaultStartTimeout;return await L(S.port,$)?(d.unref(),console.log(`Service started (PID: ${d.pid})`),console.log(`Service address: http://localhost:${S.port}`),{success:!0,pid:d.pid,message:"Service started successfully"}):(console.error(`Service health check timeout (${$}ms), stop failed instance...`),await h(d.pid,!0),g(),{success:!1,pid:null,message:`Service health check timeout (${$}ms)`})}finally{C(n)}}async function A(e={}){const{force:t=!1,timeout:s=c.defaultStopTimeout}=e;console.log(`Stop service ${c.name}...`);const r=y();if(!r)return{success:!1,message:"Service not found"};if(!p(r.pid))return console.log("Service process has stopped, clean PID file..."),g(),{success:!0,message:"Service has stopped (process has exited)"};if(!await h(r.pid,t))return{success:!1,message:"Stop service failed"};const o=await P(r.pid,s);return g(),o?{success:!0,message:"Service has stopped"}:{success:!1,message:"Service stop timeout"}}async function W(e={}){console.log(`Restart service ${c.name}...`);const t=await A(e);!t.success&&t.message!=="Service not found"&&console.warn(`Stop service failed: ${t.message}`),await new Promise(r=>setTimeout(r,2e3));const s=await j(e);return s.success?{success:!0,pid:s.pid,message:"Service has restarted"}:{success:!1,pid:null,message:`Restart failed: ${s.message}`}}function U(){const e=y();return e?p(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 K(e){try{const t=new Date(e),s=new Date;if(isNaN(t.getTime()))return"unknown";const r=Math.floor((s-t)/1e3),n=Math.floor(r/3600),o=Math.floor(r%3600/60),a=r%60;return n>0?`${n} hours ${o} minutes ${a} seconds`:o>0?`${o} minutes ${a} seconds`:`${a} seconds`}catch{return"unknown"}}export{c as SERVICE_CONFIG,w as getPidFilePath,I as getLockFilePath,x as resolveServerScriptPath,F as resolvePackageVersion,y as readPidFile,E as writePidFile,g as deletePidFile,p as isProcessRunning,b as isTargetServiceProcess,R as acquireStartLock,C as releaseStartLock,O as ensureNoRunningServiceBeforeStart,h as stopProcess,P as waitForProcessStop,L as waitForServiceHealth,j as startService,A as stopService,W as restartService,U as getServiceStatus,v as isWindows,K as formatUptime};
|
package/package.json
CHANGED