aws-runtime-bridge 1.9.33 → 1.9.40

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.
Files changed (276) hide show
  1. package/dist/adapter/AcodeSdkAdapter.d.ts +35 -1
  2. package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -1
  3. package/dist/adapter/AcodeSdkAdapter.js +417 -40
  4. package/dist/adapter/ClaudeSdkAdapter.d.ts +6 -0
  5. package/dist/adapter/ClaudeSdkAdapter.d.ts.map +1 -1
  6. package/dist/adapter/ClaudeSdkAdapter.js +23 -1
  7. package/dist/adapter/idle-poll-loop.d.ts +2 -1
  8. package/dist/adapter/idle-poll-loop.d.ts.map +1 -1
  9. package/dist/adapter/types.d.ts +65 -3
  10. package/dist/adapter/types.d.ts.map +1 -1
  11. package/dist/adapter/types.js +5 -4
  12. package/dist/index.js +4 -0
  13. package/dist/routes/acode-todos.d.ts +11 -0
  14. package/dist/routes/acode-todos.d.ts.map +1 -0
  15. package/dist/routes/acode-todos.js +75 -0
  16. package/dist/routes/memory.d.ts +1 -1
  17. package/dist/routes/memory.d.ts.map +1 -1
  18. package/dist/routes/memory.js +3 -3
  19. package/dist/routes/pty.d.ts.map +1 -1
  20. package/dist/routes/pty.js +9 -0
  21. package/dist/routes/sub-agents.d.ts +14 -0
  22. package/dist/routes/sub-agents.d.ts.map +1 -0
  23. package/dist/routes/sub-agents.js +253 -0
  24. package/dist/routes/terminal.d.ts +9 -1
  25. package/dist/routes/terminal.d.ts.map +1 -1
  26. package/dist/routes/terminal.js +207 -22
  27. package/dist/services/agent-event-store.d.ts +1 -0
  28. package/dist/services/agent-event-store.d.ts.map +1 -1
  29. package/dist/services/event-delta-pusher.js +4 -4
  30. package/dist/services/session-output.d.ts +3 -0
  31. package/dist/services/session-output.d.ts.map +1 -1
  32. package/dist/services/session-output.js +38 -2
  33. package/dist/services/sub-agent-manager-instance.d.ts +4 -0
  34. package/dist/services/sub-agent-manager-instance.d.ts.map +1 -0
  35. package/dist/services/sub-agent-manager-instance.js +12 -0
  36. package/dist/services/workspace-files.d.ts +1 -0
  37. package/dist/services/workspace-files.d.ts.map +1 -1
  38. package/dist/services/workspace-files.js +57 -1
  39. package/package/acode/dist/ask-user-question-tool.d.ts +47 -0
  40. package/package/acode/dist/ask-user-question-tool.d.ts.map +1 -0
  41. package/package/acode/dist/ask-user-question-tool.js +233 -0
  42. package/package/acode/dist/built-in-file-tools.d.ts +2 -0
  43. package/package/acode/dist/built-in-file-tools.d.ts.map +1 -1
  44. package/package/acode/dist/compaction.d.ts +206 -0
  45. package/package/acode/dist/compaction.d.ts.map +1 -0
  46. package/package/acode/dist/compaction.js +664 -0
  47. package/package/acode/dist/config.d.ts +6 -1
  48. package/package/acode/dist/config.d.ts.map +1 -1
  49. package/package/acode/dist/config.js +27 -2
  50. package/package/acode/dist/index.d.ts +4 -0
  51. package/package/acode/dist/index.d.ts.map +1 -1
  52. package/package/acode/dist/index.js +4 -0
  53. package/package/acode/dist/runtime.d.ts +137 -0
  54. package/package/acode/dist/runtime.d.ts.map +1 -1
  55. package/package/acode/dist/runtime.js +974 -172
  56. package/package/acode/dist/skill-tool.d.ts +30 -0
  57. package/package/acode/dist/skill-tool.d.ts.map +1 -0
  58. package/package/acode/dist/skill-tool.js +174 -0
  59. package/package/acode/dist/sub-agent-manager.d.ts +169 -0
  60. package/package/acode/dist/sub-agent-manager.d.ts.map +1 -0
  61. package/package/acode/dist/sub-agent-manager.js +535 -0
  62. package/package/acode/dist/sub-agent-store.d.ts +33 -0
  63. package/package/acode/dist/sub-agent-store.d.ts.map +1 -0
  64. package/package/acode/dist/sub-agent-store.js +109 -0
  65. package/package/acode/dist/sub-agent-tools.d.ts +72 -0
  66. package/package/acode/dist/sub-agent-tools.d.ts.map +1 -0
  67. package/package/acode/dist/sub-agent-tools.js +429 -0
  68. package/package/acode/dist/sub-agent-types.d.ts +81 -0
  69. package/package/acode/dist/sub-agent-types.d.ts.map +1 -0
  70. package/package/acode/dist/sub-agent-types.js +8 -0
  71. package/package/acode/dist/todo-store.d.ts +42 -0
  72. package/package/acode/dist/todo-store.d.ts.map +1 -0
  73. package/package/acode/dist/todo-store.js +68 -0
  74. package/package/acode/dist/todo-tool.d.ts +6 -0
  75. package/package/acode/dist/todo-tool.d.ts.map +1 -0
  76. package/package/acode/dist/todo-tool.js +138 -0
  77. package/package/acode/dist/types.d.ts +22 -1
  78. package/package/acode/dist/types.d.ts.map +1 -1
  79. package/package/acode/package.json +13 -2
  80. package/package/aws-client-agent-mcp/dist/context-manager.test.js +0 -3
  81. package/package/aws-client-agent-mcp/dist/context-manager.test.js.map +1 -1
  82. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +83 -50
  83. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
  84. package/package/aws-client-agent-mcp/dist/mcp-server.js +451 -346
  85. package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
  86. package/package/aws-client-agent-mcp/dist/mcp-server.test.js +259 -241
  87. package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
  88. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +1 -19
  89. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts.map +1 -1
  90. package/package/aws-client-agent-mcp/dist/mcp-tools.js +4 -77
  91. package/package/aws-client-agent-mcp/dist/mcp-tools.js.map +1 -1
  92. package/package/aws-client-agent-mcp/dist/memory-store.d.ts +17 -40
  93. package/package/aws-client-agent-mcp/dist/memory-store.d.ts.map +1 -1
  94. package/package/aws-client-agent-mcp/dist/memory-store.js +62 -149
  95. package/package/aws-client-agent-mcp/dist/memory-store.js.map +1 -1
  96. package/package/aws-client-agent-mcp/dist/memory-store.test.js +175 -164
  97. package/package/aws-client-agent-mcp/dist/memory-store.test.js.map +1 -1
  98. package/package/aws-client-agent-mcp/dist/memory-tools.d.ts +3 -9
  99. package/package/aws-client-agent-mcp/dist/memory-tools.d.ts.map +1 -1
  100. package/package/aws-client-agent-mcp/dist/memory-tools.js +28 -55
  101. package/package/aws-client-agent-mcp/dist/memory-tools.js.map +1 -1
  102. package/package/aws-client-agent-mcp/dist/message-buffer.d.ts +27 -9
  103. package/package/aws-client-agent-mcp/dist/message-buffer.d.ts.map +1 -1
  104. package/package/aws-client-agent-mcp/dist/message-buffer.js +100 -23
  105. package/package/aws-client-agent-mcp/dist/message-buffer.js.map +1 -1
  106. package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts +1 -14
  107. package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts.map +1 -1
  108. package/package/aws-client-agent-mcp/dist/server-memory-store.js +7 -18
  109. package/package/aws-client-agent-mcp/dist/server-memory-store.js.map +1 -1
  110. package/package/aws-client-agent-mcp/dist/status-reporter.d.ts.map +1 -1
  111. package/package/aws-client-agent-mcp/dist/status-reporter.js +8 -5
  112. package/package/aws-client-agent-mcp/dist/status-reporter.js.map +1 -1
  113. package/package/aws-client-agent-mcp/dist/status-reporter.test.js +93 -0
  114. package/package/aws-client-agent-mcp/dist/status-reporter.test.js.map +1 -1
  115. package/package/aws-client-agent-mcp/dist/types.d.ts +3 -25
  116. package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
  117. package/package.json +17 -7
  118. package/dist/adapter/AcodeSdkAdapter.test.d.ts +0 -2
  119. package/dist/adapter/AcodeSdkAdapter.test.d.ts.map +0 -1
  120. package/dist/adapter/AcodeSdkAdapter.test.js +0 -2000
  121. package/dist/adapter/AdapterRegistry.test.d.ts +0 -5
  122. package/dist/adapter/AdapterRegistry.test.d.ts.map +0 -1
  123. package/dist/adapter/AdapterRegistry.test.js +0 -118
  124. package/dist/adapter/ClaudeSdkAdapter.test.d.ts +0 -2
  125. package/dist/adapter/ClaudeSdkAdapter.test.d.ts.map +0 -1
  126. package/dist/adapter/ClaudeSdkAdapter.test.js +0 -311
  127. package/dist/adapter/CodexSdkAdapter.test.d.ts +0 -2
  128. package/dist/adapter/CodexSdkAdapter.test.d.ts.map +0 -1
  129. package/dist/adapter/CodexSdkAdapter.test.js +0 -270
  130. package/dist/adapter/OpencodeSdkAdapter.test.d.ts +0 -2
  131. package/dist/adapter/OpencodeSdkAdapter.test.d.ts.map +0 -1
  132. package/dist/adapter/OpencodeSdkAdapter.test.js +0 -158
  133. package/dist/adapter/acode-tool-names.test.d.ts +0 -2
  134. package/dist/adapter/acode-tool-names.test.d.ts.map +0 -1
  135. package/dist/adapter/acode-tool-names.test.js +0 -28
  136. package/dist/adapter/adapter.test.d.ts +0 -5
  137. package/dist/adapter/adapter.test.d.ts.map +0 -1
  138. package/dist/adapter/adapter.test.js +0 -180
  139. package/dist/adapter/idle-poll-loop.test.d.ts +0 -2
  140. package/dist/adapter/idle-poll-loop.test.d.ts.map +0 -1
  141. package/dist/adapter/idle-poll-loop.test.js +0 -85
  142. package/dist/adapter/types.test.d.ts +0 -2
  143. package/dist/adapter/types.test.d.ts.map +0 -1
  144. package/dist/adapter/types.test.js +0 -50
  145. package/dist/adapters/cc-switch/mcp-acode.test.d.ts +0 -2
  146. package/dist/adapters/cc-switch/mcp-acode.test.d.ts.map +0 -1
  147. package/dist/adapters/cc-switch/mcp-acode.test.js +0 -80
  148. package/dist/adapters/cc-switch/mcp-claudecode.test.d.ts +0 -2
  149. package/dist/adapters/cc-switch/mcp-claudecode.test.d.ts.map +0 -1
  150. package/dist/adapters/cc-switch/mcp-claudecode.test.js +0 -75
  151. package/dist/adapters/cc-switch/mcp-codex.test.d.ts +0 -2
  152. package/dist/adapters/cc-switch/mcp-codex.test.d.ts.map +0 -1
  153. package/dist/adapters/cc-switch/mcp-codex.test.js +0 -126
  154. package/dist/adapters/cc-switch/mcp-opencode.test.d.ts +0 -2
  155. package/dist/adapters/cc-switch/mcp-opencode.test.d.ts.map +0 -1
  156. package/dist/adapters/cc-switch/mcp-opencode.test.js +0 -62
  157. package/dist/adapters/cc-switch/skill-directory.test.d.ts +0 -2
  158. package/dist/adapters/cc-switch/skill-directory.test.d.ts.map +0 -1
  159. package/dist/adapters/cc-switch/skill-directory.test.js +0 -74
  160. package/dist/config.test.d.ts +0 -2
  161. package/dist/config.test.d.ts.map +0 -1
  162. package/dist/config.test.js +0 -107
  163. package/dist/routes/ai-sources.test.d.ts +0 -2
  164. package/dist/routes/ai-sources.test.d.ts.map +0 -1
  165. package/dist/routes/ai-sources.test.js +0 -169
  166. package/dist/routes/file-browser.test.d.ts +0 -5
  167. package/dist/routes/file-browser.test.d.ts.map +0 -1
  168. package/dist/routes/file-browser.test.js +0 -155
  169. package/dist/routes/git.test.d.ts +0 -2
  170. package/dist/routes/git.test.d.ts.map +0 -1
  171. package/dist/routes/git.test.js +0 -330
  172. package/dist/routes/instance.test.d.ts +0 -5
  173. package/dist/routes/instance.test.d.ts.map +0 -1
  174. package/dist/routes/instance.test.js +0 -389
  175. package/dist/routes/mcp.test.d.ts +0 -5
  176. package/dist/routes/mcp.test.d.ts.map +0 -1
  177. package/dist/routes/mcp.test.js +0 -92
  178. package/dist/routes/memory-route-wiring.test.d.ts +0 -2
  179. package/dist/routes/memory-route-wiring.test.d.ts.map +0 -1
  180. package/dist/routes/memory-route-wiring.test.js +0 -15
  181. package/dist/routes/memory.test.d.ts +0 -2
  182. package/dist/routes/memory.test.d.ts.map +0 -1
  183. package/dist/routes/memory.test.js +0 -177
  184. package/dist/routes/properties.test.d.ts +0 -5
  185. package/dist/routes/properties.test.d.ts.map +0 -1
  186. package/dist/routes/properties.test.js +0 -72
  187. package/dist/routes/pty.test.d.ts +0 -2
  188. package/dist/routes/pty.test.d.ts.map +0 -1
  189. package/dist/routes/pty.test.js +0 -82
  190. package/dist/routes/runtime-binding.test.d.ts +0 -2
  191. package/dist/routes/runtime-binding.test.d.ts.map +0 -1
  192. package/dist/routes/runtime-binding.test.js +0 -32
  193. package/dist/routes/sessions.test.d.ts +0 -5
  194. package/dist/routes/sessions.test.d.ts.map +0 -1
  195. package/dist/routes/sessions.test.js +0 -86
  196. package/dist/routes/skills.test.d.ts +0 -5
  197. package/dist/routes/skills.test.d.ts.map +0 -1
  198. package/dist/routes/skills.test.js +0 -50
  199. package/dist/routes/system-metrics.test.d.ts +0 -2
  200. package/dist/routes/system-metrics.test.d.ts.map +0 -1
  201. package/dist/routes/system-metrics.test.js +0 -16
  202. package/dist/routes/terminal.stop-options.test.d.ts +0 -2
  203. package/dist/routes/terminal.stop-options.test.d.ts.map +0 -1
  204. package/dist/routes/terminal.stop-options.test.js +0 -39
  205. package/dist/routes/terminal.test.d.ts +0 -2
  206. package/dist/routes/terminal.test.d.ts.map +0 -1
  207. package/dist/routes/terminal.test.js +0 -746
  208. package/dist/routes/yml.test.d.ts +0 -5
  209. package/dist/routes/yml.test.d.ts.map +0 -1
  210. package/dist/routes/yml.test.js +0 -58
  211. package/dist/services/ai-source-apply.test.d.ts +0 -2
  212. package/dist/services/ai-source-apply.test.d.ts.map +0 -1
  213. package/dist/services/ai-source-apply.test.js +0 -138
  214. package/dist/services/auto-register.test.d.ts +0 -2
  215. package/dist/services/auto-register.test.d.ts.map +0 -1
  216. package/dist/services/auto-register.test.js +0 -184
  217. package/dist/services/aws-client-agent-mcp.test.d.ts +0 -2
  218. package/dist/services/aws-client-agent-mcp.test.d.ts.map +0 -1
  219. package/dist/services/aws-client-agent-mcp.test.js +0 -242
  220. package/dist/services/bridge-package-publish-hooks.test.d.ts +0 -2
  221. package/dist/services/bridge-package-publish-hooks.test.d.ts.map +0 -1
  222. package/dist/services/bridge-package-publish-hooks.test.js +0 -136
  223. package/dist/services/cc-switch-discovery.test.d.ts +0 -2
  224. package/dist/services/cc-switch-discovery.test.d.ts.map +0 -1
  225. package/dist/services/cc-switch-discovery.test.js +0 -56
  226. package/dist/services/cli-commands.test.d.ts +0 -2
  227. package/dist/services/cli-commands.test.d.ts.map +0 -1
  228. package/dist/services/cli-commands.test.js +0 -230
  229. package/dist/services/instance-init-service.test.d.ts +0 -2
  230. package/dist/services/instance-init-service.test.d.ts.map +0 -1
  231. package/dist/services/instance-init-service.test.js +0 -140
  232. package/dist/services/instance-state.test.d.ts +0 -2
  233. package/dist/services/instance-state.test.d.ts.map +0 -1
  234. package/dist/services/instance-state.test.js +0 -245
  235. package/dist/services/mcp-launch-binding-queue.test.d.ts +0 -2
  236. package/dist/services/mcp-launch-binding-queue.test.d.ts.map +0 -1
  237. package/dist/services/mcp-launch-binding-queue.test.js +0 -181
  238. package/dist/services/runtime-binding.test.d.ts +0 -2
  239. package/dist/services/runtime-binding.test.d.ts.map +0 -1
  240. package/dist/services/runtime-binding.test.js +0 -78
  241. package/dist/services/runtime-lifecycle-policy.test.d.ts +0 -2
  242. package/dist/services/runtime-lifecycle-policy.test.d.ts.map +0 -1
  243. package/dist/services/runtime-lifecycle-policy.test.js +0 -41
  244. package/dist/services/session-output.test.d.ts +0 -5
  245. package/dist/services/session-output.test.d.ts.map +0 -1
  246. package/dist/services/session-output.test.js +0 -177
  247. package/dist/services/startup-config-wizard.test.d.ts +0 -2
  248. package/dist/services/startup-config-wizard.test.d.ts.map +0 -1
  249. package/dist/services/startup-config-wizard.test.js +0 -184
  250. package/dist/services/terminal-persistence.test.d.ts +0 -5
  251. package/dist/services/terminal-persistence.test.d.ts.map +0 -1
  252. package/dist/services/terminal-persistence.test.js +0 -159
  253. package/dist/services/tool-installer.test.d.ts +0 -2
  254. package/dist/services/tool-installer.test.d.ts.map +0 -1
  255. package/dist/services/tool-installer.test.js +0 -225
  256. package/dist/services/workspace-files.test.d.ts +0 -2
  257. package/dist/services/workspace-files.test.d.ts.map +0 -1
  258. package/dist/services/workspace-files.test.js +0 -648
  259. package/dist/services/workspace-watch.test.d.ts +0 -2
  260. package/dist/services/workspace-watch.test.d.ts.map +0 -1
  261. package/dist/services/workspace-watch.test.js +0 -38
  262. package/dist/utils/file-utils.test.d.ts +0 -2
  263. package/dist/utils/file-utils.test.d.ts.map +0 -1
  264. package/dist/utils/file-utils.test.js +0 -244
  265. package/dist/utils/logger.test.d.ts +0 -2
  266. package/dist/utils/logger.test.d.ts.map +0 -1
  267. package/dist/utils/logger.test.js +0 -93
  268. package/dist/utils/sdk-package-loader.test.d.ts +0 -2
  269. package/dist/utils/sdk-package-loader.test.d.ts.map +0 -1
  270. package/dist/utils/sdk-package-loader.test.js +0 -84
  271. package/dist/utils/validation.test.d.ts +0 -2
  272. package/dist/utils/validation.test.d.ts.map +0 -1
  273. package/dist/utils/validation.test.js +0 -88
  274. package/dist/utils/yaml-utils.test.d.ts +0 -2
  275. package/dist/utils/yaml-utils.test.d.ts.map +0 -1
  276. package/dist/utils/yaml-utils.test.js +0 -363
@@ -1,648 +0,0 @@
1
- import { createWriteStream, promises as fs } from 'node:fs';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
- import * as tar from 'tar';
5
- import { afterAll, beforeAll, describe, expect, it } from 'vitest';
6
- import { chmodWorkspaceEntry, createWorkspaceEntry, deleteWorkspaceEntry, extractWorkspaceArchive, getWorkspaceEntryProperties, listWorkspaceDirectory, moveWorkspaceEntry, previewWorkspaceDocument, readWorkspaceFile, renameWorkspaceEntry, resolveWorkspaceDownloadTarget, streamWorkspaceDirectoryZip, uploadWorkspaceFiles, writeWorkspaceFile, } from './workspace-files.js';
7
- const { ZipArchive } = await import('archiver');
8
- function crc32(input) {
9
- let crc = 0xffffffff;
10
- for (const byte of input) {
11
- crc ^= byte;
12
- for (let bit = 0; bit < 8; bit += 1) {
13
- crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));
14
- }
15
- }
16
- return (crc ^ 0xffffffff) >>> 0;
17
- }
18
- async function writeStoredZip(filePath, entryName, content) {
19
- const nameBytes = Buffer.from(entryName, 'utf-8');
20
- await writeStoredZipWithNameBytes(filePath, nameBytes, content);
21
- }
22
- async function writeStoredZipWithNameBytes(filePath, nameBytes, content) {
23
- const contentBytes = Buffer.from(content, 'utf-8');
24
- const checksum = crc32(contentBytes);
25
- const localHeader = Buffer.alloc(30);
26
- localHeader.writeUInt32LE(0x04034b50, 0);
27
- localHeader.writeUInt16LE(20, 4);
28
- localHeader.writeUInt16LE(0, 6);
29
- localHeader.writeUInt16LE(0, 8);
30
- localHeader.writeUInt32LE(0, 10);
31
- localHeader.writeUInt32LE(checksum, 14);
32
- localHeader.writeUInt32LE(contentBytes.length, 18);
33
- localHeader.writeUInt32LE(contentBytes.length, 22);
34
- localHeader.writeUInt16LE(nameBytes.length, 26);
35
- localHeader.writeUInt16LE(0, 28);
36
- const centralHeader = Buffer.alloc(46);
37
- centralHeader.writeUInt32LE(0x02014b50, 0);
38
- centralHeader.writeUInt16LE(20, 4);
39
- centralHeader.writeUInt16LE(20, 6);
40
- centralHeader.writeUInt16LE(0, 8);
41
- centralHeader.writeUInt16LE(0, 10);
42
- centralHeader.writeUInt32LE(0, 12);
43
- centralHeader.writeUInt32LE(checksum, 16);
44
- centralHeader.writeUInt32LE(contentBytes.length, 20);
45
- centralHeader.writeUInt32LE(contentBytes.length, 24);
46
- centralHeader.writeUInt16LE(nameBytes.length, 28);
47
- centralHeader.writeUInt16LE(0, 30);
48
- centralHeader.writeUInt16LE(0, 32);
49
- centralHeader.writeUInt16LE(0, 34);
50
- centralHeader.writeUInt16LE(0, 36);
51
- centralHeader.writeUInt32LE(0, 38);
52
- centralHeader.writeUInt32LE(0, 42);
53
- const centralDirectoryOffset = localHeader.length + nameBytes.length + contentBytes.length;
54
- const centralDirectorySize = centralHeader.length + nameBytes.length;
55
- const endRecord = Buffer.alloc(22);
56
- endRecord.writeUInt32LE(0x06054b50, 0);
57
- endRecord.writeUInt16LE(0, 4);
58
- endRecord.writeUInt16LE(0, 6);
59
- endRecord.writeUInt16LE(1, 8);
60
- endRecord.writeUInt16LE(1, 10);
61
- endRecord.writeUInt32LE(centralDirectorySize, 12);
62
- endRecord.writeUInt32LE(centralDirectoryOffset, 16);
63
- endRecord.writeUInt16LE(0, 20);
64
- await fs.writeFile(filePath, Buffer.concat([
65
- localHeader,
66
- nameBytes,
67
- contentBytes,
68
- centralHeader,
69
- nameBytes,
70
- endRecord
71
- ]));
72
- }
73
- async function writeTarHeaderOnlyEntry(filePath, entryName, typeFlag) {
74
- const header = Buffer.alloc(512, 0);
75
- header.write(entryName, 0, Math.min(Buffer.byteLength(entryName), 100), 'utf-8');
76
- header.write('0000644\0', 100, 'ascii');
77
- header.write('0000000\0', 108, 'ascii');
78
- header.write('0000000\0', 116, 'ascii');
79
- header.write('00000000000\0', 124, 'ascii');
80
- header.write('00000000000\0', 136, 'ascii');
81
- header.fill(0x20, 148, 156);
82
- header.write(typeFlag, 156, 'ascii');
83
- header.write('ustar\0', 257, 'ascii');
84
- header.write('00', 263, 'ascii');
85
- let checksum = 0;
86
- for (const byte of header) {
87
- checksum += byte;
88
- }
89
- header.write(checksum.toString(8).padStart(6, '0'), 148, 'ascii');
90
- header[154] = 0;
91
- header[155] = 0x20;
92
- await fs.writeFile(filePath, Buffer.concat([header, Buffer.alloc(1024, 0)]));
93
- }
94
- describe('workspace file service', () => {
95
- const tempRoot = path.join(os.tmpdir(), `aws-workspace-files-${Date.now()}`);
96
- const workspacePath = path.join(tempRoot, 'workspace');
97
- const nestedDir = path.join(workspacePath, 'src');
98
- const nestedFile = path.join(nestedDir, 'index.ts');
99
- let realWorkspacePath = '';
100
- let realNestedFile = '';
101
- beforeAll(async () => {
102
- await fs.mkdir(nestedDir, { recursive: true });
103
- await fs.writeFile(nestedFile, 'console.log("hello")\n', 'utf-8');
104
- await fs.writeFile(path.join(workspacePath, 'README.md'), '# test\n', 'utf-8');
105
- realWorkspacePath = (await fs.realpath(workspacePath)).replace(/\\/g, '/');
106
- realNestedFile = (await fs.realpath(nestedFile)).replace(/\\/g, '/');
107
- });
108
- afterAll(async () => {
109
- await Promise.all([
110
- path.join(workspacePath, 'outside-secret-link.txt'),
111
- path.join(workspacePath, 'outside-link'),
112
- path.join(tempRoot, 'archive-link')
113
- ].map(async (linkPath) => {
114
- try {
115
- const stat = await fs.lstat(linkPath);
116
- if (stat.isSymbolicLink()) {
117
- await fs.unlink(linkPath);
118
- }
119
- }
120
- catch {
121
- // Best-effort cleanup for platform-dependent symlink tests.
122
- }
123
- }));
124
- await fs.rm(tempRoot, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
125
- }, 60_000);
126
- it('lists workspace root when request path is empty', async () => {
127
- const result = await listWorkspaceDirectory({
128
- workspacePath,
129
- targetPath: ''
130
- });
131
- expect(result.currentPath).toBe(realWorkspacePath);
132
- expect(result.workspacePath).toBe(realWorkspacePath);
133
- expect(result.items.some((item) => item.name === 'src' && item.isDirectory)).toBe(true);
134
- expect(result.items.some((item) => item.name === 'README.md' && !item.isDirectory)).toBe(true);
135
- expect(result.items.find((item) => item.name === 'README.md')?.size).toBe(Buffer.byteLength('# test\n'));
136
- expect(result.items.every((item) => !item.path.includes('\\'))).toBe(true);
137
- });
138
- it('rejects reading files outside workspace', async () => {
139
- await expect(readWorkspaceFile({
140
- workspacePath,
141
- filePath: path.join(tempRoot, 'outside.txt')
142
- })).rejects.toThrow(/outside workspace/);
143
- });
144
- it('reads and writes text files within workspace', async () => {
145
- const beforeRead = await readWorkspaceFile({
146
- workspacePath,
147
- filePath: nestedFile
148
- });
149
- expect(beforeRead.content).toBe('console.log("hello")\n');
150
- expect(beforeRead.workspacePath).toBe(realWorkspacePath);
151
- expect(beforeRead.filePath).toBe(realNestedFile);
152
- await writeWorkspaceFile({
153
- workspacePath,
154
- filePath: nestedFile,
155
- content: 'console.log("updated")\n'
156
- });
157
- const afterRead = await readWorkspaceFile({
158
- workspacePath,
159
- filePath: nestedFile
160
- });
161
- expect(afterRead.content).toBe('console.log("updated")\n');
162
- });
163
- it('creates, renames, and deletes files and directories within workspace', async () => {
164
- const featureDir = path.join(workspacePath, 'feature');
165
- const createdFile = path.join(featureDir, 'demo.txt');
166
- const renamedFile = path.join(featureDir, 'demo-renamed.txt');
167
- const renamedDir = path.join(workspacePath, 'feature-renamed');
168
- await createWorkspaceEntry({
169
- workspacePath,
170
- targetPath: workspacePath,
171
- entryName: 'feature',
172
- isDirectory: true
173
- });
174
- await createWorkspaceEntry({
175
- workspacePath,
176
- targetPath: featureDir,
177
- entryName: 'demo.txt',
178
- isDirectory: false
179
- });
180
- const createdContent = await readWorkspaceFile({
181
- workspacePath,
182
- filePath: createdFile
183
- });
184
- expect(createdContent.content).toBe('');
185
- const renameFileResult = await renameWorkspaceEntry({
186
- workspacePath,
187
- targetPath: createdFile,
188
- newName: 'demo-renamed.txt'
189
- });
190
- expect(renameFileResult.targetPath).toBe((await fs.realpath(renamedFile)).replace(/\\/g, '/'));
191
- const renameDirResult = await renameWorkspaceEntry({
192
- workspacePath,
193
- targetPath: featureDir,
194
- newName: 'feature-renamed'
195
- });
196
- expect(renameDirResult.targetPath).toBe((await fs.realpath(renamedDir)).replace(/\\/g, '/'));
197
- const listRenamedDirectory = await listWorkspaceDirectory({
198
- workspacePath,
199
- targetPath: renamedDir
200
- });
201
- expect(listRenamedDirectory.items.some((item) => item.name === 'demo-renamed.txt')).toBe(true);
202
- await deleteWorkspaceEntry({
203
- workspacePath,
204
- targetPath: renamedDir
205
- });
206
- await expect(fs.access(renamedDir)).rejects.toThrow();
207
- });
208
- it('returns workspace entry properties with image dimensions', async () => {
209
- const pngPath = path.join(workspacePath, 'tiny.png');
210
- const pngBytes = Buffer.from('89504e470d0a1a0a0000000d4948445200000002000000030802000000947ddc6d0000000049454e44ae426082', 'hex');
211
- await fs.writeFile(pngPath, pngBytes);
212
- const result = await getWorkspaceEntryProperties({
213
- workspacePath,
214
- targetPath: pngPath
215
- });
216
- expect(result.workspacePath).toBe(realWorkspacePath);
217
- expect(result.targetPath).toBe((await fs.realpath(pngPath)).replace(/\\/g, '/'));
218
- expect(result.name).toBe('tiny.png');
219
- expect(result.isDirectory).toBe(false);
220
- expect(result.size).toBe(pngBytes.length);
221
- expect(result.modeOctal).toMatch(/^[0-7]{4}$/);
222
- expect(result.permissions).toMatch(/^[r-][w-][xsS-][r-][w-][xsS-][r-][w-][xtT-]$/);
223
- expect(result.image).toEqual({ format: 'png', width: 2, height: 3 });
224
- });
225
- it('returns read-only properties for workspace root', async () => {
226
- const result = await getWorkspaceEntryProperties({
227
- workspacePath,
228
- targetPath: workspacePath
229
- });
230
- expect(result.workspacePath).toBe(realWorkspacePath);
231
- expect(result.targetPath).toBe(realWorkspacePath);
232
- expect(result.isDirectory).toBe(true);
233
- expect(result.name).toBe(path.basename(realWorkspacePath));
234
- });
235
- it('changes workspace entry permissions non-recursively and recursively', async () => {
236
- if (process.platform === 'win32') {
237
- return;
238
- }
239
- const permissionDir = path.join(workspacePath, 'permission-dir');
240
- const nestedPermissionFile = path.join(permissionDir, 'nested.txt');
241
- await fs.mkdir(permissionDir, { recursive: true });
242
- await fs.writeFile(nestedPermissionFile, 'nested', 'utf-8');
243
- const nonRecursiveResult = await chmodWorkspaceEntry({
244
- workspacePath,
245
- targetPath: permissionDir,
246
- mode: '700',
247
- recursive: false
248
- });
249
- expect(nonRecursiveResult.changedCount).toBe(1);
250
- expect((await fs.stat(permissionDir)).mode & 0o777).toBe(0o700);
251
- expect((await fs.stat(nestedPermissionFile)).mode & 0o777).not.toBe(0o700);
252
- const recursiveResult = await chmodWorkspaceEntry({
253
- workspacePath,
254
- targetPath: permissionDir,
255
- mode: '755',
256
- recursive: true
257
- });
258
- expect(recursiveResult.changedCount).toBe(2);
259
- expect((await fs.stat(permissionDir)).mode & 0o777).toBe(0o755);
260
- expect((await fs.stat(nestedPermissionFile)).mode & 0o777).toBe(0o755);
261
- });
262
- it('rejects recursive permission changes through symlink children', async () => {
263
- if (process.platform === 'win32') {
264
- return;
265
- }
266
- const outsideSecret = path.join(tempRoot, 'chmod-outside.txt');
267
- const symlinkDirectory = path.join(workspacePath, 'symlink-permission-dir');
268
- const symlinkPath = path.join(symlinkDirectory, 'outside-link.txt');
269
- await fs.writeFile(outsideSecret, 'secret', 'utf-8');
270
- await fs.mkdir(symlinkDirectory, { recursive: true });
271
- await fs.symlink(outsideSecret, symlinkPath);
272
- await expect(chmodWorkspaceEntry({
273
- workspacePath,
274
- targetPath: symlinkDirectory,
275
- mode: '755',
276
- recursive: true
277
- })).rejects.toThrow(/symbolic link/i);
278
- });
279
- it('rejects invalid workspace mutations', async () => {
280
- await expect(createWorkspaceEntry({
281
- workspacePath,
282
- targetPath: workspacePath,
283
- entryName: '../escape.txt',
284
- isDirectory: false
285
- })).rejects.toThrow(/plain file or directory name/);
286
- await expect(renameWorkspaceEntry({
287
- workspacePath,
288
- targetPath: workspacePath,
289
- newName: 'renamed-root'
290
- })).rejects.toThrow(/Workspace root cannot be modified/);
291
- await expect(deleteWorkspaceEntry({
292
- workspacePath,
293
- targetPath: workspacePath
294
- })).rejects.toThrow(/Workspace root cannot be modified/);
295
- });
296
- it('moves files and rejects moving directories into themselves', async () => {
297
- const moveSourceDir = path.join(workspacePath, 'move-source');
298
- const moveDestinationDir = path.join(workspacePath, 'move-destination');
299
- const fileToMove = path.join(moveSourceDir, 'move-me.txt');
300
- await fs.mkdir(moveSourceDir, { recursive: true });
301
- await fs.mkdir(moveDestinationDir, { recursive: true });
302
- await fs.writeFile(fileToMove, 'move me', 'utf-8');
303
- const result = await moveWorkspaceEntry({
304
- workspacePath,
305
- targetPath: fileToMove,
306
- destinationPath: moveDestinationDir,
307
- });
308
- const movedPath = path.join(moveDestinationDir, 'move-me.txt');
309
- expect(result.sourcePath).toBe(fileToMove.replace(/\\/g, '/'));
310
- expect(result.targetPath).toBe((await fs.realpath(movedPath)).replace(/\\/g, '/'));
311
- await expect(fs.access(fileToMove)).rejects.toThrow();
312
- await expect(fs.readFile(movedPath, 'utf-8')).resolves.toBe('move me');
313
- const childDestination = path.join(moveSourceDir, 'child');
314
- await fs.mkdir(childDestination, { recursive: true });
315
- await expect(moveWorkspaceEntry({
316
- workspacePath,
317
- targetPath: moveSourceDir,
318
- destinationPath: childDestination,
319
- })).rejects.toThrow(/itself or its descendant/);
320
- });
321
- it('rejects unsafe workspace move targets', async () => {
322
- const moveSafetyDir = path.join(workspacePath, 'move-safety');
323
- const moveSafetyFile = path.join(moveSafetyDir, 'safe.txt');
324
- const moveSafetyDestination = path.join(workspacePath, 'move-safety-dest');
325
- await fs.mkdir(moveSafetyDir, { recursive: true });
326
- await fs.mkdir(moveSafetyDestination, { recursive: true });
327
- await fs.writeFile(moveSafetyFile, 'safe', 'utf-8');
328
- await fs.writeFile(path.join(moveSafetyDestination, 'safe.txt'), 'exists', 'utf-8');
329
- await expect(moveWorkspaceEntry({
330
- workspacePath,
331
- targetPath: workspacePath,
332
- destinationPath: moveSafetyDestination,
333
- })).rejects.toThrow(/Workspace root cannot be modified/);
334
- await expect(moveWorkspaceEntry({
335
- workspacePath,
336
- targetPath: path.join(tempRoot, 'outside.txt'),
337
- destinationPath: moveSafetyDestination,
338
- })).rejects.toThrow(/outside workspace/);
339
- await expect(moveWorkspaceEntry({
340
- workspacePath,
341
- targetPath: moveSafetyFile,
342
- destinationPath: tempRoot,
343
- })).rejects.toThrow(/outside workspace/);
344
- await expect(moveWorkspaceEntry({
345
- workspacePath,
346
- targetPath: moveSafetyFile,
347
- destinationPath: moveSafetyDestination,
348
- })).rejects.toThrow(/Path already exists/);
349
- });
350
- it('uploads files to a selected workspace directory', async () => {
351
- const uploadSource = path.join(tempRoot, 'upload-source.txt');
352
- const uploadTargetDir = path.join(workspacePath, 'uploads');
353
- await fs.mkdir(uploadTargetDir, { recursive: true });
354
- await fs.writeFile(uploadSource, 'uploaded content', 'utf-8');
355
- const result = await uploadWorkspaceFiles({
356
- workspacePath,
357
- targetPath: uploadTargetDir,
358
- files: [{ originalname: '../safe-name.txt', path: uploadSource, size: 16 }]
359
- });
360
- const uploadedPath = path.join(uploadTargetDir, 'safe-name.txt');
361
- expect(result.files[0].targetPath).toBe((await fs.realpath(uploadedPath)).replace(/\\/g, '/'));
362
- await expect(fs.readFile(uploadedPath, 'utf-8')).resolves.toBe('uploaded content');
363
- });
364
- it('preserves safe relative paths when uploading folders', async () => {
365
- const firstSource = path.join(tempRoot, 'folder-upload-first.txt');
366
- const secondSource = path.join(tempRoot, 'folder-upload-second.txt');
367
- const uploadTargetDir = path.join(workspacePath, 'folder-uploads');
368
- await fs.mkdir(uploadTargetDir, { recursive: true });
369
- await fs.writeFile(firstSource, 'first nested file', 'utf-8');
370
- await fs.writeFile(secondSource, 'second nested file', 'utf-8');
371
- const result = await uploadWorkspaceFiles({
372
- workspacePath,
373
- targetPath: uploadTargetDir,
374
- files: [
375
- { originalname: 'first.txt', relativePath: 'project/src/first.txt', path: firstSource, size: 17 },
376
- { originalname: 'second.txt', relativePath: 'project/docs/second.txt', path: secondSource, size: 18 }
377
- ]
378
- });
379
- const firstUploadedPath = path.join(uploadTargetDir, 'project', 'src', 'first.txt');
380
- const secondUploadedPath = path.join(uploadTargetDir, 'project', 'docs', 'second.txt');
381
- expect(result.files.map((file) => file.targetPath)).toEqual([
382
- (await fs.realpath(firstUploadedPath)).replace(/\\/g, '/'),
383
- (await fs.realpath(secondUploadedPath)).replace(/\\/g, '/')
384
- ]);
385
- await expect(fs.readFile(firstUploadedPath, 'utf-8')).resolves.toBe('first nested file');
386
- await expect(fs.readFile(secondUploadedPath, 'utf-8')).resolves.toBe('second nested file');
387
- });
388
- it('rejects unsafe relative upload paths', async () => {
389
- const uploadSource = path.join(tempRoot, 'unsafe-folder-upload.txt');
390
- const uploadTargetDir = path.join(workspacePath, 'unsafe-folder-uploads');
391
- await fs.mkdir(uploadTargetDir, { recursive: true });
392
- await fs.writeFile(uploadSource, 'unsafe content', 'utf-8');
393
- await expect(uploadWorkspaceFiles({
394
- workspacePath,
395
- targetPath: uploadTargetDir,
396
- files: [{ originalname: 'safe.txt', relativePath: 'project/../escape.txt', path: uploadSource, size: 14 }]
397
- })).rejects.toThrow(/entryName is invalid/);
398
- });
399
- it('uploads binary exe files and lists them after upload', async () => {
400
- const uploadSource = path.join(tempRoot, 'demo-app-source.exe');
401
- const uploadTargetDir = path.join(workspacePath, 'bin');
402
- const exeBytes = Buffer.from([0x4d, 0x5a, 0x90, 0x00, 0x03, 0x00, 0xff, 0x00]);
403
- await fs.mkdir(uploadTargetDir, { recursive: true });
404
- await fs.writeFile(uploadSource, exeBytes);
405
- const uploadResult = await uploadWorkspaceFiles({
406
- workspacePath,
407
- targetPath: uploadTargetDir,
408
- files: [{ originalname: 'demo-app.exe', path: uploadSource, size: exeBytes.length }]
409
- });
410
- const uploadedPath = path.join(uploadTargetDir, 'demo-app.exe');
411
- expect(uploadResult.files[0]).toMatchObject({
412
- fileName: 'demo-app.exe',
413
- size: exeBytes.length
414
- });
415
- await expect(fs.readFile(uploadedPath)).resolves.toEqual(exeBytes);
416
- const listedDirectory = await listWorkspaceDirectory({
417
- workspacePath,
418
- targetPath: uploadTargetDir
419
- });
420
- expect(listedDirectory.items.some((item) => item.name === 'demo-app.exe' && !item.isDirectory)).toBe(true);
421
- });
422
- it('decodes latin1-mojibake UTF-8 upload file names before saving', async () => {
423
- const uploadSource = path.join(tempRoot, 'springboot-source.zip');
424
- const uploadTargetDir = path.join(workspacePath, 'encoded-uploads');
425
- const originalName = 'springboot测试压缩包2.zip';
426
- const mojibakeName = Buffer.from(originalName, 'utf8').toString('latin1');
427
- await fs.mkdir(uploadTargetDir, { recursive: true });
428
- await fs.writeFile(uploadSource, 'zip bytes', 'utf-8');
429
- const uploadResult = await uploadWorkspaceFiles({
430
- workspacePath,
431
- targetPath: uploadTargetDir,
432
- files: [{ originalname: mojibakeName, path: uploadSource, size: 9 }]
433
- });
434
- const uploadedPath = path.join(uploadTargetDir, originalName);
435
- expect(uploadResult.files[0].fileName).toBe(originalName);
436
- await expect(fs.readFile(uploadedPath, 'utf-8')).resolves.toBe('zip bytes');
437
- const listedDirectory = await listWorkspaceDirectory({
438
- workspacePath,
439
- targetPath: uploadTargetDir
440
- });
441
- expect(listedDirectory.items.some((item) => item.name === originalName)).toBe(true);
442
- });
443
- it('extracts uploaded zip archives into a directory named after the archive', async () => {
444
- const uploadTargetDir = path.join(workspacePath, 'zip-auto-extract');
445
- const uploadSource = path.join(tempRoot, 'project-package-source.zip');
446
- await fs.mkdir(uploadTargetDir, { recursive: true });
447
- await new Promise((resolve, reject) => {
448
- const archive = new ZipArchive();
449
- const output = createWriteStream(uploadSource);
450
- output.on('close', resolve);
451
- archive.on('error', reject);
452
- archive.pipe(output);
453
- archive.append('first file', { name: 'a.txt' });
454
- archive.append('second file', { name: 'nested/b.txt' });
455
- archive.finalize().catch(reject);
456
- });
457
- const uploadResult = await uploadWorkspaceFiles({
458
- workspacePath,
459
- targetPath: uploadTargetDir,
460
- extractArchives: true,
461
- files: [{ originalname: 'project-package.zip', path: uploadSource, size: (await fs.stat(uploadSource)).size }]
462
- });
463
- const extractedRoot = path.join(uploadTargetDir, 'project-package');
464
- expect(uploadResult.files[0].extracted?.targetPath).toBe((await fs.realpath(extractedRoot)).replace(/\\/g, '/'));
465
- expect(uploadResult.files[0].extracted?.extractedEntries).toBe(2);
466
- await expect(fs.readFile(path.join(extractedRoot, 'a.txt'), 'utf-8')).resolves.toBe('first file');
467
- await expect(fs.readFile(path.join(extractedRoot, 'nested', 'b.txt'), 'utf-8')).resolves.toBe('second file');
468
- await expect(fs.access(path.join(uploadTargetDir, 'a.txt'))).rejects.toThrow();
469
- });
470
- it('extracts uploaded tar archives into a directory named after the archive', async () => {
471
- const uploadTargetDir = path.join(workspacePath, 'tar-auto-extract');
472
- const tarSourceDir = path.join(tempRoot, 'tar-source');
473
- const uploadSource = path.join(tempRoot, 'bundle-source.tar');
474
- await fs.mkdir(path.join(tarSourceDir, 'nested'), { recursive: true });
475
- await fs.mkdir(uploadTargetDir, { recursive: true });
476
- await fs.writeFile(path.join(tarSourceDir, 'root.txt'), 'root tar file', 'utf-8');
477
- await fs.writeFile(path.join(tarSourceDir, 'nested', 'child.txt'), 'child tar file', 'utf-8');
478
- await tar.c({ file: uploadSource, cwd: tarSourceDir }, ['root.txt', 'nested/child.txt']);
479
- const uploadResult = await uploadWorkspaceFiles({
480
- workspacePath,
481
- targetPath: uploadTargetDir,
482
- extractArchives: true,
483
- files: [{ originalname: 'bundle.tar', path: uploadSource, size: (await fs.stat(uploadSource)).size }]
484
- });
485
- const extractedRoot = path.join(uploadTargetDir, 'bundle');
486
- expect(uploadResult.files[0].extracted?.targetPath).toBe((await fs.realpath(extractedRoot)).replace(/\\/g, '/'));
487
- expect(uploadResult.files[0].extracted?.extractedEntries).toBe(2);
488
- await expect(fs.readFile(path.join(extractedRoot, 'root.txt'), 'utf-8')).resolves.toBe('root tar file');
489
- await expect(fs.readFile(path.join(extractedRoot, 'nested', 'child.txt'), 'utf-8')).resolves.toBe('child tar file');
490
- await expect(fs.access(path.join(uploadTargetDir, 'root.txt'))).rejects.toThrow();
491
- });
492
- it('does not expose outside target size for file symlinks in directory listings', async () => {
493
- const outsideFile = path.join(tempRoot, 'outside-secret.txt');
494
- const symlinkPath = path.join(workspacePath, 'outside-secret-link.txt');
495
- await fs.writeFile(outsideFile, 'outside-secret-content', 'utf-8');
496
- try {
497
- await fs.symlink(outsideFile, symlinkPath, 'file');
498
- }
499
- catch (error) {
500
- const errorCode = typeof error === 'object' && error !== null ? Reflect.get(error, 'code') : undefined;
501
- if (errorCode === 'EPERM' || errorCode === 'EACCES') {
502
- return;
503
- }
504
- throw error;
505
- }
506
- const result = await listWorkspaceDirectory({
507
- workspacePath,
508
- targetPath: workspacePath
509
- });
510
- const symlinkItem = result.items.find((item) => item.name === 'outside-secret-link.txt');
511
- expect(symlinkItem).toBeDefined();
512
- expect(symlinkItem?.size).toBeUndefined();
513
- });
514
- it('resolves file and directory download targets safely', async () => {
515
- const fileTarget = await resolveWorkspaceDownloadTarget({
516
- workspacePath,
517
- targetPath: nestedFile
518
- });
519
- expect(fileTarget.isDirectory).toBe(false);
520
- expect(fileTarget.fileName).toBe('index.ts');
521
- const directoryTarget = await resolveWorkspaceDownloadTarget({
522
- workspacePath,
523
- targetPath: nestedDir
524
- });
525
- expect(directoryTarget.isDirectory).toBe(true);
526
- expect(directoryTarget.fileName).toBe('src.zip');
527
- });
528
- it('streams a workspace directory as a zip archive', async () => {
529
- const zipPath = path.join(tempRoot, 'src.zip');
530
- await streamWorkspaceDirectoryZip(nestedDir, createWriteStream(zipPath));
531
- const stat = await fs.stat(zipPath);
532
- expect(stat.size).toBeGreaterThan(0);
533
- });
534
- it('extracts zip archives and rejects path traversal entries', async () => {
535
- const goodZip = path.join(workspacePath, 'archive.zip');
536
- const badZip = path.join(workspacePath, 'bad.zip');
537
- const extractDir = path.join(workspacePath, 'extracted');
538
- await new Promise((resolve, reject) => {
539
- const archive = new ZipArchive();
540
- const output = createWriteStream(goodZip);
541
- output.on('close', resolve);
542
- archive.on('error', reject);
543
- archive.pipe(output);
544
- archive.append('zip content', { name: 'nested/file.txt' });
545
- archive.finalize().catch(reject);
546
- });
547
- const extracted = await extractWorkspaceArchive({
548
- workspacePath,
549
- archivePath: goodZip,
550
- outputPath: extractDir
551
- });
552
- expect(extracted.extractedEntries).toBe(1);
553
- await expect(fs.readFile(path.join(extractDir, 'nested', 'file.txt'), 'utf-8')).resolves.toBe('zip content');
554
- await writeStoredZip(badZip, '../escape.txt', 'escape');
555
- await expect(extractWorkspaceArchive({
556
- workspacePath,
557
- archivePath: badZip,
558
- outputPath: extractDir
559
- })).rejects.toThrow(/Archive entry path is invalid|outside workspace/);
560
- });
561
- it('extracts GBK encoded zip entry names as Chinese paths', async () => {
562
- const gbkZip = path.join(workspacePath, 'gbk-archive.zip');
563
- const extractDir = path.join(workspacePath, 'gbk-extracted');
564
- const chineseDirectoryName = 'springboot067摄影师社区2';
565
- const gbkEntryNameBytes = Buffer.from([
566
- 115, 112, 114, 105, 110, 103, 98, 111, 111, 116, 48, 54, 55,
567
- 201, 227, 211, 176, 202, 166, 201, 231, 199, 248, 50,
568
- 47, 114, 101, 97, 100, 109, 101, 46, 116, 120, 116
569
- ]);
570
- await writeStoredZipWithNameBytes(gbkZip, gbkEntryNameBytes, '中文目录内容');
571
- const extracted = await extractWorkspaceArchive({
572
- workspacePath,
573
- archivePath: gbkZip,
574
- outputPath: extractDir
575
- });
576
- expect(extracted.extractedEntries).toBe(1);
577
- await expect(fs.readFile(path.join(extractDir, chineseDirectoryName, 'readme.txt'), 'utf-8'))
578
- .resolves.toBe('中文目录内容');
579
- });
580
- it('returns docx preview metadata without parsing document contents', async () => {
581
- const docxPath = path.join(workspacePath, 'large-preview.docx');
582
- const largeImageLikePayload = Buffer.alloc(21 * 1024 * 1024, 1);
583
- await fs.writeFile(docxPath, largeImageLikePayload);
584
- const preview = await previewWorkspaceDocument({ workspacePath, filePath: docxPath });
585
- expect(preview.kind).toBe('html');
586
- expect(preview.content).toBe('');
587
- expect(preview.size).toBe(largeImageLikePayload.length);
588
- expect(preview.mtimeMs).toBeGreaterThan(0);
589
- });
590
- it('returns unsupported preview metadata for legacy doc files', async () => {
591
- const docPath = path.join(workspacePath, 'legacy.doc');
592
- await fs.writeFile(docPath, 'legacy word bytes', 'utf-8');
593
- const preview = await previewWorkspaceDocument({ workspacePath, filePath: docPath });
594
- expect(preview.kind).toBe('unsupported');
595
- expect(preview.message).toMatch(/暂不支持旧版 \.doc/);
596
- });
597
- it('rejects workspace access through symlink escapes', async () => {
598
- const outsideDir = path.join(tempRoot, 'outside');
599
- const symlinkPath = path.join(workspacePath, 'outside-link');
600
- await fs.mkdir(outsideDir, { recursive: true });
601
- await fs.writeFile(path.join(outsideDir, 'secret.txt'), 'secret', 'utf-8');
602
- try {
603
- await fs.symlink(outsideDir, symlinkPath, 'dir');
604
- }
605
- catch (error) {
606
- const errorCode = typeof error === 'object' && error !== null ? Reflect.get(error, 'code') : undefined;
607
- if (errorCode === 'EPERM') {
608
- return;
609
- }
610
- throw error;
611
- }
612
- await expect(readWorkspaceFile({
613
- workspacePath,
614
- filePath: path.join(symlinkPath, 'secret.txt')
615
- })).rejects.toThrow(/outside workspace/);
616
- });
617
- it('rejects tar archives with symbolic links', async () => {
618
- const archivePath = path.join(workspacePath, 'link.tar');
619
- const linkTarget = path.join(tempRoot, 'linked-target');
620
- const sourceLink = path.join(tempRoot, 'archive-link');
621
- await fs.mkdir(linkTarget, { recursive: true });
622
- try {
623
- await fs.symlink(linkTarget, sourceLink, 'dir');
624
- }
625
- catch (error) {
626
- const errorCode = typeof error === 'object' && error !== null ? Reflect.get(error, 'code') : undefined;
627
- if (errorCode === 'EPERM') {
628
- return;
629
- }
630
- throw error;
631
- }
632
- await tar.c({ file: archivePath, cwd: tempRoot }, ['archive-link']);
633
- await expect(extractWorkspaceArchive({
634
- workspacePath,
635
- archivePath,
636
- outputPath: workspacePath
637
- })).rejects.toThrow(/links are not supported/);
638
- });
639
- it('rejects tar archives with unsupported special entries', async () => {
640
- const archivePath = path.join(workspacePath, 'special-entry.tar');
641
- await writeTarHeaderOnlyEntry(archivePath, 'special-device', '3');
642
- await expect(extractWorkspaceArchive({
643
- workspacePath,
644
- archivePath,
645
- outputPath: workspacePath
646
- })).rejects.toThrow(/Unsupported archive entry type/);
647
- });
648
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=workspace-watch.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"workspace-watch.test.d.ts","sourceRoot":"","sources":["../../src/services/workspace-watch.test.ts"],"names":[],"mappings":""}