nuwax-file-server 1.2.5 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import E from"path";import n from"fs";import o from"os";import{fileURLToPath as 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:process.env.INIT_PROJECT_NAME,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,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;
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 p(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)}}p(I);const R={NODE_ENV:I,PORT:parseInt(process.env.PORT),INIT_PROJECT_NAME:process.env.INIT_PROJECT_NAME,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 R;
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.5",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 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();
@@ -35,6 +35,9 @@ UPLOAD_ALLOWED_EXTENSIONS=.zip
35
35
  # 单文件上传大小限制(用于上传工程单文件;1000M)
36
36
  UPLOAD_SINGLE_FILE_SIZE_BYTES=1048576000
37
37
 
38
+ # 打包下载文件大小限制(字节;100M)
39
+ DOWNLOAD_MAX_FILE_SIZE_BYTES=104857600
40
+
38
41
  # 请求体大小限制(Express 格式)
39
42
  REQUEST_BODY_LIMIT=2000mb
40
43
 
@@ -35,6 +35,9 @@ UPLOAD_ALLOWED_EXTENSIONS=.zip
35
35
  # 单文件上传大小限制(用于上传工程单文件;1000M)
36
36
  UPLOAD_SINGLE_FILE_SIZE_BYTES=1048576000
37
37
 
38
+ # 打包下载文件大小限制(字节;100M)
39
+ DOWNLOAD_MAX_FILE_SIZE_BYTES=104857600
40
+
38
41
  # 请求体大小限制(Express 格式)
39
42
  REQUEST_BODY_LIMIT=2000mb
40
43
 
package/dist/server.js CHANGED
@@ -1 +1 @@
1
- import A from"express";import"json-bigint";import O from"swagger-ui-express";import E from"./config/swagger.js";import s from"./appConfig/index.js";import{log as a,logger as I}from"./utils/log/logUtils.js";import S from"./utils/log/logCacheManager.js";import{errorHandler as P,notFoundHandler as w}from"./utils/error/errorHandler.js";import R from"./routes/router.js";import{cleanupInitProjectOnStartup as y}from"./utils/project/initProjectCleanupUtils.js";import{startScheduler as N,stopScheduler as D}from"./scheduler/pnpmPruneScheduler.js";import g from"path";const o=A();o.use(A.json({limit:s.REQUEST_BODY_LIMIT,reviver:(t,e)=>typeof e=="number"&&!Number.isSafeInteger(e)?e.toString():e})),o.use(A.urlencoded({extended:!0,limit:s.REQUEST_BODY_LIMIT})),o.use(I);const f=t=>{let e=t;try{for(;;){const n=decodeURIComponent(e);if(n===e)break;e=n}}catch{}return e};o.use("/api/page/static/:projectId",(t,e,n)=>{const{projectId:l}=t.params;let i=t.path||"/";if(!l||i==="/")return e.status(404).send("Not Found");const r=t.headers.origin,c=r||"*";if(e.header("Access-Control-Allow-Origin",c),e.header("Access-Control-Allow-Methods","HEAD,GET,POST,PUT,DELETE,OPTIONS"),e.header("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Fragment"),e.header("Access-Control-Expose-Headers","Content-Type"),r&&(e.header("Access-Control-Allow-Credentials","true"),e.header("Vary","Origin")),t.method==="OPTIONS")return e.sendStatus(200);i=i.replace(/^\/+/,"");const p=f(i),m=g.join(s.PROJECT_SOURCE_DIR,l,p),d={"Access-Control-Allow-Origin":c,"Access-Control-Allow-Methods":"HEAD,GET,POST,PUT,DELETE,OPTIONS","Access-Control-Allow-Headers":"Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Fragment"};return r&&(d["Access-Control-Allow-Credentials"]="true",d.Vary="Origin"),e.sendFile(m,{dotfiles:"allow",headers:d},u=>{if(u)return n()})}),o.use("/api/computer/static/:userId/:cId",(t,e,n)=>{const{userId:l,cId:i}=t.params;let r=t.path||"/";if(!l||!i||r==="/")return e.status(404).send("Not Found");const c=t.headers.origin,p=c||"*";if(e.header("Access-Control-Allow-Origin",p),e.header("Access-Control-Allow-Methods","HEAD,GET,POST,PUT,DELETE,OPTIONS"),e.header("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control"),e.header("Access-Control-Expose-Headers","Content-Type"),c&&(e.header("Access-Control-Allow-Credentials","true"),e.header("Vary","Origin")),t.method==="OPTIONS")return e.sendStatus(200);r=r.replace(/^\/+/,"");const m=f(r),d=g.join(s.COMPUTER_WORKSPACE_DIR,l,i,m),u={"Access-Control-Allow-Origin":p,"Access-Control-Allow-Methods":"HEAD,GET,POST,PUT,DELETE,OPTIONS","Access-Control-Allow-Headers":"Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control"};return c&&(u["Access-Control-Allow-Credentials"]="true",u.Vary="Origin"),e.sendFile(d,{dotfiles:"allow",headers:u},C=>{if(C)return n()})}),o.use("/api-docs",O.serve,O.setup(E,{customCss:".swagger-ui .topbar { display: none }",customSiteTitle:"nuwax-file-server API Documentation"})),o.use(R),o.use(w),o.use(P);const h=o.listen(s.PORT,async()=>{a("default","INFO",`Server is running on port ${s.PORT} (${s.NODE_ENV} mode)`),await y(s);try{N()}catch(t){a("default","ERROR",`pnpm prune scheduled task failed to start: ${t.message}`)}});h.timeout=6e5,h.keepAliveTimeout=61e4,h.headersTimeout=62e4;const T=t=>{a("default","INFO",`Received ${t} signal, preparing graceful exit...`),D();try{S.destroy(),a("default","INFO","Log cache manager cleared")}catch(e){a("default","ERROR",`Failed to clear log cache manager: ${e.message}`)}h.close(()=>{a("default","INFO","Server closed"),process.exit(0)}),setTimeout(()=>{a("default","ERROR","Force exit (timeout)"),process.exit(1)},3e4)};process.on("SIGTERM",()=>T("SIGTERM")),process.on("SIGINT",()=>T("SIGINT"));
1
+ import A from"express";import"json-bigint";import m from"swagger-ui-express";import E from"./config/swagger.js";import n from"./appConfig/index.js";import{log as i,logger as R}from"./utils/log/logUtils.js";import I from"./utils/log/logCacheManager.js";import{errorHandler as S,notFoundHandler as P}from"./utils/error/errorHandler.js";import w from"./routes/router.js";import{cleanupInitProjectOnStartup as y}from"./utils/project/initProjectCleanupUtils.js";import{startScheduler as N,stopScheduler as D}from"./scheduler/pnpmPruneScheduler.js";import f from"path";const o=A();o.use(A.json({limit:n.REQUEST_BODY_LIMIT,reviver:(t,e)=>typeof e=="number"&&!Number.isSafeInteger(e)?e.toString():e})),o.use(A.urlencoded({extended:!0,limit:n.REQUEST_BODY_LIMIT})),o.use(R);const C=t=>{let e=t;try{for(;;){const s=decodeURIComponent(e);if(s===e)break;e=s}}catch{}return e};o.use("/api/page/static/:projectId",(t,e,s)=>{const{projectId:l}=t.params;let a=t.path||"/";if(!l||a==="/")return e.status(404).send("Not Found");const r=t.headers.origin,c=r||"*";if(e.header("Access-Control-Allow-Origin",c),e.header("Access-Control-Allow-Methods","HEAD,GET,POST,PUT,DELETE,OPTIONS"),e.header("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Fragment"),e.header("Access-Control-Expose-Headers","Content-Type"),r&&(e.header("Access-Control-Allow-Credentials","true"),e.header("Vary","Origin")),t.method==="OPTIONS")return e.sendStatus(200);a=a.replace(/^\/+/,"");const p=C(a),h=f.join(n.PROJECT_SOURCE_DIR,l,p),d={"Access-Control-Allow-Origin":c,"Access-Control-Allow-Methods":"HEAD,GET,POST,PUT,DELETE,OPTIONS","Access-Control-Allow-Headers":"Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Fragment"};return r&&(d["Access-Control-Allow-Credentials"]="true",d.Vary="Origin"),e.sendFile(h,{dotfiles:"allow",headers:d},u=>{if(u)return s()})}),o.use("/api/computer/static/:userId/:cId",(t,e,s)=>{const{userId:l,cId:a}=t.params;let r=t.path||"/";if(!l||!a||r==="/")return e.status(404).send("Not Found");const c=t.headers.origin,p=c||"*";if(e.header("Access-Control-Allow-Origin",p),e.header("Access-Control-Allow-Methods","HEAD,GET,POST,PUT,DELETE,OPTIONS"),e.header("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Range, If-Range"),e.header("Access-Control-Expose-Headers","Content-Type, Content-Length, Content-Range, Accept-Ranges, ETag, Last-Modified"),c&&(e.header("Access-Control-Allow-Credentials","true"),e.header("Vary","Origin")),t.method==="OPTIONS")return e.sendStatus(200);r=r.replace(/^\/+/,"");const h=C(r),d=f.join(n.COMPUTER_WORKSPACE_DIR,l,a,h),u={"Access-Control-Allow-Origin":p,"Access-Control-Allow-Methods":"HEAD,GET,POST,PUT,DELETE,OPTIONS","Access-Control-Allow-Headers":"Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control, Range, If-Range","Access-Control-Expose-Headers":"Content-Type, Content-Length, Content-Range, Accept-Ranges, ETag, Last-Modified"};return c&&(u["Access-Control-Allow-Credentials"]="true",u.Vary="Origin"),e.sendFile(d,{dotfiles:"allow",headers:u},T=>{if(T)return s()})}),o.use("/api-docs",m.serve,m.setup(E,{customCss:".swagger-ui .topbar { display: none }",customSiteTitle:"nuwax-file-server API Documentation"})),o.use(w),o.use(P),o.use(S);const g=o.listen(n.PORT,async()=>{i("default","INFO",`Server is running on port ${n.PORT} (${n.NODE_ENV} mode)`),await y(n);try{N()}catch(t){i("default","ERROR",`pnpm prune scheduled task failed to start: ${t.message}`)}});g.timeout=6e5,g.keepAliveTimeout=61e4,g.headersTimeout=62e4;const O=t=>{i("default","INFO",`Received ${t} signal, preparing graceful exit...`),D();try{I.destroy(),i("default","INFO","Log cache manager cleared")}catch(e){i("default","ERROR",`Failed to clear log cache manager: ${e.message}`)}g.close(()=>{i("default","INFO","Server closed"),process.exit(0)}),setTimeout(()=>{i("default","ERROR","Force exit (timeout)"),process.exit(1)},3e4)};process.on("SIGTERM",()=>O("SIGTERM")),process.on("SIGINT",()=>O("SIGINT"));
@@ -1 +1 @@
1
- import f from"fs";import h from"path";import S from"archiver";import I from"../../appConfig/index.js";import{log as i}from"../log/logUtils.js";import{ValidationError as g,SystemError as P}from"../error/errorHandler.js";async function k(d,m,s,l){const n=[],w=await f.promises.readdir(d,{withFileTypes:!0});w.sort((r,o)=>r.isDirectory()&&!o.isDirectory()?-1:!r.isDirectory()&&o.isDirectory()?1:r.name.toLowerCase().localeCompare(o.name.toLowerCase()));for(const r of w){const o=h.join(d,r.name);if(!(r.name.startsWith(".")||(I.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(r.name))&&!(r.isDirectory()&&I.TRAVERSE_EXCLUDE_DIRS.includes(r.name)))if(r.isDirectory()){const e=await k(o,m,s,l);if(e.length===0){const c=m||d,p=h.relative(c,o).replace(/\\/g,"/");n.push({name:p,isDir:!0})}else n.push(...e)}else try{const e=m||d,c=h.relative(e,o).replace(/\\/g,"/"),p=r.isSymbolicLink();let t=null;if(l){const y=c.split("/").map(F=>encodeURIComponent(F)).join("/");t=`${l}/${y}`}const a={name:c,isDir:!1,fileProxyUrl:t,isLink:p};n.push(a)}catch(e){i(s,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${o}`,{error:e.message})}}return n}async function O(d,m,s){const l=Date.now(),n=`computer:${d}:${m}`,w=I.COMPUTER_WORKSPACE_DIR;if(!d)throw new g("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!m)throw new g("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const r=String(d),o=String(m),u=h.join(w,r,o);if(!f.existsSync(u))return i(n,"INFO","Directory does not exist, returning empty list",{targetDir:u,userId:r,cId:o}),{files:[]};i(n,"DEBUG","Start getting user file list",{targetDir:u,userId:r,cId:o});try{const e=await k(u,u,n,s);return i(n,"INFO","User file list obtained successfully",{fileCount:e.length,targetDir:u,userId:r,cId:o,elapsedMs:Date.now()-l}),{files:e}}catch(e){throw i(n,"ERROR","Failed to get user file list",{targetDir:u,userId:r,cId:o,error:e.message,elapsedMs:Date.now()-l}),new P(`Failed to get file list: ${e.message}`,{targetDir:u,originalError:e.message})}}async function $(d,m,s){const l=Date.now(),n=`computer:${d}:${m}`,w=I.COMPUTER_WORKSPACE_DIR;if(!d)throw new g("userId cannot be empty",{field:"userId"});if(!m)throw new g("cId cannot be empty",{field:"cId"});if(!Array.isArray(s))throw new g("files must be an array",{field:"files"});const r=String(d),o=String(m),u=h.join(w,r,o);f.existsSync(u)||f.mkdirSync(u,{recursive:!0});for(let e=0;e<s.length;e++){const c=s[e];if(!c||typeof c.operation!="string")throw new g(`files[${e}].operation cannot be empty`,{field:`files[${e}].operation`});if(!c.name||typeof c.name!="string")throw new g(`files[${e}].name cannot be empty`,{field:`files[${e}].name`});const p=c.operation.toLowerCase();if(!["create","delete","rename","modify"].includes(p))throw new g(`files[${e}].operation must be one of create, delete, rename or modify`,{field:`files[${e}].operation`});if(p==="rename"&&!c.renameFrom)throw new g(`files[${e}].renameFrom cannot be empty (rename operation requires)`,{field:`files[${e}].renameFrom`});if(p==="modify"&&c.isDir!==!0&&typeof c.contents!="string")throw new g(`files[${e}].contents must be a string (modify operation requires)`,{field:`files[${e}].contents`})}i(n,"DEBUG","Start updating user files",{userId:r,cId:o,filesCount:s.length});try{for(const e of s){const c=e.operation.toLowerCase(),p=e.name,t=h.normalize(p).replace(/^[\/\\]+/,""),a=h.join(u,t),y=h.resolve(a),F=h.resolve(u);if(!y.startsWith(F+h.sep)&&y!==F){i(n,"WARN","File path is not secure, skipping",{filePath:t,resolvedPath:y});continue}switch(c){case"create":{if(e.isDir===!0){if(f.existsSync(a)){if((await f.promises.stat(a)).isFile())throw new g("Cannot create directory, file with the same name already exists",{filePath:t});i(n,"INFO","Directory already exists, skipping creation",{filePath:t});break}await f.promises.mkdir(a,{recursive:!0}),i(n,"INFO","Directory created successfully",{filePath:t});break}if(f.existsSync(a)){if((await f.promises.stat(a)).isDirectory())throw new g("Cannot create file, directory with the same name already exists",{filePath:t});i(n,"INFO","File already exists, skipping creation",{filePath:t});break}await f.promises.mkdir(h.dirname(a),{recursive:!0});const R=e.contents||"";await f.promises.writeFile(a,R,"utf8"),i(n,"INFO","File created successfully",{filePath:t});break}case"delete":{f.existsSync(a)?(await f.promises.stat(a)).isDirectory()?(await f.promises.rm(a,{recursive:!0,force:!0}),i(n,"INFO","Directory deleted successfully",{filePath:t})):(await f.promises.unlink(a),i(n,"INFO","File deleted successfully",{filePath:t})):i(n,"WARN","The file or directory to be deleted does not exist",{filePath:t});break}case"rename":{const R=e.renameFrom;if(!R||typeof R!="string"){i(n,"WARN","Rename operation missing renameFrom",{filePath:t});break}const E=h.normalize(R).replace(/^[\/\\]+/,""),D=h.join(u,E),b=h.resolve(D);if(!b.startsWith(F+h.sep)&&b!==F){i(n,"WARN","Source path is not secure, skipping rename",{sourcePath:E,targetPath:t});break}if(f.existsSync(D)){const C=(await f.promises.stat(D)).isDirectory();await f.promises.mkdir(h.dirname(a),{recursive:!0}),await f.promises.rename(D,a),i(n,"INFO",C?"Directory renamed successfully":"File renamed successfully",{sourcePath:E,targetPath:t})}else i(n,"WARN","The file or directory to be renamed does not exist",{sourcePath:E});break}case"modify":{if(!f.existsSync(a)){i(n,"WARN","The file to be modified does not exist",{filePath:t});break}if((await f.promises.stat(a)).isDirectory()){i(n,"INFO","The target is a directory, skipping modification",{filePath:t});break}const E=typeof e.contents=="string"?e.contents:"";if(await f.promises.readFile(a,"utf8")===E){i(n,"INFO","File content has no changes, skipping write",{filePath:t});break}await f.promises.writeFile(a,E,"utf8"),i(n,"INFO","File modified successfully",{filePath:t});break}default:{i(n,"WARN","Unsupported operation type",{operation:c,filePath:t});break}}}return i(n,"INFO","User files updated successfully",{userId:r,cId:o,filesCount:s.length,elapsedMs:Date.now()-l}),{success:!0,message:"User files updated successfully",userId:r,cId:o,filesCount:s.length}}catch(e){throw i(n,"ERROR","User files updated failed",{userId:r,cId:o,error:e.message,elapsedMs:Date.now()-l}),new P(`User files updated failed: ${e.message}`,{userId:r,cId:o,originalError:e.message})}}async function N(d,m,s,l){const n=Date.now(),w=`computer:${d}:${m}`,r=I.COMPUTER_WORKSPACE_DIR;if(!d)throw new g("userId cannot be empty",{field:"userId"});if(!m)throw new g("cId cannot be empty",{field:"cId"});if(!s)throw new g("file cannot be empty",{field:"file"});if(!l||typeof l!="string")throw new g("filePath cannot be empty",{field:"filePath"});const o=String(d),u=String(m),e=h.join(r,o,u);f.existsSync(e)||f.mkdirSync(e,{recursive:!0});const c=h.normalize(l).replace(/^[\/\\]+/,""),p=h.join(e,c),t=h.resolve(p),a=h.resolve(e);if(!t.startsWith(a+h.sep)&&t!==a)throw new g("File path is not secure, cannot exceed user directory",{field:"filePath",providedPath:l,resolvedPath:t});try{if(await f.promises.mkdir(h.dirname(p),{recursive:!0}),s.buffer)await f.promises.writeFile(p,s.buffer);else if(typeof s.contents=="string")await f.promises.writeFile(p,s.contents,"utf8");else throw new g("File content format is incorrect",{field:"file",hasBuffer:!!s.buffer,hasContents:typeof s.contents});return i(w,"INFO","File uploaded successfully",{userId:o,cId:u,filePath:c,targetPath:t,fileSize:s.buffer?s.buffer.length:s.contents?s.contents.length:0,elapsedMs:Date.now()-n}),{success:!0,message:"File uploaded successfully",fileSize:s.buffer?s.buffer.length:s.contents?s.contents.length:0}}catch(y){throw i(w,"ERROR","File upload failed",{userId:o,cId:u,filePath:c,error:y.message,elapsedMs:Date.now()-n}),new P(`File upload failed: ${y.message}`,{userId:o,cId:u,filePath:c,originalError:y.message})}}async function z(d,m,s,l){const n=Date.now(),w=`computer:${d}:${m}`;if(!d)throw new g("userId cannot be empty",{field:"userId"});if(!m)throw new g("cId cannot be empty",{field:"cId"});if(!Array.isArray(s))throw new g("files must be an array",{field:"files"});if(!Array.isArray(l))throw new g("filePaths must be an array",{field:"filePaths"});if(s.length!==l.length)throw new g(`File count (${s.length}) does not match path count (${l.length})`,{field:"filePaths"});i(w,"DEBUG","Start batch uploading files",{userId:d,cId:m,filesCount:s.length});const r=[];try{for(let e=0;e<s.length;e++){const c=s[e],p=l[e];if(!c){i(w,"WARN","Empty file object encountered in batch upload, skipping",{index:e,filePath:p}),r.push({success:!1,filePath:p,error:"Empty file object"});continue}if(!p||typeof p!="string"){i(w,"WARN","Invalid file path in batch upload, skipping",{index:e,originalname:c.originalname}),r.push({success:!1,filePath:p||"",originalname:c.originalname,error:"Invalid file path"});continue}try{const t=await N(d,m,c,p);r.push({success:!0,filePath:p,originalname:c.originalname,...t})}catch(t){i(w,"ERROR","Single file upload failed in batch upload",{filePath:p,originalname:c.originalname,error:t.message}),r.push({success:!1,filePath:p,originalname:c.originalname,error:t.message})}}const o=r.filter(e=>e.success).length,u=r.filter(e=>!e.success).length;return i(w,"INFO","Batch upload files completed",{userId:d,cId:m,totalCount:s.length,successCount:o,failCount:u,elapsedMs:Date.now()-n}),{success:!0,message:"Batch upload completed",totalCount:s.length,successCount:o,failCount:u,results:r}}catch(o){throw i(w,"ERROR","Batch upload files failed",{userId:d,cId:m,error:o.message,elapsedMs:Date.now()-n}),new P(`Batch upload files failed: ${o.message}`,{userId:d,cId:m,originalError:o.message})}}async function v(d,m){const s=Date.now(),l=`computer:${d}:${m}`,n=I.COMPUTER_WORKSPACE_DIR;if(!d)throw new g("userId cannot be empty",{field:"userId"});if(!m)throw new g("cId cannot be empty",{field:"cId"});if(!n)throw new P("COMPUTER_WORKSPACE_DIR is not configured, cannot create zip");const w=String(d),r=String(m),o=h.join(n,w,r);if(!f.existsSync(o)){const t=`${w}_${r}.zip`;i(l,"WARN","Workspace directory does not exist, returning empty zip",{targetDir:o,userId:w,cId:r,zipFileName:t});const a=S("zip",{zlib:{level:9}});return a.append(null,{name:`${w}_${r}/`,type:"directory"}),a.on("warning",y=>{if(y.code==="ENOENT")i(l,"WARN","Encountered file problem when creating empty zip",{message:y.message,code:y.code});else throw i(l,"ERROR","Encountered warning when creating empty zip",{message:y.message,code:y.code}),y}),a.on("error",y=>{i(l,"ERROR","Failed to create empty zip",{message:y.message})}),{archive:a,zipFileName:t}}const u=`${w}_${r}.zip`;i(l,"DEBUG","Start creating workspace directory zip",{targetDir:o,zipFileName:u});const e=S("zip",{zlib:{level:9}}),c=I.CONTENT_TRAVERSE_EXCLUDE_FILES||[],p=I.TRAVERSE_EXCLUDE_DIRS||[];return e.directory(o,`${w}_${r}`,t=>{const a=t.name||"",y=a.split(/[\/\\]/).filter(Boolean);if(y.some(R=>R.startsWith(".")))return!1;const F=y[y.length-1];if(c.includes(F)||y.some(R=>p.includes(R)))return!1;try{const R=h.join(o,a),E=f.lstatSync(R);if(E.isSymbolicLink()||E.nlink>1)return!1}catch(R){return i(l,"WARN","Error occurred when detecting link file, skipping",{filePath:a,error:R.message}),!1}return t}),e.on("warning",t=>{if(t.code==="ENOENT")i(l,"WARN","Encountered file problem when creating zip",{message:t.message,code:t.code});else throw i(l,"ERROR","Encountered warning when creating zip",{message:t.message,code:t.code}),t}),e.on("error",t=>{i(l,"ERROR","Failed to create zip",{message:t.message,elapsedMs:Date.now()-s})}),e.on("end",()=>{i(l,"INFO","Workspace directory zip created successfully",{targetDir:o,zipFileName:u,elapsedMs:Date.now()-s})}),{archive:e,zipFileName:u}}export{O as getFileList,$ as updateFiles,N as uploadFile,z as uploadFiles,v as downloadAllFiles};
1
+ import m from"fs";import h from"path";import N from"archiver";import I from"../../appConfig/index.js";import{log as r}from"../log/logUtils.js";import{ValidationError as g,SystemError as D}from"../error/errorHandler.js";const A=100*1024*1024,P=I.DOWNLOAD_MAX_FILE_SIZE_BYTES||A;async function O(f,u,a,c){const n=[],w=await m.promises.readdir(f,{withFileTypes:!0});w.sort((i,s)=>i.isDirectory()&&!s.isDirectory()?-1:!i.isDirectory()&&s.isDirectory()?1:i.name.toLowerCase().localeCompare(s.name.toLowerCase()));for(const i of w){const s=h.join(f,i.name);if(!(i.name.startsWith(".")||(I.CONTENT_TRAVERSE_EXCLUDE_FILES||[]).includes(i.name))&&!(i.isDirectory()&&I.TRAVERSE_EXCLUDE_DIRS.includes(i.name)))if(i.isDirectory()){const e=await O(s,u,a,c);if(e.length===0){const l=u||f,p=h.relative(l,s).replace(/\\/g,"/");n.push({name:p,isDir:!0})}else n.push(...e)}else try{const e=u||f,l=h.relative(e,s).replace(/\\/g,"/"),p=i.isSymbolicLink();let o=null;if(c){const y=l.split("/").map(R=>encodeURIComponent(R)).join("/");o=`${c}/${y}`}const t={name:l,isDir:!1,fileProxyUrl:o,isLink:p};n.push(t)}catch(e){r(a,"WARN",`\u5904\u7406\u6587\u4EF6\u5931\u8D25: ${s}`,{error:e.message})}}return n}async function k(f,u,a,c,n=""){const w=await m.promises.readdir(h.join(f,n),{withFileTypes:!0});let i=0;for(const s of w){const d=n?h.join(n,s.name):s.name,e=d.split(h.sep).filter(Boolean);if(e.some(t=>t.startsWith(".")))continue;const l=e[e.length-1];if(u.includes(l)||e.some(t=>a.includes(t)))continue;const p=h.join(f,d);let o;try{o=await m.promises.lstat(p)}catch(t){r(c,"WARN","Error occurred when getting file stats, skipping",{filePath:d.replace(/\\/g,"/"),error:t.message});continue}o.isSymbolicLink()||o.nlink>1||(o.isDirectory()?i+=await k(f,u,a,c,d):o.isFile()&&(i+=o.size))}return i}async function $(f,u,a){const c=Date.now(),n=`computer:${f}:${u}`,w=I.COMPUTER_WORKSPACE_DIR;if(!f)throw new g("userId \u4E0D\u80FD\u4E3A\u7A7A",{field:"userId"});if(!u)throw new g("cId \u4E0D\u80FD\u4E3A\u7A7A",{field:"cId"});const i=String(f),s=String(u),d=h.join(w,i,s);if(!m.existsSync(d))return r(n,"INFO","Directory does not exist, returning empty list",{targetDir:d,userId:i,cId:s}),{files:[]};r(n,"DEBUG","Start getting user file list",{targetDir:d,userId:i,cId:s});try{const e=await O(d,d,n,a);return r(n,"INFO","User file list obtained successfully",{fileCount:e.length,targetDir:d,userId:i,cId:s,elapsedMs:Date.now()-c}),{files:e}}catch(e){throw r(n,"ERROR","Failed to get user file list",{targetDir:d,userId:i,cId:s,error:e.message,elapsedMs:Date.now()-c}),new D(`Failed to get file list: ${e.message}`,{targetDir:d,originalError:e.message})}}async function _(f,u,a){const c=Date.now(),n=`computer:${f}:${u}`,w=I.COMPUTER_WORKSPACE_DIR;if(!f)throw new g("userId cannot be empty",{field:"userId"});if(!u)throw new g("cId cannot be empty",{field:"cId"});if(!Array.isArray(a))throw new g("files must be an array",{field:"files"});const i=String(f),s=String(u),d=h.join(w,i,s);m.existsSync(d)||m.mkdirSync(d,{recursive:!0});for(let e=0;e<a.length;e++){const l=a[e];if(!l||typeof l.operation!="string")throw new g(`files[${e}].operation cannot be empty`,{field:`files[${e}].operation`});if(!l.name||typeof l.name!="string")throw new g(`files[${e}].name cannot be empty`,{field:`files[${e}].name`});const p=l.operation.toLowerCase();if(!["create","delete","rename","modify"].includes(p))throw new g(`files[${e}].operation must be one of create, delete, rename or modify`,{field:`files[${e}].operation`});if(p==="rename"&&!l.renameFrom)throw new g(`files[${e}].renameFrom cannot be empty (rename operation requires)`,{field:`files[${e}].renameFrom`});if(p==="modify"&&l.isDir!==!0&&typeof l.contents!="string")throw new g(`files[${e}].contents must be a string (modify operation requires)`,{field:`files[${e}].contents`})}r(n,"DEBUG","Start updating user files",{userId:i,cId:s,filesCount:a.length});try{for(const e of a){const l=e.operation.toLowerCase(),p=e.name,o=h.normalize(p).replace(/^[\/\\]+/,""),t=h.join(d,o),y=h.resolve(t),R=h.resolve(d);if(!y.startsWith(R+h.sep)&&y!==R){r(n,"WARN","File path is not secure, skipping",{filePath:o,resolvedPath:y});continue}switch(l){case"create":{if(e.isDir===!0){if(m.existsSync(t)){if((await m.promises.stat(t)).isFile())throw new g("Cannot create directory, file with the same name already exists",{filePath:o});r(n,"INFO","Directory already exists, skipping creation",{filePath:o});break}await m.promises.mkdir(t,{recursive:!0}),r(n,"INFO","Directory created successfully",{filePath:o});break}if(m.existsSync(t)){if((await m.promises.stat(t)).isDirectory())throw new g("Cannot create file, directory with the same name already exists",{filePath:o});r(n,"INFO","File already exists, skipping creation",{filePath:o});break}await m.promises.mkdir(h.dirname(t),{recursive:!0});const F=e.contents||"";await m.promises.writeFile(t,F,"utf8"),r(n,"INFO","File created successfully",{filePath:o});break}case"delete":{m.existsSync(t)?(await m.promises.stat(t)).isDirectory()?(await m.promises.rm(t,{recursive:!0,force:!0}),r(n,"INFO","Directory deleted successfully",{filePath:o})):(await m.promises.unlink(t),r(n,"INFO","File deleted successfully",{filePath:o})):r(n,"WARN","The file or directory to be deleted does not exist",{filePath:o});break}case"rename":{const F=e.renameFrom;if(!F||typeof F!="string"){r(n,"WARN","Rename operation missing renameFrom",{filePath:o});break}const E=h.normalize(F).replace(/^[\/\\]+/,""),S=h.join(d,E),b=h.resolve(S);if(!b.startsWith(R+h.sep)&&b!==R){r(n,"WARN","Source path is not secure, skipping rename",{sourcePath:E,targetPath:o});break}if(m.existsSync(S)){const C=(await m.promises.stat(S)).isDirectory();await m.promises.mkdir(h.dirname(t),{recursive:!0}),await m.promises.rename(S,t),r(n,"INFO",C?"Directory renamed successfully":"File renamed successfully",{sourcePath:E,targetPath:o})}else r(n,"WARN","The file or directory to be renamed does not exist",{sourcePath:E});break}case"modify":{if(!m.existsSync(t)){r(n,"WARN","The file to be modified does not exist",{filePath:o});break}if((await m.promises.stat(t)).isDirectory()){r(n,"INFO","The target is a directory, skipping modification",{filePath:o});break}const E=typeof e.contents=="string"?e.contents:"";if(await m.promises.readFile(t,"utf8")===E){r(n,"INFO","File content has no changes, skipping write",{filePath:o});break}await m.promises.writeFile(t,E,"utf8"),r(n,"INFO","File modified successfully",{filePath:o});break}default:{r(n,"WARN","Unsupported operation type",{operation:l,filePath:o});break}}}return r(n,"INFO","User files updated successfully",{userId:i,cId:s,filesCount:a.length,elapsedMs:Date.now()-c}),{success:!0,message:"User files updated successfully",userId:i,cId:s,filesCount:a.length}}catch(e){throw r(n,"ERROR","User files updated failed",{userId:i,cId:s,error:e.message,elapsedMs:Date.now()-c}),new D(`User files updated failed: ${e.message}`,{userId:i,cId:s,originalError:e.message})}}async function z(f,u,a,c){const n=Date.now(),w=`computer:${f}:${u}`,i=I.COMPUTER_WORKSPACE_DIR;if(!f)throw new g("userId cannot be empty",{field:"userId"});if(!u)throw new g("cId cannot be empty",{field:"cId"});if(!a)throw new g("file cannot be empty",{field:"file"});if(!c||typeof c!="string")throw new g("filePath cannot be empty",{field:"filePath"});const s=String(f),d=String(u),e=h.join(i,s,d);m.existsSync(e)||m.mkdirSync(e,{recursive:!0});const l=h.normalize(c).replace(/^[\/\\]+/,""),p=h.join(e,l),o=h.resolve(p),t=h.resolve(e);if(!o.startsWith(t+h.sep)&&o!==t)throw new g("File path is not secure, cannot exceed user directory",{field:"filePath",providedPath:c,resolvedPath:o});try{if(await m.promises.mkdir(h.dirname(p),{recursive:!0}),a.buffer)await m.promises.writeFile(p,a.buffer);else if(typeof a.contents=="string")await m.promises.writeFile(p,a.contents,"utf8");else throw new g("File content format is incorrect",{field:"file",hasBuffer:!!a.buffer,hasContents:typeof a.contents});return r(w,"INFO","File uploaded successfully",{userId:s,cId:d,filePath:l,targetPath:o,fileSize:a.buffer?a.buffer.length:a.contents?a.contents.length:0,elapsedMs:Date.now()-n}),{success:!0,message:"File uploaded successfully",fileSize:a.buffer?a.buffer.length:a.contents?a.contents.length:0}}catch(y){throw r(w,"ERROR","File upload failed",{userId:s,cId:d,filePath:l,error:y.message,elapsedMs:Date.now()-n}),new D(`File upload failed: ${y.message}`,{userId:s,cId:d,filePath:l,originalError:y.message})}}async function T(f,u,a,c){const n=Date.now(),w=`computer:${f}:${u}`;if(!f)throw new g("userId cannot be empty",{field:"userId"});if(!u)throw new g("cId cannot be empty",{field:"cId"});if(!Array.isArray(a))throw new g("files must be an array",{field:"files"});if(!Array.isArray(c))throw new g("filePaths must be an array",{field:"filePaths"});if(a.length!==c.length)throw new g(`File count (${a.length}) does not match path count (${c.length})`,{field:"filePaths"});r(w,"DEBUG","Start batch uploading files",{userId:f,cId:u,filesCount:a.length});const i=[];try{for(let e=0;e<a.length;e++){const l=a[e],p=c[e];if(!l){r(w,"WARN","Empty file object encountered in batch upload, skipping",{index:e,filePath:p}),i.push({success:!1,filePath:p,error:"Empty file object"});continue}if(!p||typeof p!="string"){r(w,"WARN","Invalid file path in batch upload, skipping",{index:e,originalname:l.originalname}),i.push({success:!1,filePath:p||"",originalname:l.originalname,error:"Invalid file path"});continue}try{const o=await z(f,u,l,p);i.push({success:!0,filePath:p,originalname:l.originalname,...o})}catch(o){r(w,"ERROR","Single file upload failed in batch upload",{filePath:p,originalname:l.originalname,error:o.message}),i.push({success:!1,filePath:p,originalname:l.originalname,error:o.message})}}const s=i.filter(e=>e.success).length,d=i.filter(e=>!e.success).length;return r(w,"INFO","Batch upload files completed",{userId:f,cId:u,totalCount:a.length,successCount:s,failCount:d,elapsedMs:Date.now()-n}),{success:!0,message:"Batch upload completed",totalCount:a.length,successCount:s,failCount:d,results:i}}catch(s){throw r(w,"ERROR","Batch upload files failed",{userId:f,cId:u,error:s.message,elapsedMs:Date.now()-n}),new D(`Batch upload files failed: ${s.message}`,{userId:f,cId:u,originalError:s.message})}}async function v(f,u){const a=Date.now(),c=`computer:${f}:${u}`,n=I.COMPUTER_WORKSPACE_DIR;if(!f)throw new g("userId cannot be empty",{field:"userId"});if(!u)throw new g("cId cannot be empty",{field:"cId"});if(!n)throw new D("COMPUTER_WORKSPACE_DIR is not configured, cannot create zip");const w=String(f),i=String(u),s=h.join(n,w,i);if(!m.existsSync(s)){const t=`${w}_${i}.zip`;r(c,"WARN","Workspace directory does not exist, returning empty zip",{targetDir:s,userId:w,cId:i,zipFileName:t});const y=N("zip",{zlib:{level:9}});return y.append(null,{name:`${w}_${i}/`,type:"directory"}),y.on("warning",R=>{if(R.code==="ENOENT")r(c,"WARN","Encountered file problem when creating empty zip",{message:R.message,code:R.code});else throw r(c,"ERROR","Encountered warning when creating empty zip",{message:R.message,code:R.code}),R}),y.on("error",R=>{r(c,"ERROR","Failed to create empty zip",{message:R.message})}),{archive:y,zipFileName:t}}const d=`${w}_${i}.zip`;r(c,"DEBUG","Start creating workspace directory zip",{targetDir:s,zipFileName:d});const e=N("zip",{zlib:{level:9}}),l=I.CONTENT_TRAVERSE_EXCLUDE_FILES||[],p=I.TRAVERSE_EXCLUDE_DIRS||[],o=await k(s,l,p,c);if(o>P){const t=P/1024/1024,y=(o/1024/1024).toFixed(2);throw r(c,"WARN","Download rejected due to oversized workspace",{targetDir:s,downloadableSize:o,maxSizeBytes:P}),new g(`Download failed: total file size ${y}MB exceeds limit ${t}MB`,{field:"downloadSize",downloadableSize:o,maxSizeBytes:P})}return e.directory(s,`${w}_${i}`,t=>{const y=t.name||"",R=y.split(/[\/\\]/).filter(Boolean);if(R.some(E=>E.startsWith(".")))return!1;const F=R[R.length-1];if(l.includes(F)||R.some(E=>p.includes(E)))return!1;try{const E=h.join(s,y),S=m.lstatSync(E);if(S.isSymbolicLink()||S.nlink>1)return!1}catch(E){return r(c,"WARN","Error occurred when detecting link file, skipping",{filePath:y,error:E.message}),!1}return t}),e.on("warning",t=>{if(t.code==="ENOENT")r(c,"WARN","Encountered file problem when creating zip",{message:t.message,code:t.code});else throw r(c,"ERROR","Encountered warning when creating zip",{message:t.message,code:t.code}),t}),e.on("error",t=>{r(c,"ERROR","Failed to create zip",{message:t.message,elapsedMs:Date.now()-a})}),e.on("end",()=>{r(c,"INFO","Workspace directory zip created successfully",{targetDir:s,zipFileName:d,elapsedMs:Date.now()-a})}),{archive:e,zipFileName:d}}export{$ as getFileList,_ as updateFiles,z as uploadFile,T as uploadFiles,v as downloadAllFiles};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuwax-file-server",
3
3
  "displayName": "nuwax-file-server",
4
- "version": "1.2.5",
4
+ "version": "1.2.6",
5
5
  "description": "Cross-platform file service deployment tool with start/stop/restart CLI commands",
6
6
  "type": "module",
7
7
  "main": "index.js",