aws-runtime-bridge 1.9.119 → 1.9.120
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/README.md
CHANGED
|
@@ -14,6 +14,8 @@ npm install -g aws-runtime-bridge
|
|
|
14
14
|
|
|
15
15
|
安装脚本会在 macOS、Linux 与常见 Unix 平台自动为 CLI 入口补齐可执行权限;Windows 平台无需 chmod,会自动跳过该步骤。
|
|
16
16
|
|
|
17
|
+
> **Linux 编译工具链自动安装**:`node-pty`(终端 PTY)只为 macOS/Windows 提供预编译产物,Linux 上安装时必然现场编译,因此服务器需有 `g++`/`make`/`python3`。以 root 执行 `npm install -g`(或 `sudo npm install -g`)时,安装过程会自动用 `apt-get`/`dnf`/`yum`/`zypper`/`apk`/`pacman` 补齐工具链,无需手动干预;非 root 环境会提示手动执行 `sudo apt-get install -y build-essential python3`(按发行版对应命令)。可用 `AWS_BRIDGE_SKIP_BUILD_TOOLS=1` 跳过自动安装。
|
|
18
|
+
|
|
17
19
|
> **浏览器内核自动安装**:安装完成后会自动下载 Playwright Chromium(约 150MB,存于 `~/.cache/ms-playwright`),Linux 上以 root 安装(如 `sudo npm install -g`)时还会自动补齐系统依赖,装完即可使用浏览器功能。可用 `AWS_BRIDGE_SKIP_PLAYWRIGHT_INSTALL=1 npm install -g aws-runtime-bridge` 跳过;跳过或安装失败后,可随时手动执行:`npx playwright install chromium`(Linux 缺系统库时:`sudo npx playwright install --with-deps chromium`)。
|
|
18
20
|
|
|
19
21
|
从当前仓库安装时:
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createReadStream as T,promises as w}from"node:fs";import v from"node:os";import d from"node:path";import{Router as x}from"express";import P from"multer";import{allowHostFileBrowser as I}from"../config.js";import{validateToken as n}from"../middleware/auth.js";import{chmodWorkspaceEntry as _,createWorkspaceEntry as F,deleteWorkspaceEntry as N,extractWorkspaceArchive as C,getWorkspaceEntryProperties as A,listWorkspaceDirectory as H,moveWorkspaceEntry as M,previewWorkspaceDocument as O,readWorkspaceFile as L,renameWorkspaceEntry as U,replaceWorkspaceFiles as z,resolveWorkspaceDownloadTarget as $,searchWorkspaceFiles as G,streamWorkspaceDirectoryZip as B,uploadWorkspaceFiles as X,writeWorkspaceFile as q}from"../services/workspace-files.js";import{unwatchWorkspaceFile as J,watchWorkspaceFile as Y}from"../services/workspace-watch.js";import{createLogger as K}from"../utils/logger.js";const l=K("file-browser"),c=x(),S=2e3,Z=P({dest:d.join(v.tmpdir(),"agentswork-runtime-bridge-uploads"),limits:{files:S,fileSize:512*1024*1024}}),g=Number.MAX_SAFE_INTEGER,f=Number.MAX_SAFE_INTEGER;function y(a){return a===!0||a==="true"||a==="1"}function Q(a){return`attachment; filename="${a.replace(/[\\"\r\n]/g,"_").replace(/[^\x20-\x7E]/g,"_")}"; filename*=UTF-8''${encodeURIComponent(a)}`}async function V(a=[]){await Promise.all(a.map(t=>w.rm(t.path,{force:!0}).catch(()=>{})))}function tt(a){return{status:400,body:{error:a.code==="LIMIT_FILE_COUNT"?`\u4E00\u6B21\u6700\u591A\u4E0A\u4F20 ${S} \u4E2A\u6587\u4EF6\uFF0C\u8BF7\u62C6\u5206\u6587\u4EF6\u5939\u540E\u91CD\u8BD5\u3002`:a.message,code:a.code}}}function rt(a){const t=a.relativePathsJson;if(typeof t=="string"&&t.trim()){const r=JSON.parse(t);if(!Array.isArray(r))throw new Error("relativePathsJson must be an array");return r.map(o=>String(o||""))}const e=a.relativePaths;return Array.isArray(e)?e.map(r=>String(r||"")):typeof e=="string"?[e]:[]}function et(a,t,e){Z.array("files")(a,t,r=>{if(!r){e();return}if(r instanceof P.MulterError){const o=tt(r);t.status(o.status).json(o.body);return}e(r)})}async function k(a=""){const e=process.platform==="win32";if(!a||a===""){if(e){const u=Array.from({length:24},(m,R)=>String.fromCharCode(67+R)+":\\");return{isWindows:!0,currentPath:"",items:(await Promise.all(u.map(async m=>{try{return await w.access(m),{name:m,path:m,isDirectory:!0}}catch{return null}}))).filter(m=>m!==null)}}return{isWindows:!1,currentPath:"/",items:(await w.readdir("/",{withFileTypes:!0})).filter(u=>u.isDirectory()).map(u=>({name:u.name,path:"/"+u.name,isDirectory:!0}))}}const r=d.normalize(a);let o;try{o=await w.stat(r)}catch{throw new Error("Path not found: "+r)}if(!o.isDirectory())throw new Error("Path is not a directory: "+r);const p=(await w.readdir(r,{withFileTypes:!0})).filter(i=>i.isDirectory()).map(i=>({name:i.name,path:d.join(r,i.name),isDirectory:!0}));return{isWindows:e,currentPath:r,items:p}}async function at(a=""){const t=await k(a),e=t.currentPath?await st(t.currentPath):[];return t.currentPath?{...t,maxDepth:g,maxItemsPerDirectory:f,tree:await j(t.currentPath,t.currentPath,1,e)}:{...t,maxDepth:g,maxItemsPerDirectory:f,tree:t.items.slice(0,f)}}async function j(a,t,e,r){if(e>g)return[];const s=(await w.readdir(t,{withFileTypes:!0})).filter(i=>i.isDirectory()).filter(i=>!ct(a,d.join(t,i.name),r)).sort((i,h)=>i.name.localeCompare(h.name)).slice(0,f),p=[];for(const i of s){const h=d.join(t,i.name),u={name:i.name,path:h.replace(/\\/g,"/"),isDirectory:!0};if(e<g)try{u.children=await j(a,h,e+1,r)}catch{u.children=[]}else u.truncated=!0;p.push(u)}return p}function ot(a){return String(a||"").split(/\r?\n/).map(t=>t.trim()).filter(t=>t&&!t.startsWith("#")).map(t=>{const e=t.startsWith("!"),r=e?t.slice(1).trim():t,o=r.startsWith("/"),s=r.endsWith("/"),p=r.replace(/^\/+/,"").replace(/\/+$/,"").replace(/\\/g,"/");return{pattern:p,negated:e,directoryOnly:s,anchored:o,hasSlash:p.includes("/")}}).filter(t=>t.pattern.length>0)}async function st(a){const t=[];let e=a;for(;;){try{const o=d.join(e,".gitignore"),s=await w.readFile(o,"utf8"),p=ot(s);t.unshift(...p)}catch{}const r=d.dirname(e);if(r===e)break;e=r}return t}function nt(a,t,e){const r=lt(a);if(!r)return!1;let o=!1;for(const s of e)s.directoryOnly&&!t||it(r,s)&&(o=!s.negated);return o}function ct(a,t,e){if(e.length===0)return!1;const r=d.relative(a,t).replace(/\\/g,"/");return nt(r,!0,e)}function it(a,t){if(!t.hasSlash&&!t.anchored)return a.split("/").some(r=>pt(r,t.pattern));const e=t.pattern;return t.anchored?b(a,e)||a.startsWith(e+"/"):b(a,e)||a.endsWith("/"+e)||a.includes("/"+e+"/")}function b(a,t){return new RegExp("^"+E(t)+"(?:/.*)?$").test(a)}function pt(a,t){return new RegExp("^"+E(t)+"$").test(a)}function E(a){return a.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*\*/g,".*").replace(/\*/g,"[^/]*").replace(/\?/g,"[^/]")}function lt(a){return String(a||"").replace(/\\/g,"/").replace(/^\.\//,"").replace(/^\/+/,"").replace(/\/+$/,"")}async function D(a){const t=d.normalize(String(a||""));if(!(await w.stat(t)).isDirectory())throw new Error("Path is not a directory: "+t);return t}function W(a){const t=String(a||"").trim();if(!t)throw new Error("Folder name is required");if(t.includes("/")||t.includes("\\")||t==="."||t==="..")throw new Error("Folder name must not contain path separators");return t}async function ht(a,t){const e=await D(a),r=W(t),o=d.join(e,r);return await w.mkdir(o),{ok:!0,parentPath:a,targetPath:o.replace(/\\/g,"/"),entryName:r}}async function ut(a,t){const e=await D(a),r=W(t),o=d.join(d.dirname(e),r);return await w.rename(e,o),{ok:!0,sourcePath:e.replace(/\\/g,"/"),targetPath:o.replace(/\\/g,"/"),newName:r}}c.post("/list",n,async(a,t)=>{try{if(!I)return t.status(403).json({error:"Host filesystem browsing is disabled. Use /api/file-browser/workspace/list with a registered workspace, or set AWS_ALLOW_HOST_FILE_BROWSER=true for isolated local debugging."});const{path:e=""}=a.body||{};t.json(await k(String(e||"")))}catch(e){const r=e;t.status(500).json({error:r.message})}}),c.post("/directory-picker/list",n,async(a,t)=>{try{const{path:e=""}=a.body||{};t.json(await k(String(e||"")))}catch(e){const r=e;t.status(400).json({error:r.message})}}),c.post("/directory-picker/tree",n,async(a,t)=>{try{const{path:e=""}=a.body||{};t.json(await at(String(e||"")))}catch(e){const r=e;t.status(400).json({error:r.message})}}),c.post("/workspace/list",n,async(a,t)=>{try{const{workspacePath:e,path:r=""}=a.body||{},o=await H({workspacePath:String(e||""),targetPath:String(r||"")});t.json({isWindows:process.platform==="win32",currentPath:o.currentPath,workspacePath:o.workspacePath,items:o.items})}catch(e){const r=e;console.error("[file-browser] failed to list workspace directory:",r),t.status(400).json({error:r.message})}}),c.post("/workspace/read",n,async(a,t)=>{try{const{workspacePath:e,filePath:r}=a.body||{},o=await L({workspacePath:String(e||""),filePath:String(r||"")});t.json(o)}catch(e){const r=e;console.error("[file-browser] failed to read workspace file:",r),t.status(400).json({error:r.message})}}),c.post("/workspace/properties",n,async(a,t)=>{try{const{workspacePath:e,targetPath:r}=a.body||{};t.json(await A({workspacePath:String(e||""),targetPath:String(r||"")}))}catch(e){const r=e;l.error("failed to get workspace entry properties:",r),t.status(400).json({error:r.message})}}),c.post("/workspace/chmod",n,async(a,t)=>{try{const{workspacePath:e,targetPath:r,mode:o,recursive:s}=a.body||{};t.json(await _({workspacePath:String(e||""),targetPath:String(r||""),mode:String(o||""),recursive:!!s}))}catch(e){const r=e;l.error("failed to chmod workspace entry:",r),t.status(400).json({error:r.message})}}),c.post("/directory-picker/create",n,async(a,t)=>{try{const{path:e="",entryName:r}=a.body||{};t.json(await ht(String(e||""),String(r||"")))}catch(e){const r=e;l.error("failed to create directory from directory picker:",r),t.status(400).json({error:r.message})}}),c.post("/directory-picker/rename",n,async(a,t)=>{try{const{targetPath:e,newName:r}=a.body||{};t.json(await ut(String(e||""),String(r||"")))}catch(e){const r=e;l.error("failed to rename directory from directory picker:",r),t.status(400).json({error:r.message})}}),c.post("/workspace/preview",n,async(a,t)=>{try{const{workspacePath:e,filePath:r}=a.body||{},o=await O({workspacePath:String(e||""),filePath:String(r||"")});t.json(o)}catch(e){const r=e;l.error("failed to preview workspace document:",r),t.status(400).json({error:r.message})}}),c.post("/workspace/watch",n,async(a,t)=>{try{const{workspacePath:e,filePath:r}=a.body||{};t.json(await Y({workspacePath:String(e||""),filePath:String(r||"")}))}catch(e){const r=e;l.error("failed to watch workspace file:",r),t.status(400).json({error:r.message})}}),c.post("/workspace/unwatch",n,async(a,t)=>{try{const{workspacePath:e,filePath:r}=a.body||{};t.json(await J({workspacePath:String(e||""),filePath:String(r||"")}))}catch(e){const r=e;l.error("failed to unwatch workspace file:",r),t.status(400).json({error:r.message})}}),c.post("/workspace/write",n,async(a,t)=>{try{const{workspacePath:e,filePath:r,content:o}=a.body||{},s=await q({workspacePath:String(e||""),filePath:String(r||""),content:String(o??"")});t.json(s)}catch(e){const r=e;console.error("[file-browser] failed to write workspace file:",r),t.status(400).json({error:r.message})}}),c.post("/workspace/create",n,async(a,t)=>{try{const{workspacePath:e,path:r="",entryName:o,isDirectory:s}=a.body||{},p=await F({workspacePath:String(e||""),targetPath:String(r||""),entryName:String(o||""),isDirectory:!!s});t.json(p)}catch(e){const r=e;l.error("failed to create workspace entry:",r),t.status(400).json({error:r.message})}}),c.post("/workspace/rename",n,async(a,t)=>{try{const{workspacePath:e,targetPath:r,newName:o}=a.body||{},s=await U({workspacePath:String(e||""),targetPath:String(r||""),newName:String(o||"")});t.json(s)}catch(e){const r=e;l.error("failed to rename workspace entry:",r),t.status(400).json({error:r.message})}}),c.post("/workspace/move",n,async(a,t)=>{try{const{workspacePath:e,targetPath:r,destinationPath:o}=a.body||{},s=await M({workspacePath:String(e||""),targetPath:String(r||""),destinationPath:String(o||"")});t.json(s)}catch(e){const r=e;l.error("failed to move workspace entry:",r),t.status(400).json({error:r.message})}}),c.post("/workspace/delete",n,async(a,t)=>{try{const{workspacePath:e,targetPath:r}=a.body||{},o=await N({workspacePath:String(e||""),targetPath:String(r||"")});t.json(o)}catch(e){const r=e;l.error("failed to delete workspace entry:",r),t.status(400).json({error:r.message})}}),c.post("/workspace/upload",n,et,async(a,t)=>{const e=a.files||[];try{const{workspacePath:r,path:o="",extractArchives:s}=a.body||{},p=rt(a.body||{}),i=await X({workspacePath:String(r||""),targetPath:String(o||""),extractArchives:y(s),files:e.map((h,u)=>({originalname:h.originalname,relativePath:p[u],path:h.path,size:h.size}))});t.json(i)}catch(r){const o=r;l.error("failed to upload workspace files:",o),t.status(400).json({error:o.message})}finally{await V(e)}}),c.post("/workspace/download",n,async(a,t)=>{try{const{workspacePath:e,targetPath:r}=a.body||{},o=await $({workspacePath:String(e||""),targetPath:String(r||"")});if(t.setHeader("Content-Type",o.contentType),t.setHeader("Content-Disposition",Q(o.fileName)),t.setHeader("X-Workspace-Path",encodeURIComponent(o.workspacePath)),t.setHeader("X-Workspace-Target-Path",encodeURIComponent(o.targetPath)),o.isDirectory){await B(o.resolvedTargetPath,t);return}T(o.resolvedTargetPath).pipe(t)}catch(e){const r=e;l.error("failed to download workspace entry:",r),t.headersSent?t.destroy(r):t.status(400).json({error:r.message})}}),c.post("/workspace/extract",n,async(a,t)=>{try{const{workspacePath:e,archivePath:r,outputPath:o=""}=a.body||{},s=await C({workspacePath:String(e||""),archivePath:String(r||""),outputPath:String(o||"")});t.json(s)}catch(e){const r=e;l.error("failed to extract workspace archive:",r),t.status(400).json({error:r.message})}}),c.post("/workspace/search",n,async(a,t)=>{try{const{workspacePath:e,keyword:r,fileMask:o,ignoreCase:s,maxResults:p,mode:i}=a.body||{},h=await G({workspacePath:String(e||""),keyword:String(r||""),fileMask:o!==void 0?String(o):void 0,ignoreCase:y(s),maxResults:typeof p=="number"?p:void 0,mode:i==="filename"?"filename":"content"});t.json(h)}catch(e){const r=e;l.error("failed to search workspace files:",r),t.status(400).json({error:r.message})}}),c.post("/workspace/replace",n,async(a,t)=>{try{const{workspacePath:e,keyword:r,replacement:o,fileMask:s,ignoreCase:p,filePath:i}=a.body||{},h=await z({workspacePath:String(e||""),keyword:String(r||""),replacement:String(o??""),fileMask:s!==void 0?String(s):void 0,ignoreCase:y(p),filePath:i!==void 0?String(i):void 0});t.json(h)}catch(e){const r=e;l.error("failed to replace workspace files:",r),t.status(400).json({error:r.message})}});export{S as WORKSPACE_UPLOAD_FILE_LIMIT,tt as createWorkspaceUploadLimitResponse,c as fileBrowserRouter,nt as isIgnoredByGitIgnore,at as listHostDirectoryTree,ot as parseGitIgnoreRules,rt as parseWorkspaceUploadRelativePaths};
|
|
1
|
+
import{createReadStream as S,promises as u}from"node:fs";import x from"node:os";import w from"node:path";import{pipeline as _}from"node:stream/promises";import{Router as v}from"express";import j from"multer";import{allowHostFileBrowser as A}from"../config.js";import{validateToken as c}from"../middleware/auth.js";import{chmodWorkspaceEntry as N,createWorkspaceEntry as C,deleteWorkspaceEntry as z,extractWorkspaceArchive as F,getWorkspaceEntryProperties as B,listWorkspaceDirectory as L,moveWorkspaceEntry as H,previewWorkspaceDocument as M,readWorkspaceFile as O,renameWorkspaceEntry as U,replaceWorkspaceFiles as G,resolveWorkspaceDownloadTarget as $,searchWorkspaceFiles as X,searchWorkspacePaths as q,uploadWorkspaceFiles as J,writeWorkspaceFile as Y,zipWorkspaceDirectoryToTemp as K}from"../services/workspace-files.js";import{unwatchWorkspaceFile as Q,watchWorkspaceFile as V}from"../services/workspace-watch.js";import{createLogger as Z}from"../utils/logger.js";const h=Z("file-browser"),p=v(),g=new Map,tt=6e4,b=2e3,et=j({dest:w.join(x.tmpdir(),"agentswork-runtime-bridge-uploads"),limits:{files:b,fileSize:512*1024*1024}}),m=Number.MAX_SAFE_INTEGER,y=Number.MAX_SAFE_INTEGER;function k(a){return a===!0||a==="true"||a==="1"}function rt(a){return`attachment; filename="${a.replace(/[\\"\r\n]/g,"_").replace(/[^\x20-\x7E]/g,"_")}"; filename*=UTF-8''${encodeURIComponent(a)}`}async function at(a=[]){await Promise.all(a.map(e=>u.rm(e.path,{force:!0}).catch(()=>{})))}function ot(a){return{status:400,body:{error:a.code==="LIMIT_FILE_COUNT"?`\u4E00\u6B21\u6700\u591A\u4E0A\u4F20 ${b} \u4E2A\u6587\u4EF6\uFF0C\u8BF7\u62C6\u5206\u6587\u4EF6\u5939\u540E\u91CD\u8BD5\u3002`:a.message,code:a.code}}}function st(a){const e=a.relativePathsJson;if(typeof e=="string"&&e.trim()){const t=JSON.parse(e);if(!Array.isArray(t))throw new Error("relativePathsJson must be an array");return t.map(o=>String(o||""))}const r=a.relativePaths;return Array.isArray(r)?r.map(t=>String(t||"")):typeof r=="string"?[r]:[]}function nt(a,e,r){et.array("files")(a,e,t=>{if(!t){r();return}if(t instanceof j.MulterError){const o=ot(t);e.status(o.status).json(o.body);return}r(t)})}async function P(a=""){const r=process.platform==="win32";if(!a||a===""){if(r){const d=Array.from({length:24},(f,T)=>String.fromCharCode(67+T)+":\\");return{isWindows:!0,currentPath:"",items:(await Promise.all(d.map(async f=>{try{return await u.access(f),{name:f,path:f,isDirectory:!0}}catch{return null}}))).filter(f=>f!==null)}}return{isWindows:!1,currentPath:"/",items:(await u.readdir("/",{withFileTypes:!0})).filter(d=>d.isDirectory()).map(d=>({name:d.name,path:"/"+d.name,isDirectory:!0}))}}const t=w.normalize(a);let o;try{o=await u.stat(t)}catch{throw new Error("Path not found: "+t)}if(!o.isDirectory())throw new Error("Path is not a directory: "+t);const n=(await u.readdir(t,{withFileTypes:!0})).filter(i=>i.isDirectory()).map(i=>({name:i.name,path:w.join(t,i.name),isDirectory:!0}));return{isWindows:r,currentPath:t,items:n}}async function it(a=""){const e=await P(a),r=e.currentPath?await pt(e.currentPath):[];return e.currentPath?{...e,maxDepth:m,maxItemsPerDirectory:y,tree:await D(e.currentPath,e.currentPath,1,r)}:{...e,maxDepth:m,maxItemsPerDirectory:y,tree:e.items.slice(0,y)}}async function D(a,e,r,t){if(r>m)return[];const s=(await u.readdir(e,{withFileTypes:!0})).filter(i=>i.isDirectory()).filter(i=>!dt(a,w.join(e,i.name),t)).sort((i,l)=>i.name.localeCompare(l.name)).slice(0,y),n=[];for(const i of s){const l=w.join(e,i.name),d={name:i.name,path:l.replace(/\\/g,"/"),isDirectory:!0};if(r<m)try{d.children=await D(a,l,r+1,t)}catch{d.children=[]}else d.truncated=!0;n.push(d)}return n}function ct(a){return String(a||"").split(/\r?\n/).map(e=>e.trim()).filter(e=>e&&!e.startsWith("#")).map(e=>{const r=e.startsWith("!"),t=r?e.slice(1).trim():e,o=t.startsWith("/"),s=t.endsWith("/"),n=t.replace(/^\/+/,"").replace(/\/+$/,"").replace(/\\/g,"/");return{pattern:n,negated:r,directoryOnly:s,anchored:o,hasSlash:n.includes("/")}}).filter(e=>e.pattern.length>0)}async function pt(a){const e=[];let r=a;for(;;){try{const o=w.join(r,".gitignore"),s=await u.readFile(o,"utf8"),n=ct(s);e.unshift(...n)}catch{}const t=w.dirname(r);if(t===r)break;r=t}return e}function lt(a,e,r){const t=wt(a);if(!t)return!1;let o=!1;for(const s of r)s.directoryOnly&&!e||ht(t,s)&&(o=!s.negated);return o}function dt(a,e,r){if(r.length===0)return!1;const t=w.relative(a,e).replace(/\\/g,"/");return lt(t,!0,r)}function ht(a,e){if(!e.hasSlash&&!e.anchored)return a.split("/").some(t=>ut(t,e.pattern));const r=e.pattern;return e.anchored?E(a,r)||a.startsWith(r+"/"):E(a,r)||a.endsWith("/"+r)||a.includes("/"+r+"/")}function E(a,e){return new RegExp("^"+W(e)+"(?:/.*)?$").test(a)}function ut(a,e){return new RegExp("^"+W(e)+"$").test(a)}function W(a){return a.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*\*/g,".*").replace(/\*/g,"[^/]*").replace(/\?/g,"[^/]")}function wt(a){return String(a||"").replace(/\\/g,"/").replace(/^\.\//,"").replace(/^\/+/,"").replace(/\/+$/,"")}async function R(a){const e=w.normalize(String(a||""));if(!(await u.stat(e)).isDirectory())throw new Error("Path is not a directory: "+e);return e}function I(a){const e=String(a||"").trim();if(!e)throw new Error("Folder name is required");if(e.includes("/")||e.includes("\\")||e==="."||e==="..")throw new Error("Folder name must not contain path separators");return e}async function gt(a,e){const r=await R(a),t=I(e),o=w.join(r,t);return await u.mkdir(o),{ok:!0,parentPath:a,targetPath:o.replace(/\\/g,"/"),entryName:t}}async function ft(a,e){const r=await R(a),t=I(e),o=w.join(w.dirname(r),t);return await u.rename(r,o),{ok:!0,sourcePath:r.replace(/\\/g,"/"),targetPath:o.replace(/\\/g,"/"),newName:t}}p.post("/list",c,async(a,e)=>{try{if(!A)return e.status(403).json({error:"Host filesystem browsing is disabled. Use /api/file-browser/workspace/list with a registered workspace, or set AWS_ALLOW_HOST_FILE_BROWSER=true for isolated local debugging."});const{path:r=""}=a.body||{};e.json(await P(String(r||"")))}catch(r){const t=r;e.status(500).json({error:t.message})}}),p.post("/directory-picker/list",c,async(a,e)=>{try{const{path:r=""}=a.body||{};e.json(await P(String(r||"")))}catch(r){const t=r;e.status(400).json({error:t.message})}}),p.post("/directory-picker/tree",c,async(a,e)=>{try{const{path:r=""}=a.body||{};e.json(await it(String(r||"")))}catch(r){const t=r;e.status(400).json({error:t.message})}}),p.post("/workspace/list",c,async(a,e)=>{try{const{workspacePath:r,path:t=""}=a.body||{},o=await L({workspacePath:String(r||""),targetPath:String(t||"")});e.json({isWindows:process.platform==="win32",currentPath:o.currentPath,workspacePath:o.workspacePath,items:o.items})}catch(r){const t=r;console.error("[file-browser] failed to list workspace directory:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/read",c,async(a,e)=>{try{const{workspacePath:r,filePath:t}=a.body||{},o=await O({workspacePath:String(r||""),filePath:String(t||"")});e.json(o)}catch(r){const t=r;console.error("[file-browser] failed to read workspace file:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/properties",c,async(a,e)=>{try{const{workspacePath:r,targetPath:t}=a.body||{};e.json(await B({workspacePath:String(r||""),targetPath:String(t||"")}))}catch(r){const t=r;h.error("failed to get workspace entry properties:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/chmod",c,async(a,e)=>{try{const{workspacePath:r,targetPath:t,mode:o,recursive:s}=a.body||{};e.json(await N({workspacePath:String(r||""),targetPath:String(t||""),mode:String(o||""),recursive:!!s}))}catch(r){const t=r;h.error("failed to chmod workspace entry:",t),e.status(400).json({error:t.message})}}),p.post("/directory-picker/create",c,async(a,e)=>{try{const{path:r="",entryName:t}=a.body||{};e.json(await gt(String(r||""),String(t||"")))}catch(r){const t=r;h.error("failed to create directory from directory picker:",t),e.status(400).json({error:t.message})}}),p.post("/directory-picker/rename",c,async(a,e)=>{try{const{targetPath:r,newName:t}=a.body||{};e.json(await ft(String(r||""),String(t||"")))}catch(r){const t=r;h.error("failed to rename directory from directory picker:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/preview",c,async(a,e)=>{try{const{workspacePath:r,filePath:t}=a.body||{},o=await M({workspacePath:String(r||""),filePath:String(t||"")});e.json(o)}catch(r){const t=r;h.error("failed to preview workspace document:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/watch",c,async(a,e)=>{try{const{workspacePath:r,filePath:t}=a.body||{};e.json(await V({workspacePath:String(r||""),filePath:String(t||"")}))}catch(r){const t=r;h.error("failed to watch workspace file:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/unwatch",c,async(a,e)=>{try{const{workspacePath:r,filePath:t}=a.body||{};e.json(await Q({workspacePath:String(r||""),filePath:String(t||"")}))}catch(r){const t=r;h.error("failed to unwatch workspace file:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/write",c,async(a,e)=>{try{const{workspacePath:r,filePath:t,content:o}=a.body||{},s=await Y({workspacePath:String(r||""),filePath:String(t||""),content:String(o??"")});e.json(s)}catch(r){const t=r;console.error("[file-browser] failed to write workspace file:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/create",c,async(a,e)=>{try{const{workspacePath:r,path:t="",entryName:o,isDirectory:s}=a.body||{},n=await C({workspacePath:String(r||""),targetPath:String(t||""),entryName:String(o||""),isDirectory:!!s});e.json(n)}catch(r){const t=r;h.error("failed to create workspace entry:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/rename",c,async(a,e)=>{try{const{workspacePath:r,targetPath:t,newName:o}=a.body||{},s=await U({workspacePath:String(r||""),targetPath:String(t||""),newName:String(o||"")});e.json(s)}catch(r){const t=r;h.error("failed to rename workspace entry:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/move",c,async(a,e)=>{try{const{workspacePath:r,targetPath:t,destinationPath:o}=a.body||{},s=await H({workspacePath:String(r||""),targetPath:String(t||""),destinationPath:String(o||"")});e.json(s)}catch(r){const t=r;h.error("failed to move workspace entry:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/delete",c,async(a,e)=>{try{const{workspacePath:r,targetPath:t}=a.body||{},o=await z({workspacePath:String(r||""),targetPath:String(t||"")});e.json(o)}catch(r){const t=r;h.error("failed to delete workspace entry:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/upload",c,nt,async(a,e)=>{const r=a.files||[];try{const{workspacePath:t,path:o="",extractArchives:s}=a.body||{},n=st(a.body||{}),i=await J({workspacePath:String(t||""),targetPath:String(o||""),extractArchives:k(s),files:r.map((l,d)=>({originalname:l.originalname,relativePath:n[d],path:l.path,size:l.size}))});e.json(i)}catch(t){const o=t;h.error("failed to upload workspace files:",o),e.status(400).json({error:o.message})}finally{await at(r)}}),p.post("/workspace/download",c,async(a,e)=>{let r;const t=String((a.body||{}).progressId||"").trim();try{const{workspacePath:o,targetPath:s}=a.body||{},n=await $({workspacePath:String(o||""),targetPath:String(s||"")});if(e.setHeader("Content-Type",n.contentType),e.setHeader("Content-Disposition",rt(n.fileName)),e.setHeader("X-Workspace-Path",encodeURIComponent(n.workspacePath)),e.setHeader("X-Workspace-Target-Path",encodeURIComponent(n.targetPath)),n.isDirectory){t&&g.set(t,{zippedBytes:0,totalBytes:null,done:!1,cancelled:!1,updatedAt:Date.now()}),r=await K(n.resolvedTargetPath,n.fileName,l=>{if(!t)return;const d=g.get(t);d&&(d.zippedBytes=l,d.updatedAt=Date.now())},()=>t?!!g.get(t)?.cancelled:!1);const i=await u.stat(r);if(t){const l=g.get(t);l&&(l.zippedBytes=i.size,l.totalBytes=i.size,l.done=!0,l.updatedAt=Date.now())}e.setHeader("Content-Length",String(i.size)),await _(S(r),e);return}n.size!==void 0&&e.setHeader("Content-Length",String(n.size)),S(n.resolvedTargetPath).pipe(e)}catch(o){const s=o;h.error("failed to download workspace entry:",s),e.headersSent?e.destroy(s):e.status(400).json({error:s.message})}finally{if(r&&await u.rm(r,{force:!0}).catch(()=>{}),t){const o=g.get(t);o&&(o.done=!0,o.totalBytes===null&&(o.totalBytes=0)),setTimeout(()=>g.delete(t),tt)}}}),p.post("/workspace/download-progress",c,(a,e)=>{const r=String((a.body||{}).progressId||"").trim();if(!r){e.status(400).json({error:"progressId is required"});return}const t=g.get(r);if(!t){e.status(404).json({error:"download progress not found"});return}e.json({zippedBytes:t.zippedBytes,totalBytes:t.totalBytes,done:t.done,cancelled:t.cancelled})}),p.post("/workspace/download-cancel",c,(a,e)=>{const r=String((a.body||{}).progressId||"").trim();if(!r){e.status(400).json({error:"progressId is required"});return}const t=g.get(r);t&&(t.cancelled=!0,t.done=!0,t.updatedAt=Date.now()),e.json({ok:!0})}),p.post("/workspace/extract",c,async(a,e)=>{try{const{workspacePath:r,archivePath:t,outputPath:o=""}=a.body||{},s=await F({workspacePath:String(r||""),archivePath:String(t||""),outputPath:String(o||"")});e.json(s)}catch(r){const t=r;h.error("failed to extract workspace archive:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/path-search",c,async(a,e)=>{try{const{workspacePath:r,rootPath:t,keyword:o,ignoreCase:s,maxResults:n,maxDirectories:i}=a.body||{},l=await q({workspacePath:String(r||""),rootPath:t!==void 0?String(t):void 0,keyword:String(o||""),ignoreCase:k(s),maxResults:typeof n=="number"?n:void 0,maxDirectories:typeof i=="number"?i:void 0});e.json(l)}catch(r){const t=r;h.error("failed to search workspace paths:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/search",c,async(a,e)=>{try{const{workspacePath:r,keyword:t,fileMask:o,ignoreCase:s,maxResults:n,mode:i}=a.body||{},l=await X({workspacePath:String(r||""),keyword:String(t||""),fileMask:o!==void 0?String(o):void 0,ignoreCase:k(s),maxResults:typeof n=="number"?n:void 0,mode:i==="filename"?"filename":"content"});e.json(l)}catch(r){const t=r;h.error("failed to search workspace files:",t),e.status(400).json({error:t.message})}}),p.post("/workspace/replace",c,async(a,e)=>{try{const{workspacePath:r,keyword:t,replacement:o,fileMask:s,ignoreCase:n,filePath:i}=a.body||{},l=await G({workspacePath:String(r||""),keyword:String(t||""),replacement:String(o??""),fileMask:s!==void 0?String(s):void 0,ignoreCase:k(n),filePath:i!==void 0?String(i):void 0});e.json(l)}catch(r){const t=r;h.error("failed to replace workspace files:",t),e.status(400).json({error:t.message})}});export{b as WORKSPACE_UPLOAD_FILE_LIMIT,ot as createWorkspaceUploadLimitResponse,p as fileBrowserRouter,lt as isIgnoredByGitIgnore,it as listHostDirectoryTree,ct as parseGitIgnoreRules,st as parseWorkspaceUploadRelativePaths};
|
|
2
2
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
1
|
+
import N,{AxiosError as R}from"axios";import h from"ws";import{getRuntimeAccessToken as M,normalizeSchedulerBaseUrl as b}from"./runtime-binding.js";import{loadConfigs as O,loadPrimaryLifecycleConfig as L}from"./auto-register.js";import{logger as c}from"../utils/logger.js";import{port as E}from"../config.js";const W=15e3,x=5e3,D=200,P=1*1024*1024,$=4001,q=768*1024,G=384*1024,g=new Map;function J(e){const r=e.serverUrl,n=e.instanceId;if(!r||!n)return null;try{const t=new URL(r),o=t.protocol==="https:"?"wss:":"ws:",i=M(r),l=O().find(m=>b(m.serverUrl)===r)||L(),s=i||l.userKey;if(i?c.debug("[TunnelClient] \u4F7F\u7528 runtime access token \u8FDE\u63A5\u96A7\u9053"):l.userKey&&c.warn("[TunnelClient] getRuntimeAccessToken \u8FD4\u56DE\u7A7A\uFF0C\u56DE\u9000\u5230 userKey\uFF08\u975E runtime token\uFF0C\u9274\u6743\u53EF\u80FD\u5931\u8D25\uFF09: targetUrl=",r),!s)return c.warn("[TunnelClient] \u7F3A\u5C11 runtime token\uFF0C\u65E0\u6CD5\u5EFA\u7ACB\u96A7\u9053: targetUrl=",r),null;const a=new URLSearchParams({instanceId:n});return{url:`${o}//${t.host}/ws/bridge-tunnel?${a.toString()}`,token:s}}catch(t){return c.warn("[TunnelClient] \u65E0\u6CD5\u89E3\u6790 serverUrl:",String(t)),null}}function ne(e,r){const n=b(e);if(!n){c.warn("[TunnelClient] serverUrl \u65E0\u6548\uFF0C\u65E0\u6CD5\u542F\u52A8\u96A7\u9053:",e);return}const t=g.get(n);t&&(t.shuttingDown=!0,k(t),g.delete(n));const o={serverUrl:n,instanceId:r,streams:new Map,shuttingDown:!1};g.set(n,o),v(o)}function re(){for(const e of g.values())e.shuttingDown=!0,k(e);g.clear()}function k(e){e.timer&&(clearInterval(e.timer),e.timer=void 0),e.reconnectTimer&&(clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0);for(const[r,n]of e.streams.entries()){try{n.ws.close(1e3,"tunnel client stopped")}catch{}e.streams.delete(r)}if(e.ws){e.ws.removeAllListeners();try{e.ws.close(1e3,"tunnel client stopped")}catch{}e.ws=void 0}}function v(e){if(e.shuttingDown)return;const r=e.instanceId;if(!r)return;const n=J(e);if(!n){S(e);return}c.info("[TunnelClient] \u8FDE\u63A5\u96A7\u9053\u670D\u52A1\u7AEF:",n.url);const t=new h(n.url,{handshakeTimeout:15e3,headers:{"X-Runtime-Token":n.token}});e.ws=t,t.on("open",()=>{c.info("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u5DF2\u5EFA\u7ACB: server=",e.serverUrl,"instanceId=",r),e.reconnectTimer&&clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0,e.timer=setInterval(()=>{t.readyState===h.OPEN&&t.send(JSON.stringify({t:"ping"}))},W)}),t.on("unexpected-response",(o,i)=>{c.error("[TunnelClient] \u670D\u52A1\u7AEF\u62D2\u7EDD\u8FDE\u63A5\uFF08\u975E 101 \u54CD\u5E94\uFF09: server=",e.serverUrl,"statusCode=",i.statusCode,"statusMessage=",i.statusMessage)}),t.on("upgrade",o=>{c.debug("[TunnelClient] \u6536\u5230\u5347\u7EA7\u54CD\u5E94: statusCode=",o.statusCode)}),t.on("message",o=>{let i;typeof o=="string"?i=o:Buffer.isBuffer(o)?i=o.toString("utf-8"):i=Buffer.from(o).toString("utf-8");try{const l=JSON.parse(i);K(e,l)}catch(l){c.warn("[TunnelClient] \u89E3\u6790\u96A7\u9053\u6D88\u606F\u5931\u8D25:",String(l))}}),t.on("close",(o,i)=>{if(c.warn("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u5173\u95ED: server=",e.serverUrl,"code=",o,"reason=",String(i)),B(e),o===$){c.warn("[TunnelClient] \u9274\u6743\u5931\u8D25\uFF084001\uFF09\uFF0C\u505C\u6B62\u81EA\u52A8\u91CD\u8FDE\uFF1B\u5F85\u91CD\u65B0\u6CE8\u518C\u540E\u518D\u542F\u52A8\u96A7\u9053: server=",e.serverUrl);return}S(e)}),t.on("error",o=>{c.warn("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u9519\u8BEF: server=",e.serverUrl,"err=",o.message,"code=",o.code),B(e),S(e)})}function B(e){e.timer&&(clearInterval(e.timer),e.timer=void 0);for(const[r,n]of e.streams.entries()){try{n.ws.close(1011,"tunnel disconnected")}catch{}e.streams.delete(r)}e.ws=void 0}function S(e){e.shuttingDown||e.reconnectTimer||(e.reconnectTimer=setTimeout(()=>{e.reconnectTimer=void 0,v(e)},x))}function K(e,r){switch(r.t){case"req":H(e,r);break;case"wsopen":X(e,r);break;case"wsmsg":Y(e,r);break;case"wsclose":j(e,r);break;case"ping":f(e,{t:"pong"});break;case"pong":break;default:c.debug("[TunnelClient] \u672A\u77E5\u6D88\u606F\u7C7B\u578B: t=",r.t)}}async function H(e,r){const n=r.id,t=r.method||"GET",o=r.path||"/",i=`http://127.0.0.1:${E}${o}`;try{const l=r.body?Buffer.from(r.body,"base64"):void 0,s={};if(r.headers)for(const[d,u]of Object.entries(r.headers))u&&u.length>0&&(s[d]=u[0]);s["x-bridge-tunnel-source"]=e.serverUrl;const a=await N.request({url:i,method:t,headers:s,data:l,timeout:0,responseType:"stream",validateStatus:()=>!0}),m={};for(const[d,u]of Object.entries(a.headers))u!==void 0&&(m[d]=Array.isArray(u)?u:[String(u)]);const y=a.data,p=[];let w=0,T=!1,C=!1;try{if(y)for await(const d of y){const u=Buffer.isBuffer(d)?d:Buffer.from(d);if(u.length!==0){if(T){if(!_(e,n,u))return}else if(p.push(u),w+=u.length,w>q){if(T=!0,C=!0,!f(e,{t:"res",id:n,status:a.status,headers:m,body:null,chunked:!0}))return;for(const I of p)if(!_(e,n,I))return;p.length=0,w=0}}}}catch(d){const u=d instanceof Error?d.message:String(d);c.warn("[TunnelClient] \u672C\u5730\u6D41\u8BFB\u53D6\u4E2D\u65AD:",t,o,"->",u),C?f(e,{t:"resend",id:n,error:u}):f(e,{t:"res",id:n,status:502,headers:{"content-type":["text/plain; charset=utf-8"]},body:Buffer.from(u).toString("base64")});return}if(T){f(e,{t:"resend",id:n});return}const A=w>0?Buffer.concat(p).toString("base64"):"";f(e,{t:"res",id:n,status:a.status,headers:m,body:A||null})}catch(l){const s=l instanceof R?l.message:String(l);c.warn("[TunnelClient] \u672C\u5730\u8F6C\u53D1\u5931\u8D25:",t,o,"->",s),f(e,{t:"res",id:n,status:502,headers:{"content-type":["text/plain; charset=utf-8"]},body:Buffer.from(s).toString("base64")})}}function _(e,r,n){let t=0;for(;t<n.length;){const o=n.subarray(t,t+G);if(!f(e,{t:"reschunk",id:r,d:o.toString("base64")}))return!1;t+=o.length}return!0}function X(e,r){const n=r.id,t=r.path||"/",o=`ws://127.0.0.1:${E}${t}`;let i;try{i=new h(o,{handshakeTimeout:1e4})}catch(s){c.warn("[TunnelClient] \u672C\u5730 WS \u8FDE\u63A5\u5931\u8D25:",String(s)),f(e,{t:"wsclose",id:n,code:1011,reason:"local ws connect failed"});return}const l={ws:i,pending:[]};e.streams.set(n,l),i.on("open",()=>{for(const s of l.pending)U(l,s.data,s.bin);l.pending=[],f(e,{t:"wsopened",id:n})}),i.on("message",(s,a)=>{const m=Buffer.isBuffer(s)?s:Buffer.from(s);a&&e.ws&&e.ws.bufferedAmount>P||f(e,{t:"wsmsg",id:n,bin:a,d:m.toString("base64")})}),i.on("close",(s,a)=>{e.streams.delete(n),f(e,{t:"wsclose",id:n,code:s,reason:String(a)})}),i.on("error",s=>{c.warn("[TunnelClient] \u672C\u5730 WS \u9519\u8BEF: id=",n,"err=",s.message),e.streams.delete(n);try{i.close(1011,s.message)}catch{}f(e,{t:"wsclose",id:n,code:1011,reason:s.message})})}function U(e,r,n){n?e.ws.send(r):e.ws.send(r.toString("utf-8"))}function Y(e,r){const n=e.streams.get(r.id);if(!n)return;const t=r.d?Buffer.from(r.d,"base64"):Buffer.alloc(0);if(n.ws.readyState!==h.OPEN){n.pending.length>=D&&(n.pending.shift(),c.warn("[TunnelClient] \u672C\u5730 WS \u672A\u5C31\u7EEA\uFF0C\u6392\u961F\u6D88\u606F\u8D85\u4E0A\u9650\uFF0C\u4E22\u5F03\u6700\u65E7\u4E00\u6761: id=",r.id)),n.pending.push({data:t,bin:!!r.bin});return}U(n,t,!!r.bin)}function j(e,r){const n=e.streams.get(r.id);if(n){e.streams.delete(r.id);try{n.ws.close(r.code??1e3,r.reason||"")}catch{}}}function f(e,r){if(!e.ws||e.ws.readyState!==h.OPEN)return!1;try{return e.ws.send(JSON.stringify(r)),!0}catch(n){return c.warn("[TunnelClient] sendJson \u5931\u8D25:",String(n)),!1}}export{ne as startTunnelClient,re as stopTunnelClient};
|
|
2
2
|
|
|
@@ -31,6 +31,8 @@ export interface WorkspaceDownloadTarget {
|
|
|
31
31
|
fileName: string;
|
|
32
32
|
contentType: string;
|
|
33
33
|
isDirectory: boolean;
|
|
34
|
+
|
|
35
|
+
size?: number;
|
|
34
36
|
}
|
|
35
37
|
export interface WorkspaceUploadFile {
|
|
36
38
|
originalname: string;
|
|
@@ -95,6 +97,28 @@ export interface SearchWorkspaceFilesParams {
|
|
|
95
97
|
maxResults?: number;
|
|
96
98
|
mode?: 'content' | 'filename';
|
|
97
99
|
}
|
|
100
|
+
export interface SearchWorkspacePathsParams {
|
|
101
|
+
workspacePath: string;
|
|
102
|
+
|
|
103
|
+
rootPath?: string;
|
|
104
|
+
|
|
105
|
+
keyword: string;
|
|
106
|
+
ignoreCase?: boolean;
|
|
107
|
+
maxResults?: number;
|
|
108
|
+
maxDirectories?: number;
|
|
109
|
+
}
|
|
110
|
+
export interface WorkspacePathSearchItem {
|
|
111
|
+
name: string;
|
|
112
|
+
path: string;
|
|
113
|
+
isDirectory: boolean;
|
|
114
|
+
}
|
|
115
|
+
export interface SearchWorkspacePathsResult {
|
|
116
|
+
workspacePath: string;
|
|
117
|
+
rootPath: string;
|
|
118
|
+
keyword: string;
|
|
119
|
+
items: WorkspacePathSearchItem[];
|
|
120
|
+
truncated: boolean;
|
|
121
|
+
}
|
|
98
122
|
export interface WorkspaceSearchMatch {
|
|
99
123
|
filePath: string;
|
|
100
124
|
lineNumber: number;
|
|
@@ -219,9 +243,13 @@ export declare function resolveWorkspaceDownloadTarget(params: DownloadWorkspace
|
|
|
219
243
|
|
|
220
244
|
export declare function streamWorkspaceDirectoryZip(directoryPath: string, output: NodeJS.WritableStream): Promise<void>;
|
|
221
245
|
|
|
246
|
+
export declare function zipWorkspaceDirectoryToTemp(directoryPath: string, zipName: string, onProgress?: (zippedBytes: number) => void, shouldCancel?: () => boolean): Promise<string>;
|
|
247
|
+
|
|
222
248
|
export declare function extractWorkspaceArchive(params: ExtractWorkspaceArchiveParams): Promise<WorkspaceArchiveExtractResult>;
|
|
223
249
|
|
|
224
250
|
export declare function searchWorkspaceFiles(params: SearchWorkspaceFilesParams): Promise<SearchWorkspaceFilesResult>;
|
|
225
251
|
|
|
252
|
+
export declare function searchWorkspacePaths(params: SearchWorkspacePathsParams): Promise<SearchWorkspacePathsResult>;
|
|
253
|
+
|
|
226
254
|
export declare function replaceWorkspaceFiles(params: ReplaceWorkspaceFilesParams): Promise<ReplaceWorkspaceFilesResult>;
|
|
227
255
|
export {};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createReadStream as q,createWriteStream as st,promises as n}from"node:fs";import d from"node:path";import{Readable as ct}from"node:stream";import{pipeline as lt}from"node:stream/promises";import{TextDecoder as ht}from"node:util";import{createGunzip as ut}from"node:zlib";import*as dt from"tar";import wt from"unzipper";const{ZipArchive:ft}=await import("archiver"),m={maxEntries:1e4,maxFileBytes:512*1024*1024,maxTotalBytes:2*1024*1024*1024,maxPathDepth:64,maxPathLength:4096},pt=new ht("gbk");function l(t){return String(t||"").replace(/\\/g,"/")}function O(t){const e=d.resolve(String(t||"").trim());if(!e)throw new Error("workspacePath is required");return e}async function mt(t){const e=O(t),r=await n.realpath(e);if(!(await n.stat(r)).isDirectory())throw new Error(`Workspace path is not a directory: ${e}`);return r}async function y(t){return{requestedRoot:O(t),workspaceRoot:await mt(t)}}function L(t,e){const r=d.resolve(e),i=d.relative(t,r);if(i.startsWith("..")||d.isAbsolute(i))throw new Error(`Target path is outside workspace: ${r}`);return r}async function x(t){return n.realpath(t)}async function v(t){await n.realpath(d.dirname(t));try{if((await n.lstat(t)).isSymbolicLink())throw new Error(`Refusing to write through symbolic link: ${t}`)}catch(e){if((typeof e=="object"&&e!==null?Reflect.get(e,"code"):void 0)!=="ENOENT")throw e}}function T(t,e,r){const i=d.resolve(r),a=d.relative(t,i);return a.startsWith("..")||d.isAbsolute(a)?i:L(e,d.join(e,a))}async function C({workspacePath:t,targetPath:e=""}){const{requestedRoot:r,workspaceRoot:i}=await y(t),a=String(e||"").trim(),o=a?T(r,i,a):i,s=await x(o);return{workspaceRoot:i,resolvedTargetPath:s}}function z(t){const e=String(t||"").trim();if(!e)throw new Error("entryName is required");if(e==="."||e==="..")throw new Error("entryName is invalid");if(e.includes("/")||e.includes("\\")||d.basename(e)!==e)throw new Error(`entryName must be a plain file or directory name: ${e}`);return e}function U(t){const e=String(t||"");if(!/[\u00c0-\u00ff]/.test(e))return e;const r=Buffer.from(e,"latin1").toString("utf8");return!r||r.includes("\uFFFD")?e:r}function $(t){const e=U(t);return z(d.basename(e.replace(/\\/g,"/")))}function gt(t,e){if(!t)return $(e);const i=U(String(t||e||"")).replace(/\\/g,"/").split("/").filter(Boolean).map(a=>z(a));return i.length===0?$(e):d.join(...i)}function _(t){return(t&4095).toString(8).padStart(4,"0")}function yt(t){const e=String(t||"").trim();if(!/^[0-7]{3,4}$/.test(e))throw new Error("mode must be a 3 or 4 digit octal string");return Number.parseInt(e,8)}function B(t,e,r,i,a,o,s){const c=t&e?"r":"-",h=t&r?"w":"-",u=(t&i)!==0,f=(t&a)!==0?u?o:s:u?"x":"-";return`${c}${h}${f}`}function Pt(t){return[B(t,256,128,64,2048,"s","S"),B(t,32,16,8,1024,"s","S"),B(t,4,2,1,512,"t","T")].join("")}function kt(t){if(!(t.length<24||t.toString("hex",0,8)!=="89504e470d0a1a0a"))return{format:"png",width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}function xt(t){if(!(t.length<10||t.toString("ascii",0,6)!=="GIF87a"&&t.toString("ascii",0,6)!=="GIF89a"))return{format:"gif",width:t.readUInt16LE(6),height:t.readUInt16LE(8)}}function vt(t){if(t.length<4||t[0]!==255||t[1]!==216)return;let e=2;for(;e+9<t.length;){if(t[e]!==255){e+=1;continue}const r=t[e+1],i=t.readUInt16BE(e+2);if(i<2)return;if(r>=192&&r<=207&&![196,200,204].includes(r)&&e+8<t.length)return{format:"jpeg",height:t.readUInt16BE(e+5),width:t.readUInt16BE(e+7)};e+=2+i}}function Et(t){if(t.length<30||t.toString("ascii",0,4)!=="RIFF"||t.toString("ascii",8,12)!=="WEBP")return;const e=t.toString("ascii",12,16);if(e==="VP8X"&&t.length>=30)return{format:"webp",width:1+t.readUIntLE(24,3),height:1+t.readUIntLE(27,3)};if(e==="VP8 "&&t.length>=30)return{format:"webp",width:t.readUInt16LE(26)&16383,height:t.readUInt16LE(28)&16383};if(e==="VP8L"&&t.length>=25){const r=t.readUInt32LE(21);return{format:"webp",width:(r&16383)+1,height:(r>>14&16383)+1}}}async function G(t,e){if((await n.lstat(t)).isSymbolicLink())throw new Error(`Refusing to change permissions on symbolic link: ${t}`);const i=await n.stat(t);if(!e||!i.isDirectory())return[t];const a=await n.readdir(t),o=await Promise.all(a.map(s=>{const c=d.join(t,s);return G(c,!0)}));return[t,...o.flat()]}async function St(t){if((await n.lstat(t)).isSymbolicLink())throw new Error(`Refusing to change permissions on symbolic link: ${t}`);await x(t)}async function Rt(t){const e=await n.open(t,"r");try{const r=Buffer.alloc(512),{bytesRead:i}=await e.read(r,0,r.length,0),a=r.subarray(0,i);return kt(a)||xt(a)||vt(a)||Et(a)}finally{await e.close()}}async function E({workspacePath:t,targetPath:e},r={}){const{requestedRoot:i,workspaceRoot:a}=await y(t),o=String(e||"").trim();if(!o)throw new Error("targetPath is required");const s=T(i,a,o),c=await x(s);if(!r.allowWorkspaceRoot&&c===a)throw new Error("Workspace root cannot be modified");return{workspaceRoot:a,resolvedTargetPath:c}}async function Z(t){const{requestedRoot:e,workspaceRoot:r}=await y(t.workspacePath),i=String(t.targetPath||"").trim();if(!i)return{workspaceRoot:r,resolvedTargetPath:r};const a=T(e,r,i);return await J(a)?{workspaceRoot:r,resolvedTargetPath:await n.realpath(a)}:(await n.realpath(d.dirname(a)),{workspaceRoot:r,resolvedTargetPath:a})}function H(t){const e=t.toLowerCase();return e.endsWith(".zip")?"zip":e.endsWith(".tar.gz")||e.endsWith(".tgz")?"tgz":e.endsWith(".tar")?"tar":e.endsWith(".gz")?"gz":null}function K(t){return t.replace(/\.tar\.gz$/i,"").replace(/\.tgz$/i,"").replace(/\.zip$/i,"").replace(/\.tar$/i,"").replace(/\.gz$/i,"")}function Ft(t,e){const r=z(K(e)||"extracted");return d.join(t,r)}function A(t,e){const r=String(e||"").replace(/\\/g,"/"),i=r.split("/").filter(Boolean);if(!r||r.startsWith("/")||/^[A-Za-z]:/.test(r))throw new Error(`Archive entry path is invalid: ${e}`);if(r.length>m.maxPathLength||i.length>m.maxPathDepth)throw new Error(`Archive entry path is too deep or too long: ${e}`);return L(t,d.join(t,r))}function Tt(t){const e=t.props?.pathBuffer;if(!e||t.props?.flags?.isUnicode)return t.path;const r=pt.decode(e);return!r||r.includes("\uFFFD")?t.path:r}async function V(t){const e=t.autodrain?.();e&&await new Promise((r,i)=>{e.on("close",r),e.on("end",r),e.on("finish",r),e.on("error",i)})}function b(t){if(t.entries+=1,t.entries>m.maxEntries)throw new Error(`Archive contains too many entries. Limit: ${m.maxEntries}`)}function X(t,e){if(t.totalBytes+=e,e>m.maxFileBytes)throw new Error(`Archive entry exceeds max size. Limit: ${m.maxFileBytes} bytes`);if(t.totalBytes>m.maxTotalBytes)throw new Error(`Archive extraction exceeds total size. Limit: ${m.maxTotalBytes} bytes`)}async function Y(t,e,r){let i=0;t.on("data",a=>{const o=Buffer.isBuffer(a)?a.length:Buffer.byteLength(a);i+=o,(i>m.maxFileBytes||r.totalBytes+i>m.maxTotalBytes)&&t instanceof ct&&t.destroy(new Error("Archive extraction size limit exceeded"))}),await lt(t,st(e)),X(r,i)}async function J(t){try{return await n.access(t),!0}catch(e){if((typeof e=="object"&&e!==null?Reflect.get(e,"code"):void 0)==="ENOENT")return!1;throw e}}async function W({workspacePath:t,filePath:e}){const{requestedRoot:r,workspaceRoot:i}=await y(t),a=String(e||"").trim();if(!a)throw new Error("filePath is required");return{workspaceRoot:i,resolvedFilePath:T(r,i,a)}}async function Ot(t){const{workspaceRoot:e}=await y(t.workspacePath),r=String(t.targetPath||"").trim();if(!r){const{resolvedTargetPath:h}=await C(t);return Q(e,h)}const i=d.resolve(r);let a;try{a=await n.realpath(i)}catch{throw new Error(`Path not found: ${i}`)}if(!(await n.stat(a)).isDirectory())throw new Error(`Path is not a directory: ${i}`);const s=d.relative(e,a),c=s===""||!s.startsWith("..")&&!d.isAbsolute(s);return Q(e,c?a:i)}async function Q(t,e){if(!(await n.stat(e)).isDirectory())throw new Error(`Path is not a directory: ${e}`);const i=await n.readdir(e,{withFileTypes:!0}),a=await Promise.all(i.map(async o=>{const s=d.join(e,o.name),c=o.isFile()?(await n.stat(s)).size:void 0;return{name:o.name,path:l(s),isDirectory:o.isDirectory(),size:c}}));return{currentPath:l(e),workspacePath:l(t),items:a}}async function Ut(t){const{workspaceRoot:e,resolvedFilePath:r}=await W(t),i=await x(r);if(!(await n.stat(i)).isFile())throw new Error(`Path is not a file: ${i}`);const o=await n.readFile(i,"utf-8");return{workspacePath:l(e),filePath:l(i),content:o}}const tt={maxEntries:1e5,maxDepth:32};async function zt(t){let e=0,r=0;async function i(a,o){if(o>=tt.maxDepth)return;let s;try{s=await n.readdir(a,{withFileTypes:!0})}catch{return}for(const c of s){if(r+=1,r>tt.maxEntries)return;if(c.isSymbolicLink())continue;const h=d.join(a,c.name);try{if(c.isDirectory())await i(h,o+1);else if(c.isFile()){const u=await n.stat(h);e+=u.size}}catch{}}}return await i(t,0),e}async function _t(t){const{workspaceRoot:e,resolvedTargetPath:r}=await E(t,{allowWorkspaceRoot:!0}),[i,a]=await Promise.all([n.stat(r),n.lstat(r)]),o=i.isDirectory(),s=i.isFile()?await Rt(r):void 0,c=o?await zt(r):i.size;return{workspacePath:l(e),targetPath:l(r),name:d.basename(r),isDirectory:i.isDirectory(),isFile:i.isFile(),isSymbolicLink:a.isSymbolicLink(),size:c,mode:i.mode,modeOctal:_(i.mode),permissions:Pt(i.mode),uid:i.uid,gid:i.gid,atimeMs:i.atimeMs,mtimeMs:i.mtimeMs,ctimeMs:i.ctimeMs,birthtimeMs:i.birthtimeMs,...s?{image:s}:{}}}async function Gt(t){const e=yt(t.mode),r=!!t.recursive,{workspaceRoot:i,resolvedTargetPath:a}=await E(t),o=await G(a,r);for(const s of o)await St(s),await n.chmod(s,e);return{ok:!0,workspacePath:l(i),targetPath:l(a),mode:e,modeOctal:_(e),recursive:r,changedCount:o.length}}async function Zt(t){const{workspaceRoot:e,resolvedFilePath:r}=await W(t),i=await x(r),a=await n.stat(i);if(!a.isFile())throw new Error(`Path is not a file: ${i}`);const o=d.extname(i).toLowerCase();if(o===".doc")return{workspacePath:l(e),filePath:l(i),kind:"unsupported",content:"",message:"\u6682\u4E0D\u652F\u6301\u65E7\u7248 .doc \u683C\u5F0F\u9884\u89C8\uFF0C\u8BF7\u53E6\u5B58\u4E3A .docx \u540E\u518D\u6253\u5F00\u3002",size:a.size,mtimeMs:a.mtimeMs};if(o!==".docx")throw new Error("Document preview only supports .docx files");return{workspacePath:l(e),filePath:l(i),kind:"html",content:"",size:a.size,mtimeMs:a.mtimeMs}}async function Ht(t){const{workspaceRoot:e,resolvedFilePath:r}=await W(t);return await n.mkdir(d.dirname(r),{recursive:!0}),await v(r),await n.writeFile(r,String(t.content??""),"utf-8"),{ok:!0,workspacePath:l(e),filePath:l(r)}}async function Kt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await C(t),i=z(t.entryName),a=d.join(r,i);if(!(await n.stat(r)).isDirectory())throw new Error(`Path is not a directory: ${r}`);try{throw await n.access(a),new Error(`Path already exists: ${a}`)}catch(s){if((typeof s=="object"&&s!==null?Reflect.get(s,"code"):void 0)!=="ENOENT")throw s}return t.isDirectory?await n.mkdir(a,{recursive:!1}):(await n.mkdir(d.dirname(a),{recursive:!0}),await v(a),await n.writeFile(a,"","utf-8")),{ok:!0,workspacePath:l(e),targetPath:l(a),isDirectory:t.isDirectory}}async function Vt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await E(t),i=z(t.newName),a=d.join(d.dirname(r),i);if(a===r)return{ok:!0,workspacePath:l(e),sourcePath:l(r),targetPath:l(a)};await n.stat(r);try{throw await n.access(a),new Error(`Path already exists: ${a}`)}catch(o){if((typeof o=="object"&&o!==null?Reflect.get(o,"code"):void 0)!=="ENOENT")throw o}return await n.rename(r,a),{ok:!0,workspacePath:l(e),sourcePath:l(r),targetPath:l(a)}}async function Xt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await E(t),i=await C({workspacePath:t.workspacePath,targetPath:t.destinationPath});if(!(await n.stat(i.resolvedTargetPath)).isDirectory())throw new Error(`Destination is not a directory: ${i.resolvedTargetPath}`);if((await n.stat(r)).isDirectory()){const c=d.relative(r,i.resolvedTargetPath);if(c===""||!c.startsWith("..")&&!d.isAbsolute(c))throw new Error("Cannot move a directory into itself or its descendant")}const s=d.join(i.resolvedTargetPath,d.basename(r));if(s===r)return{ok:!0,workspacePath:l(e),sourcePath:l(r),targetPath:l(s),destinationPath:l(i.resolvedTargetPath)};try{throw await n.access(s),new Error(`Path already exists: ${s}`)}catch(c){if((typeof c=="object"&&c!==null?Reflect.get(c,"code"):void 0)!=="ENOENT")throw c}return await n.rename(r,s),{ok:!0,workspacePath:l(e),sourcePath:l(r),targetPath:l(s),destinationPath:l(i.resolvedTargetPath)}}async function Yt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await E(t),i=await n.stat(r),a=d.parse(r).root;if(r===a)throw new Error("Filesystem root cannot be deleted");return i.isDirectory()?await n.rm(r,{recursive:!0,force:!1}):await n.unlink(r),{ok:!0,workspacePath:l(e),targetPath:l(r)}}async function Jt(t){if(!Array.isArray(t.files)||t.files.length===0)throw new Error("files are required");const{workspaceRoot:e,resolvedTargetPath:r}=await Z(t);if(!(await n.stat(r)).isDirectory())throw new Error(`Path is not a directory: ${r}`);const a=[];for(const o of t.files){const s=$(o.originalname),c=gt(o.relativePath,o.originalname),h=d.join(r,c);await n.mkdir(d.dirname(h),{recursive:!0}),await v(h),await n.copyFile(o.path,h);let u;t.extractArchives&&H(h)&&(u=await $t({workspacePath:e,archivePath:h,outputPath:Ft(r,s)})),a.push({fileName:s,targetPath:l(h),size:o.size,extracted:u})}return{ok:!0,workspacePath:l(e),targetPath:l(r),files:a}}async function Qt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await E(t),i=await n.stat(r),a=d.basename(r);return{workspacePath:l(e),targetPath:l(r),resolvedTargetPath:r,fileName:i.isDirectory()?`${a||"workspace"}.zip`:a,contentType:i.isDirectory()?"application/zip":Dt(r),isDirectory:i.isDirectory()}}function Dt(t){const e=d.extname(t).toLowerCase();return{".svg":"image/svg+xml",".svgz":"image/svg+xml",".ico":"image/x-icon",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".bmp":"image/bmp",".avif":"image/avif",".txt":"text/plain; charset=utf-8",".json":"application/json; charset=utf-8",".xml":"application/xml; charset=utf-8",".html":"text/html; charset=utf-8",".htm":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".md":"text/markdown; charset=utf-8",".csv":"text/csv; charset=utf-8",".pdf":"application/pdf",".zip":"application/zip",".mp3":"audio/mpeg",".wav":"audio/wav",".mp4":"video/mp4",".webm":"video/webm"}[e]||"application/octet-stream"}async function te(t,e){const r=new ft({zlib:{level:9}}),i=new Promise((a,o)=>{e.on("close",a),e.on("finish",a),r.on("error",o),e.on("error",o)});r.pipe(e),r.directory(t,!1),await r.finalize(),await i}async function Lt(t,e,r){let i=0,a;const o=q(t),s=o.pipe(wt.Parse({forceStream:!0}));try{for await(const c of s){if(a){await V(c);continue}const h=Tt(c);try{const u=A(e,h);if(b(r),c.type==="Directory"){await n.mkdir(u,{recursive:!0});continue}if(c.type!=="File")throw new Error(`Unsupported archive entry type: ${h}`);await n.mkdir(d.dirname(u),{recursive:!0}),await v(u),await Y(c,u,r),i+=1}catch(u){a=u,await V(c)}}}finally{o.destroy(),s.destroy()}if(a)throw a;return i}async function Wt(t,e,r,i){let a=0,o;const s=(c,h)=>{if(o)return!1;try{A(e,c),b(i);const u=String(h.type||"").toLowerCase();if(u==="symboliclink"||u==="link")throw new Error(`Archive links are not supported: ${c}`);if(u!=="file"&&u!=="oldfile"&&u!=="contiguousfile"&&u!=="directory")throw new Error(`Unsupported archive entry type: ${c}`);return typeof h.size=="number"&&X(i,h.size),u!=="directory"&&(a+=1),!0}catch(u){return o=u,!1}};if(await dt.x({file:t,cwd:e,gzip:r,strict:!0,filter:(c,h)=>s(c,h)}),o)throw o;return a}async function Ct(t,e,r){const i=K(d.basename(t)),a=A(e,i||"decompressed");return b(r),await n.mkdir(d.dirname(a),{recursive:!0}),await v(a),await Y(q(t).pipe(ut()),a,r),1}async function $t(t){const{requestedRoot:e,workspaceRoot:r}=await y(t.workspacePath),{resolvedTargetPath:i}=await Z({workspacePath:t.workspacePath,targetPath:t.outputPath||""}),a=T(e,r,String(t.archivePath||"").trim()),o=await x(a);if(!(await n.stat(o)).isFile())throw new Error(`Archive path is not a file: ${o}`);if(await J(i)||await n.mkdir(i,{recursive:!0}),!(await n.stat(i)).isDirectory())throw new Error(`Output path is not a directory: ${i}`);const h=H(o);if(!h)throw new Error("Unsupported archive type. Supported: .zip, .tar, .tar.gz, .tgz, .gz");let u=0;const w={entries:0,totalBytes:0};return h==="zip"?u=await Lt(o,i,w):h==="tar"||h==="tgz"?u=await Wt(o,i,h==="tgz",w):u=await Ct(o,i,w),{ok:!0,workspacePath:l(r),archivePath:l(o),targetPath:l(i),extractedEntries:u}}function et(t){const e=t.match(/^(.*?)\{([^}]+)\}(.*)$/);if(!e)return[t];const[,r,i,a]=e;return i.split(",").flatMap(o=>et(r+o.trim()+a))}function rt(t,e){const r=String(e||"").trim();if(!r||r==="*"||r==="*.*")return!0;const i=t.toLowerCase();return et(r.toLowerCase()).some(o=>{if(o==="*")return!0;const s=o.replace(/\./g,"\\.").replace(/\*\*/g,"<<GLOBSTAR>>").replace(/\*/g,"[^/]*").replace(/\?/g,"[^/]").replace(/<<GLOBSTAR>>/g,".*");return new RegExp(`^${s}$`).test(i)})}function it(t){return t===".git"||t==="node_modules"||t===".svn"||t===".hg"}const at=10*1024*1024,Bt=1e3;async function ee(t){const{workspaceRoot:e}=await y(t.workspacePath),r=String(t.keyword||"").trim();if(!r)throw new Error("keyword is required");const i=!!t.ignoreCase,a=String(t.fileMask||"").trim(),o=typeof t.maxResults=="number"&&t.maxResults>0?t.maxResults:Bt,s=i?r.toLowerCase():r,h=(t.mode==="filename"?"filename":"content")==="filename",u=[];let w=0,f=!1;async function g(k){if(f)return;let S;try{S=await n.readdir(k,{withFileTypes:!0})}catch{return}for(const p of S){if(f)return;const R=L(e,d.join(k,p.name));if(p.isSymbolicLink())continue;if(p.isDirectory()){if(it(p.name))continue;await g(R);continue}if(!p.isFile()||a&&!rt(p.name,a))continue;w+=1;let I;try{I=await n.stat(R)}catch{continue}if(I.size>at)continue;if(h){const P=(i?p.name.toLowerCase():p.name).indexOf(s);if(P!==-1&&(u.push({filePath:l(R),lineNumber:0,lineContent:p.name,matchStart:P,matchEnd:P+r.length}),u.length>=o)){f=!0;return}continue}let N;try{N=await n.readFile(R,"utf-8")}catch{continue}const M=N.split(/\r?\n/);for(let F=0;F<M.length;F+=1){const P=M[F],nt=i?P.toLowerCase():P;let j=0;for(;;){const D=nt.indexOf(s,j);if(D===-1)break;if(u.push({filePath:l(R),lineNumber:F+1,lineContent:P,matchStart:D,matchEnd:D+r.length}),u.length>=o){f=!0;return}j=D+1}}}}return await g(e),{workspacePath:l(e),keyword:r,totalFiles:w,totalMatches:u.length,matches:u,truncated:f}}async function ot(t,e,r,i){let a;try{a=await n.readFile(t,"utf-8")}catch{return{changed:!1,count:0}}const o=a;let s=0;if(i){const c=e.toLowerCase();let h=0;for(;;){const w=a.toLowerCase().indexOf(c,h);if(w===-1)break;a=a.slice(0,w)+r+a.slice(w+e.length),s+=1;const f=r.length||1;h=w+f}}else{let c=0;for(;;){const h=a.indexOf(e,c);if(h===-1)break;a=a.slice(0,h)+r+a.slice(h+e.length),s+=1;const u=r.length||1;c=h+u}}return a===o?{changed:!1,count:0}:(await v(t),await n.writeFile(t,a,"utf-8"),{changed:!0,count:s})}async function re(t){const{workspaceRoot:e}=await y(t.workspacePath),r=String(t.keyword||"").trim();if(!r)throw new Error("keyword is required");const i=String(t.replacement??""),a=!!t.ignoreCase,o=String(t.fileMask||"").trim(),s=String(t.filePath||"").trim();let c=0,h=0;if(s){const{resolvedFilePath:w}=await W({workspacePath:t.workspacePath,filePath:s}),f=await ot(w,r,i,a);return f.changed&&(c=1,h=f.count),{workspacePath:l(e),keyword:r,replacement:i,changedFiles:c,changedMatches:h}}async function u(w){let f;try{f=await n.readdir(w,{withFileTypes:!0})}catch{return}for(const g of f){const k=L(e,d.join(w,g.name));if(g.isSymbolicLink())continue;if(g.isDirectory()){if(it(g.name))continue;await u(k);continue}if(!g.isFile()||o&&!rt(g.name,o))continue;try{if((await n.stat(k)).size>at)continue}catch{continue}const S=await ot(k,r,i,a);S.changed&&(c+=1,h+=S.count)}}return await u(e),{workspacePath:l(e),keyword:r,replacement:i,changedFiles:c,changedMatches:h}}export{Gt as chmodWorkspaceEntry,Kt as createWorkspaceEntry,Yt as deleteWorkspaceEntry,$t as extractWorkspaceArchive,_t as getWorkspaceEntryProperties,Ot as listWorkspaceDirectory,Xt as moveWorkspaceEntry,Zt as previewWorkspaceDocument,Ut as readWorkspaceFile,Vt as renameWorkspaceEntry,re as replaceWorkspaceFiles,Qt as resolveWorkspaceDownloadTarget,ee as searchWorkspaceFiles,te as streamWorkspaceDirectoryZip,Jt as uploadWorkspaceFiles,Ht as writeWorkspaceFile};
|
|
1
|
+
import{createReadStream as U,createWriteStream as O,promises as c}from"node:fs";import lt from"node:os";import d from"node:path";import{Readable as ht}from"node:stream";import{pipeline as ut}from"node:stream/promises";import{TextDecoder as dt}from"node:util";import{createGunzip as wt}from"node:zlib";import*as ft from"tar";import pt from"unzipper";const{ZipArchive:_}=await import("archiver"),x={maxEntries:1e4,maxFileBytes:512*1024*1024,maxTotalBytes:2*1024*1024*1024,maxPathDepth:64,maxPathLength:4096},mt=new dt("gbk");function u(t){return String(t||"").replace(/\\/g,"/")}function Z(t){const e=d.resolve(String(t||"").trim());if(!e)throw new Error("workspacePath is required");return e}async function yt(t){const e=Z(t),r=await c.realpath(e);if(!(await c.stat(r)).isDirectory())throw new Error(`Workspace path is not a directory: ${e}`);return r}async function v(t){return{requestedRoot:Z(t),workspaceRoot:await yt(t)}}function C(t,e){const r=d.resolve(e),i=d.relative(t,r);if(i.startsWith("..")||d.isAbsolute(i))throw new Error(`Target path is outside workspace: ${r}`);return r}async function F(t){return c.realpath(t)}async function T(t){await c.realpath(d.dirname(t));try{if((await c.lstat(t)).isSymbolicLink())throw new Error(`Refusing to write through symbolic link: ${t}`)}catch(e){if((typeof e=="object"&&e!==null?Reflect.get(e,"code"):void 0)!=="ENOENT")throw e}}function L(t,e,r){const i=d.resolve(r),o=d.relative(t,i);return o.startsWith("..")||d.isAbsolute(o)?i:C(e,d.join(e,o))}async function b({workspacePath:t,targetPath:e=""}){const{requestedRoot:r,workspaceRoot:i}=await v(t),o=String(e||"").trim(),a=o?L(r,i,o):i,s=await F(a);return{workspaceRoot:i,resolvedTargetPath:s}}function $(t){const e=String(t||"").trim();if(!e)throw new Error("entryName is required");if(e==="."||e==="..")throw new Error("entryName is invalid");if(e.includes("/")||e.includes("\\")||d.basename(e)!==e)throw new Error(`entryName must be a plain file or directory name: ${e}`);return e}function G(t){const e=String(t||"");if(!/[\u00c0-\u00ff]/.test(e))return e;const r=Buffer.from(e,"latin1").toString("utf8");return!r||r.includes("\uFFFD")?e:r}function B(t){const e=G(t);return $(d.basename(e.replace(/\\/g,"/")))}function gt(t,e){if(!t)return B(e);const i=G(String(t||e||"")).replace(/\\/g,"/").split("/").filter(Boolean).map(o=>$(o));return i.length===0?B(e):d.join(...i)}function H(t){return(t&4095).toString(8).padStart(4,"0")}function Pt(t){const e=String(t||"").trim();if(!/^[0-7]{3,4}$/.test(e))throw new Error("mode must be a 3 or 4 digit octal string");return Number.parseInt(e,8)}function I(t,e,r,i,o,a,s){const n=t&e?"r":"-",l=t&r?"w":"-",h=(t&i)!==0,f=(t&o)!==0?h?a:s:h?"x":"-";return`${n}${l}${f}`}function kt(t){return[I(t,256,128,64,2048,"s","S"),I(t,32,16,8,1024,"s","S"),I(t,4,2,1,512,"t","T")].join("")}function xt(t){if(!(t.length<24||t.toString("hex",0,8)!=="89504e470d0a1a0a"))return{format:"png",width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}function vt(t){if(!(t.length<10||t.toString("ascii",0,6)!=="GIF87a"&&t.toString("ascii",0,6)!=="GIF89a"))return{format:"gif",width:t.readUInt16LE(6),height:t.readUInt16LE(8)}}function Et(t){if(t.length<4||t[0]!==255||t[1]!==216)return;let e=2;for(;e+9<t.length;){if(t[e]!==255){e+=1;continue}const r=t[e+1],i=t.readUInt16BE(e+2);if(i<2)return;if(r>=192&&r<=207&&![196,200,204].includes(r)&&e+8<t.length)return{format:"jpeg",height:t.readUInt16BE(e+5),width:t.readUInt16BE(e+7)};e+=2+i}}function St(t){if(t.length<30||t.toString("ascii",0,4)!=="RIFF"||t.toString("ascii",8,12)!=="WEBP")return;const e=t.toString("ascii",12,16);if(e==="VP8X"&&t.length>=30)return{format:"webp",width:1+t.readUIntLE(24,3),height:1+t.readUIntLE(27,3)};if(e==="VP8 "&&t.length>=30)return{format:"webp",width:t.readUInt16LE(26)&16383,height:t.readUInt16LE(28)&16383};if(e==="VP8L"&&t.length>=25){const r=t.readUInt32LE(21);return{format:"webp",width:(r&16383)+1,height:(r>>14&16383)+1}}}async function K(t,e){if((await c.lstat(t)).isSymbolicLink())throw new Error(`Refusing to change permissions on symbolic link: ${t}`);const i=await c.stat(t);if(!e||!i.isDirectory())return[t];const o=await c.readdir(t),a=await Promise.all(o.map(s=>{const n=d.join(t,s);return K(n,!0)}));return[t,...a.flat()]}async function Rt(t){if((await c.lstat(t)).isSymbolicLink())throw new Error(`Refusing to change permissions on symbolic link: ${t}`);await F(t)}async function Dt(t){const e=await c.open(t,"r");try{const r=Buffer.alloc(512),{bytesRead:i}=await e.read(r,0,r.length,0),o=r.subarray(0,i);return xt(o)||vt(o)||Et(o)||St(o)}finally{await e.close()}}async function z({workspacePath:t,targetPath:e},r={}){const{requestedRoot:i,workspaceRoot:o}=await v(t),a=String(e||"").trim();if(!a)throw new Error("targetPath is required");const s=L(i,o,a),n=await F(s);if(!r.allowWorkspaceRoot&&n===o)throw new Error("Workspace root cannot be modified");return{workspaceRoot:o,resolvedTargetPath:n}}async function X(t){const{requestedRoot:e,workspaceRoot:r}=await v(t.workspacePath),i=String(t.targetPath||"").trim();if(!i)return{workspaceRoot:r,resolvedTargetPath:r};const o=L(e,r,i);return await et(o)?{workspaceRoot:r,resolvedTargetPath:await c.realpath(o)}:(await c.realpath(d.dirname(o)),{workspaceRoot:r,resolvedTargetPath:o})}function V(t){const e=t.toLowerCase();return e.endsWith(".zip")?"zip":e.endsWith(".tar.gz")||e.endsWith(".tgz")?"tgz":e.endsWith(".tar")?"tar":e.endsWith(".gz")?"gz":null}function Y(t){return t.replace(/\.tar\.gz$/i,"").replace(/\.tgz$/i,"").replace(/\.zip$/i,"").replace(/\.tar$/i,"").replace(/\.gz$/i,"")}function Ft(t,e){const r=$(Y(e)||"extracted");return d.join(t,r)}function N(t,e){const r=String(e||"").replace(/\\/g,"/"),i=r.split("/").filter(Boolean);if(!r||r.startsWith("/")||/^[A-Za-z]:/.test(r))throw new Error(`Archive entry path is invalid: ${e}`);if(r.length>x.maxPathLength||i.length>x.maxPathDepth)throw new Error(`Archive entry path is too deep or too long: ${e}`);return C(t,d.join(t,r))}function Tt(t){const e=t.props?.pathBuffer;if(!e||t.props?.flags?.isUnicode)return t.path;const r=mt.decode(e);return!r||r.includes("\uFFFD")?t.path:r}async function J(t){const e=t.autodrain?.();e&&await new Promise((r,i)=>{e.on("close",r),e.on("end",r),e.on("finish",r),e.on("error",i)})}function M(t){if(t.entries+=1,t.entries>x.maxEntries)throw new Error(`Archive contains too many entries. Limit: ${x.maxEntries}`)}function Q(t,e){if(t.totalBytes+=e,e>x.maxFileBytes)throw new Error(`Archive entry exceeds max size. Limit: ${x.maxFileBytes} bytes`);if(t.totalBytes>x.maxTotalBytes)throw new Error(`Archive extraction exceeds total size. Limit: ${x.maxTotalBytes} bytes`)}async function tt(t,e,r){let i=0;t.on("data",o=>{const a=Buffer.isBuffer(o)?o.length:Buffer.byteLength(o);i+=a,(i>x.maxFileBytes||r.totalBytes+i>x.maxTotalBytes)&&t instanceof ht&&t.destroy(new Error("Archive extraction size limit exceeded"))}),await ut(t,O(e)),Q(r,i)}async function et(t){try{return await c.access(t),!0}catch(e){if((typeof e=="object"&&e!==null?Reflect.get(e,"code"):void 0)==="ENOENT")return!1;throw e}}async function W({workspacePath:t,filePath:e}){const{requestedRoot:r,workspaceRoot:i}=await v(t),o=String(e||"").trim();if(!o)throw new Error("filePath is required");return{workspaceRoot:i,resolvedFilePath:L(r,i,o)}}async function Gt(t){const{workspaceRoot:e}=await v(t.workspacePath),r=String(t.targetPath||"").trim();if(!r){const{resolvedTargetPath:l}=await b(t);return rt(e,l)}const i=d.resolve(r);let o;try{o=await c.realpath(i)}catch{throw new Error(`Path not found: ${i}`)}if(!(await c.stat(o)).isDirectory())throw new Error(`Path is not a directory: ${i}`);const s=d.relative(e,o),n=s===""||!s.startsWith("..")&&!d.isAbsolute(s);return rt(e,n?o:i)}async function rt(t,e){if(!(await c.stat(e)).isDirectory())throw new Error(`Path is not a directory: ${e}`);const i=await c.readdir(e,{withFileTypes:!0}),o=await Promise.all(i.map(async a=>{const s=d.join(e,a.name),n=a.isFile()?(await c.stat(s)).size:void 0;return{name:a.name,path:u(s),isDirectory:a.isDirectory(),size:n}}));return{currentPath:u(e),workspacePath:u(t),items:o}}async function Ht(t){const{workspaceRoot:e,resolvedFilePath:r}=await W(t),i=await F(r);if(!(await c.stat(i)).isFile())throw new Error(`Path is not a file: ${i}`);const a=await c.readFile(i,"utf-8");return{workspacePath:u(e),filePath:u(i),content:a}}const it={maxEntries:1e5,maxDepth:32};async function zt(t){let e=0,r=0;async function i(o,a){if(a>=it.maxDepth)return;let s;try{s=await c.readdir(o,{withFileTypes:!0})}catch{return}for(const n of s){if(r+=1,r>it.maxEntries)return;if(n.isSymbolicLink())continue;const l=d.join(o,n.name);try{if(n.isDirectory())await i(l,a+1);else if(n.isFile()){const h=await c.stat(l);e+=h.size}}catch{}}}return await i(t,0),e}async function Kt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await z(t,{allowWorkspaceRoot:!0}),[i,o]=await Promise.all([c.stat(r),c.lstat(r)]),a=i.isDirectory(),s=i.isFile()?await Dt(r):void 0,n=a?await zt(r):i.size;return{workspacePath:u(e),targetPath:u(r),name:d.basename(r),isDirectory:i.isDirectory(),isFile:i.isFile(),isSymbolicLink:o.isSymbolicLink(),size:n,mode:i.mode,modeOctal:H(i.mode),permissions:kt(i.mode),uid:i.uid,gid:i.gid,atimeMs:i.atimeMs,mtimeMs:i.mtimeMs,ctimeMs:i.ctimeMs,birthtimeMs:i.birthtimeMs,...s?{image:s}:{}}}async function Xt(t){const e=Pt(t.mode),r=!!t.recursive,{workspaceRoot:i,resolvedTargetPath:o}=await z(t),a=await K(o,r);for(const s of a)await Rt(s),await c.chmod(s,e);return{ok:!0,workspacePath:u(i),targetPath:u(o),mode:e,modeOctal:H(e),recursive:r,changedCount:a.length}}async function Vt(t){const{workspaceRoot:e,resolvedFilePath:r}=await W(t),i=await F(r),o=await c.stat(i);if(!o.isFile())throw new Error(`Path is not a file: ${i}`);const a=d.extname(i).toLowerCase();if(a===".doc")return{workspacePath:u(e),filePath:u(i),kind:"unsupported",content:"",message:"\u6682\u4E0D\u652F\u6301\u65E7\u7248 .doc \u683C\u5F0F\u9884\u89C8\uFF0C\u8BF7\u53E6\u5B58\u4E3A .docx \u540E\u518D\u6253\u5F00\u3002",size:o.size,mtimeMs:o.mtimeMs};if(a!==".docx")throw new Error("Document preview only supports .docx files");return{workspacePath:u(e),filePath:u(i),kind:"html",content:"",size:o.size,mtimeMs:o.mtimeMs}}async function Yt(t){const{workspaceRoot:e,resolvedFilePath:r}=await W(t);return await c.mkdir(d.dirname(r),{recursive:!0}),await T(r),await c.writeFile(r,String(t.content??""),"utf-8"),{ok:!0,workspacePath:u(e),filePath:u(r)}}async function Jt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await b(t),i=$(t.entryName),o=d.join(r,i);if(!(await c.stat(r)).isDirectory())throw new Error(`Path is not a directory: ${r}`);try{throw await c.access(o),new Error(`Path already exists: ${o}`)}catch(s){if((typeof s=="object"&&s!==null?Reflect.get(s,"code"):void 0)!=="ENOENT")throw s}return t.isDirectory?await c.mkdir(o,{recursive:!1}):(await c.mkdir(d.dirname(o),{recursive:!0}),await T(o),await c.writeFile(o,"","utf-8")),{ok:!0,workspacePath:u(e),targetPath:u(o),isDirectory:t.isDirectory}}async function Qt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await z(t),i=$(t.newName),o=d.join(d.dirname(r),i);if(o===r)return{ok:!0,workspacePath:u(e),sourcePath:u(r),targetPath:u(o)};await c.stat(r);try{throw await c.access(o),new Error(`Path already exists: ${o}`)}catch(a){if((typeof a=="object"&&a!==null?Reflect.get(a,"code"):void 0)!=="ENOENT")throw a}return await c.rename(r,o),{ok:!0,workspacePath:u(e),sourcePath:u(r),targetPath:u(o)}}async function te(t){const{workspaceRoot:e,resolvedTargetPath:r}=await z(t),i=await b({workspacePath:t.workspacePath,targetPath:t.destinationPath});if(!(await c.stat(i.resolvedTargetPath)).isDirectory())throw new Error(`Destination is not a directory: ${i.resolvedTargetPath}`);if((await c.stat(r)).isDirectory()){const n=d.relative(r,i.resolvedTargetPath);if(n===""||!n.startsWith("..")&&!d.isAbsolute(n))throw new Error("Cannot move a directory into itself or its descendant")}const s=d.join(i.resolvedTargetPath,d.basename(r));if(s===r)return{ok:!0,workspacePath:u(e),sourcePath:u(r),targetPath:u(s),destinationPath:u(i.resolvedTargetPath)};try{throw await c.access(s),new Error(`Path already exists: ${s}`)}catch(n){if((typeof n=="object"&&n!==null?Reflect.get(n,"code"):void 0)!=="ENOENT")throw n}return await c.rename(r,s),{ok:!0,workspacePath:u(e),sourcePath:u(r),targetPath:u(s),destinationPath:u(i.resolvedTargetPath)}}async function ee(t){const{workspaceRoot:e,resolvedTargetPath:r}=await z(t),i=await c.stat(r),o=d.parse(r).root;if(r===o)throw new Error("Filesystem root cannot be deleted");return i.isDirectory()?await c.rm(r,{recursive:!0,force:!1}):await c.unlink(r),{ok:!0,workspacePath:u(e),targetPath:u(r)}}async function re(t){if(!Array.isArray(t.files)||t.files.length===0)throw new Error("files are required");const{workspaceRoot:e,resolvedTargetPath:r}=await X(t);if(!(await c.stat(r)).isDirectory())throw new Error(`Path is not a directory: ${r}`);const o=[];for(const a of t.files){const s=B(a.originalname),n=gt(a.relativePath,a.originalname),l=d.join(r,n);await c.mkdir(d.dirname(l),{recursive:!0}),await T(l),await c.copyFile(a.path,l);let h;t.extractArchives&&V(l)&&(h=await Wt({workspacePath:e,archivePath:l,outputPath:Ft(r,s)})),o.push({fileName:s,targetPath:u(l),size:a.size,extracted:h})}return{ok:!0,workspacePath:u(e),targetPath:u(r),files:o}}async function ie(t){const{workspaceRoot:e,resolvedTargetPath:r}=await z(t),i=await c.stat(r),o=d.basename(r);return{workspacePath:u(e),targetPath:u(r),resolvedTargetPath:r,fileName:i.isDirectory()?`${o||"workspace"}.zip`:o,contentType:i.isDirectory()?"application/zip":Lt(r),isDirectory:i.isDirectory(),size:i.isDirectory()?void 0:i.size}}function Lt(t){const e=d.extname(t).toLowerCase();return{".svg":"image/svg+xml",".svgz":"image/svg+xml",".ico":"image/x-icon",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".bmp":"image/bmp",".avif":"image/avif",".txt":"text/plain; charset=utf-8",".json":"application/json; charset=utf-8",".xml":"application/xml; charset=utf-8",".html":"text/html; charset=utf-8",".htm":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".md":"text/markdown; charset=utf-8",".csv":"text/csv; charset=utf-8",".pdf":"application/pdf",".zip":"application/zip",".mp3":"audio/mpeg",".wav":"audio/wav",".mp4":"video/mp4",".webm":"video/webm"}[e]||"application/octet-stream"}async function oe(t,e){const r=new _({zlib:{level:9}}),i=new Promise((o,a)=>{e.on("close",o),e.on("finish",o),r.on("error",a),e.on("error",a)});r.pipe(e),r.directory(t,!1),await r.finalize(),await i}async function ae(t,e,r,i){const o=d.join(lt.tmpdir(),"agentswork-bridge-downloads");await c.mkdir(o,{recursive:!0});const a=(e||"workspace.zip").replace(/[^\w.-]/g,"_").replace(/\.zip$/i,"")||"workspace",s=d.join(o,`${a}-${Date.now()}-${Math.random().toString(36).slice(2,8)}.zip`),n=new _({zlib:{level:9}}),l=()=>n.pointer(),h=()=>n.abort(),w=O(s),f=new Promise((m,g)=>{w.on("close",m),w.on("finish",m),n.on("error",g),w.on("error",g)});f.catch(()=>{}),n.pipe(w);const p=setInterval(()=>{try{if(i?.()){h();return}r?.(l())}catch{}},200);try{return n.on("progress",()=>{i?.()&&h()}),n.directory(t,!1),i?.()&&h(),await n.finalize(),await f,r?.(l()),s}catch(m){throw await c.rm(s,{force:!0}).catch(()=>{}),m}finally{clearInterval(p)}}async function $t(t,e,r){let i=0,o;const a=U(t),s=a.pipe(pt.Parse({forceStream:!0}));try{for await(const n of s){if(o){await J(n);continue}const l=Tt(n);try{const h=N(e,l);if(M(r),n.type==="Directory"){await c.mkdir(h,{recursive:!0});continue}if(n.type!=="File")throw new Error(`Unsupported archive entry type: ${l}`);await c.mkdir(d.dirname(h),{recursive:!0}),await T(h),await tt(n,h,r),i+=1}catch(h){o=h,await J(n)}}}finally{a.destroy(),s.destroy()}if(o)throw o;return i}async function At(t,e,r,i){let o=0,a;const s=(n,l)=>{if(a)return!1;try{N(e,n),M(i);const h=String(l.type||"").toLowerCase();if(h==="symboliclink"||h==="link")throw new Error(`Archive links are not supported: ${n}`);if(h!=="file"&&h!=="oldfile"&&h!=="contiguousfile"&&h!=="directory")throw new Error(`Unsupported archive entry type: ${n}`);return typeof l.size=="number"&&Q(i,l.size),h!=="directory"&&(o+=1),!0}catch(h){return a=h,!1}};if(await ft.x({file:t,cwd:e,gzip:r,strict:!0,filter:(n,l)=>s(n,l)}),a)throw a;return o}async function Ct(t,e,r){const i=Y(d.basename(t)),o=N(e,i||"decompressed");return M(r),await c.mkdir(d.dirname(o),{recursive:!0}),await T(o),await tt(U(t).pipe(wt()),o,r),1}async function Wt(t){const{requestedRoot:e,workspaceRoot:r}=await v(t.workspacePath),{resolvedTargetPath:i}=await X({workspacePath:t.workspacePath,targetPath:t.outputPath||""}),o=L(e,r,String(t.archivePath||"").trim()),a=await F(o);if(!(await c.stat(a)).isFile())throw new Error(`Archive path is not a file: ${a}`);if(await et(i)||await c.mkdir(i,{recursive:!0}),!(await c.stat(i)).isDirectory())throw new Error(`Output path is not a directory: ${i}`);const l=V(a);if(!l)throw new Error("Unsupported archive type. Supported: .zip, .tar, .tar.gz, .tgz, .gz");let h=0;const w={entries:0,totalBytes:0};return l==="zip"?h=await $t(a,i,w):l==="tar"||l==="tgz"?h=await At(a,i,l==="tgz",w):h=await Ct(a,i,w),{ok:!0,workspacePath:u(r),archivePath:u(a),targetPath:u(i),extractedEntries:h}}function ot(t){const e=t.match(/^(.*?)\{([^}]+)\}(.*)$/);if(!e)return[t];const[,r,i,o]=e;return i.split(",").flatMap(a=>ot(r+a.trim()+o))}function at(t,e){const r=String(e||"").trim();if(!r||r==="*"||r==="*.*")return!0;const i=t.toLowerCase();return ot(r.toLowerCase()).some(a=>{if(a==="*")return!0;const s=a.replace(/\./g,"\\.").replace(/\*\*/g,"<<GLOBSTAR>>").replace(/\*/g,"[^/]*").replace(/\?/g,"[^/]").replace(/<<GLOBSTAR>>/g,".*");return new RegExp(`^${s}$`).test(i)})}function j(t){return t===".git"||t==="node_modules"||t===".svn"||t===".hg"}const nt=10*1024*1024,bt=1e3;async function ne(t){const{workspaceRoot:e}=await v(t.workspacePath),r=String(t.keyword||"").trim();if(!r)throw new Error("keyword is required");const i=!!t.ignoreCase,o=String(t.fileMask||"").trim(),a=typeof t.maxResults=="number"&&t.maxResults>0?t.maxResults:bt,s=i?r.toLowerCase():r,l=(t.mode==="filename"?"filename":"content")==="filename",h=[];let w=0,f=!1;async function p(m){if(f)return;let g;try{g=await c.readdir(m,{withFileTypes:!0})}catch{return}for(const y of g){if(f)return;const P=C(e,d.join(m,y.name));if(y.isSymbolicLink())continue;if(y.isDirectory()){if(j(y.name))continue;await p(P);continue}if(!y.isFile()||o&&!at(y.name,o))continue;w+=1;let E;try{E=await c.stat(P)}catch{continue}if(E.size>nt)continue;if(l){const R=(i?y.name.toLowerCase():y.name).indexOf(s);if(R!==-1&&(h.push({filePath:u(P),lineNumber:0,lineContent:y.name,matchStart:R,matchEnd:R+r.length}),h.length>=a)){f=!0;return}continue}let k;try{k=await c.readFile(P,"utf-8")}catch{continue}const D=k.split(/\r?\n/);for(let S=0;S<D.length;S+=1){const R=D[S],ct=i?R.toLowerCase():R;let q=0;for(;;){const A=ct.indexOf(s,q);if(A===-1)break;if(h.push({filePath:u(P),lineNumber:S+1,lineContent:R,matchStart:A,matchEnd:A+r.length}),h.length>=a){f=!0;return}q=A+1}}}}return await p(e),{workspacePath:u(e),keyword:r,totalFiles:w,totalMatches:h.length,matches:h,truncated:f}}const Bt=100,It=120;async function se(t){const{requestedRoot:e,workspaceRoot:r}=await v(t.workspacePath),i=String(t.keyword||"").trim();if(!i)throw new Error("keyword is required");const o=!!t.ignoreCase,a=o?i.toLowerCase():i,s=typeof t.maxResults=="number"&&t.maxResults>0?t.maxResults:Bt,n=typeof t.maxDirectories=="number"&&t.maxDirectories>0?t.maxDirectories:It,l=String(t.rootPath||"").trim(),h=l?d.resolve(/^[A-Za-z]:$/.test(l)?`${l}/`:l):r;let w;try{w=await c.realpath(h)}catch{throw new Error(`Path not found: ${h}`)}if(!(await c.stat(w)).isDirectory())throw new Error(`Path is not a directory: ${h}`);const p=[],m=[w];let g=0,y=!1;for(;m.length>0&&p.length<s&&g<n;){const P=m.shift();g+=1;let E;try{E=await c.readdir(P,{withFileTypes:!0})}catch{continue}for(const k of E){if(p.length>=s){y=!0;break}if(k.isSymbolicLink())continue;const D=d.join(P,k.name);if(k.isDirectory()&&j(k.name))continue;const S=d.relative(w,D).replace(/\\/g,"/");(o?S.toLowerCase():S).includes(a)&&p.push({name:k.name,path:u(D),isDirectory:k.isDirectory()}),k.isDirectory()&&g<n&&m.push(D)}}return(g>=n||p.length>=s)&&(y=!0),p.sort((P,E)=>P.isDirectory===E.isDirectory?P.name.localeCompare(E.name):P.isDirectory?-1:1),{workspacePath:u(r),rootPath:u(w),keyword:i,items:p,truncated:y}}async function st(t,e,r,i){let o;try{o=await c.readFile(t,"utf-8")}catch{return{changed:!1,count:0}}const a=o;let s=0;if(i){const n=e.toLowerCase();let l=0;for(;;){const w=o.toLowerCase().indexOf(n,l);if(w===-1)break;o=o.slice(0,w)+r+o.slice(w+e.length),s+=1;const f=r.length||1;l=w+f}}else{let n=0;for(;;){const l=o.indexOf(e,n);if(l===-1)break;o=o.slice(0,l)+r+o.slice(l+e.length),s+=1;const h=r.length||1;n=l+h}}return o===a?{changed:!1,count:0}:(await T(t),await c.writeFile(t,o,"utf-8"),{changed:!0,count:s})}async function ce(t){const{workspaceRoot:e}=await v(t.workspacePath),r=String(t.keyword||"").trim();if(!r)throw new Error("keyword is required");const i=String(t.replacement??""),o=!!t.ignoreCase,a=String(t.fileMask||"").trim(),s=String(t.filePath||"").trim();let n=0,l=0;if(s){const{resolvedFilePath:w}=await W({workspacePath:t.workspacePath,filePath:s}),f=await st(w,r,i,o);return f.changed&&(n=1,l=f.count),{workspacePath:u(e),keyword:r,replacement:i,changedFiles:n,changedMatches:l}}async function h(w){let f;try{f=await c.readdir(w,{withFileTypes:!0})}catch{return}for(const p of f){const m=C(e,d.join(w,p.name));if(p.isSymbolicLink())continue;if(p.isDirectory()){if(j(p.name))continue;await h(m);continue}if(!p.isFile()||a&&!at(p.name,a))continue;try{if((await c.stat(m)).size>nt)continue}catch{continue}const g=await st(m,r,i,o);g.changed&&(n+=1,l+=g.count)}}return await h(e),{workspacePath:u(e),keyword:r,replacement:i,changedFiles:n,changedMatches:l}}export{Xt as chmodWorkspaceEntry,Jt as createWorkspaceEntry,ee as deleteWorkspaceEntry,Wt as extractWorkspaceArchive,Kt as getWorkspaceEntryProperties,Gt as listWorkspaceDirectory,te as moveWorkspaceEntry,Vt as previewWorkspaceDocument,Ht as readWorkspaceFile,Qt as renameWorkspaceEntry,ce as replaceWorkspaceFiles,ie as resolveWorkspaceDownloadTarget,ne as searchWorkspaceFiles,se as searchWorkspacePaths,oe as streamWorkspaceDirectoryZip,re as uploadWorkspaceFiles,Yt as writeWorkspaceFile,ae as zipWorkspaceDirectoryToTemp};
|
|
2
2
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aws-runtime-bridge",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.120",
|
|
4
4
|
"description": "AgentsWorkStudio runtime bridge service for machine-level agent runtime integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"dist",
|
|
14
14
|
"scripts/ensure-bin-executable.mjs",
|
|
15
15
|
"scripts/ensure-playwright-browser.mjs",
|
|
16
|
+
"scripts/ensure-build-tools.mjs",
|
|
16
17
|
"package/aws-client-agent-mcp/dist",
|
|
17
18
|
"package/aws-client-agent-mcp/package.json",
|
|
18
19
|
"package/aws-client-agent-mcp/README.md",
|
|
@@ -39,6 +40,7 @@
|
|
|
39
40
|
"build:sdk": "npm run build --prefix package/cc-switch-sdk",
|
|
40
41
|
"build:all": "npm run build:sdk && npm run build:acode && npm run build:agent-mcp",
|
|
41
42
|
"prebuild": "npm run build:all",
|
|
43
|
+
"preinstall": "node scripts/ensure-build-tools.mjs",
|
|
42
44
|
"build": "tsc -p tsconfig.json && node scripts/minify-dist.mjs",
|
|
43
45
|
"postinstall": "node scripts/ensure-bin-executable.mjs && node scripts/ensure-playwright-browser.mjs",
|
|
44
46
|
"pretypecheck": "npm run build:all",
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* preinstall 钩子:Linux 上确保 node-pty 等原生依赖编译所需的 C/C++ 工具链已就绪。
|
|
4
|
+
*
|
|
5
|
+
* 背景:node-pty(终端 PTY)的 npm 包只为 darwin/win32 提供预编译产物,
|
|
6
|
+
* Linux 上安装时总是走 node-gyp 源码编译(其 scripts/prebuild.js 检测到
|
|
7
|
+
* prebuilds/linux-x64 不存在即回退编译)。若服务器缺少 g++/make/python3,
|
|
8
|
+
* `npm install -g aws-runtime-bridge` 会在编译 node-pty 时报
|
|
9
|
+
* 「make: g++: No such file or directory」直接失败。
|
|
10
|
+
*
|
|
11
|
+
* 本脚本在依赖安装之前(preinstall 阶段)自动补齐工具链:
|
|
12
|
+
* - Linux + root(如 sudo npm install -g):按发行版自动安装
|
|
13
|
+
* (apt-get / dnf / yum / zypper / apk / pacman),一步到位。
|
|
14
|
+
* - Linux 非 root:输出可操作的 sudo 命令提示并以非零退出(缺少工具链时
|
|
15
|
+
* 依赖安装必然失败,提前给出明确报错比 node-gyp 的原始报错更友好)。
|
|
16
|
+
* - 其他平台(Windows/macOS):无需编译,静默跳过。
|
|
17
|
+
* - 幂等:工具链已就绪时直接跳过,重复安装无副作用。
|
|
18
|
+
* - 跳过:设置环境变量 AWS_BRIDGE_SKIP_BUILD_TOOLS=1(自行负责)。
|
|
19
|
+
*/
|
|
20
|
+
import { spawnSync } from "node:child_process";
|
|
21
|
+
|
|
22
|
+
const SKIP_ENV = "AWS_BRIDGE_SKIP_BUILD_TOOLS";
|
|
23
|
+
|
|
24
|
+
/** node-pty 源码编译所需的最小工具链。 */
|
|
25
|
+
const REQUIRED_TOOLS = ["g++", "make", "python3"];
|
|
26
|
+
|
|
27
|
+
/** 常见发行版包管理器及其编译工具链安装命令(顺序即探测优先级)。 */
|
|
28
|
+
const PACKAGE_MANAGERS = [
|
|
29
|
+
{
|
|
30
|
+
id: "apt-get",
|
|
31
|
+
update: ["apt-get", "update"],
|
|
32
|
+
install: ["apt-get", "install", "-y", "build-essential", "python3"],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "dnf",
|
|
36
|
+
install: ["dnf", "install", "-y", "gcc-c++", "make", "python3"],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "yum",
|
|
40
|
+
install: ["yum", "install", "-y", "gcc-c++", "make", "python3"],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "zypper",
|
|
44
|
+
install: ["zypper", "install", "-y", "gcc-c++", "make", "python3"],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "apk",
|
|
48
|
+
install: ["apk", "add", "--no-cache", "build-base", "python3"],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "pacman",
|
|
52
|
+
install: ["pacman", "-S", "--noconfirm", "base-devel", "python3"],
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
/** 探测命令是否可用(ENOENT 或非零退出视为缺失)。 */
|
|
57
|
+
function toolAvailable(bin) {
|
|
58
|
+
const result = spawnSync(bin, ["--version"], { stdio: "ignore" });
|
|
59
|
+
return !result.error && result.status === 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function run(argv, { fatal = true } = {}) {
|
|
63
|
+
const result = spawnSync(argv[0], argv.slice(1), { stdio: "inherit" });
|
|
64
|
+
if (result.error || result.status !== 0) {
|
|
65
|
+
if (fatal) {
|
|
66
|
+
console.error(`[aws-runtime-bridge] 命令执行失败:${argv.join(" ")}`);
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
console.warn(`[aws-runtime-bridge] 命令执行失败(已忽略):${argv.join(" ")}`);
|
|
70
|
+
}
|
|
71
|
+
return !result.error && result.status === 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function main() {
|
|
75
|
+
// 非 Linux 平台无需编译工具链
|
|
76
|
+
if (process.platform !== "linux") return;
|
|
77
|
+
|
|
78
|
+
const missing = REQUIRED_TOOLS.filter((bin) => !toolAvailable(bin));
|
|
79
|
+
if (missing.length === 0) return; // 已就绪,幂等跳过
|
|
80
|
+
|
|
81
|
+
if (process.env[SKIP_ENV] === "1") {
|
|
82
|
+
console.warn(
|
|
83
|
+
`[aws-runtime-bridge] ${SKIP_ENV}=1,跳过编译工具链自动安装;` +
|
|
84
|
+
`但 Linux 上 node-pty 必须源码编译,缺少 ${missing.join("、")} 会导致安装失败`,
|
|
85
|
+
);
|
|
86
|
+
return; // 用户显式要求跳过,放行由后续 node-gyp 自行报错
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
|
|
90
|
+
const manualHint =
|
|
91
|
+
"Ubuntu/Debian:sudo apt-get update && sudo apt-get install -y build-essential python3\n" +
|
|
92
|
+
" Fedora/RHEL:sudo dnf install -y gcc-c++ make python3(旧版 CentOS 用 yum)\n" +
|
|
93
|
+
" openSUSE:sudo zypper install -y gcc-c++ make python3\n" +
|
|
94
|
+
" Alpine:sudo apk add --no-cache build-base python3";
|
|
95
|
+
|
|
96
|
+
if (!isRoot) {
|
|
97
|
+
console.error(
|
|
98
|
+
`[aws-runtime-bridge] Linux 上 node-pty 无预编译产物,需要现场编译,` +
|
|
99
|
+
`但当前缺少:${missing.join("、")}。请先用 root 安装编译工具链后重试:\n` +
|
|
100
|
+
manualHint,
|
|
101
|
+
);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const pm = PACKAGE_MANAGERS.find((entry) => toolAvailable(entry.id));
|
|
106
|
+
if (!pm) {
|
|
107
|
+
console.error(
|
|
108
|
+
`[aws-runtime-bridge] 未识别到支持的包管理器,且缺少编译工具链:${missing.join("、")}。` +
|
|
109
|
+
`请手动安装后重试:\n${manualHint}`,
|
|
110
|
+
);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log(
|
|
115
|
+
`[aws-runtime-bridge] 检测到缺少编译工具链(${missing.join("、")}),` +
|
|
116
|
+
`正在用 ${pm.id} 自动安装(node-pty 在 Linux 上需要源码编译)...`,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (pm.update && !run(pm.update, { fatal: false })) {
|
|
120
|
+
console.warn("[aws-runtime-bridge] 包索引更新失败,继续尝试直接安装...");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!run(pm.install)) {
|
|
124
|
+
console.error(
|
|
125
|
+
`[aws-runtime-bridge] 编译工具链自动安装失败,请手动执行后重试:\n${manualHint}`,
|
|
126
|
+
);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const stillMissing = REQUIRED_TOOLS.filter((bin) => !toolAvailable(bin));
|
|
131
|
+
if (stillMissing.length > 0) {
|
|
132
|
+
console.error(
|
|
133
|
+
`[aws-runtime-bridge] 安装后仍缺少:${stillMissing.join("、")},请手动安装后重试:\n${manualHint}`,
|
|
134
|
+
);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.log("[aws-runtime-bridge] 编译工具链已就绪,继续安装...");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
main();
|