aws-runtime-bridge 1.9.78 → 1.9.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/AcodeSdkAdapter.d.ts +0 -1
- package/dist/adapter/AcodeSdkAdapter.js +11 -1986
- package/dist/adapter/AdapterRegistry.d.ts +0 -1
- package/dist/adapter/AdapterRegistry.js +2 -100
- package/dist/adapter/ClaudeSdkAdapter.d.ts +0 -1
- package/dist/adapter/ClaudeSdkAdapter.js +4 -1300
- package/dist/adapter/CodexSdkAdapter.d.ts +0 -1
- package/dist/adapter/CodexSdkAdapter.js +4 -836
- package/dist/adapter/OpencodeSdkAdapter.d.ts +0 -1
- package/dist/adapter/OpencodeSdkAdapter.js +4 -879
- package/dist/adapter/SdkProviderSpi.d.ts +0 -1
- package/dist/adapter/SdkProviderSpi.js +2 -55
- package/dist/adapter/acode-tool-names.d.ts +0 -1
- package/dist/adapter/acode-tool-names.js +2 -37
- package/dist/adapter/idle-poll-loop.d.ts +0 -1
- package/dist/adapter/idle-poll-loop.js +2 -59
- package/dist/adapter/index.d.ts +0 -1
- package/dist/adapter/index.js +2 -11
- package/dist/adapter/instance-ai-config-resolver.d.ts +0 -1
- package/dist/adapter/instance-ai-config-resolver.js +2 -46
- package/dist/adapter/types.d.ts +0 -1
- package/dist/adapter/types.js +2 -443
- package/dist/adapters/cc-switch/common.d.ts +0 -1
- package/dist/adapters/cc-switch/common.js +2 -61
- package/dist/adapters/cc-switch/index.d.ts +0 -1
- package/dist/adapters/cc-switch/index.js +2 -57
- package/dist/adapters/cc-switch/mcp-acode.d.ts +0 -1
- package/dist/adapters/cc-switch/mcp-acode.js +2 -71
- package/dist/adapters/cc-switch/mcp-claude.d.ts +0 -1
- package/dist/adapters/cc-switch/mcp-claude.js +2 -89
- package/dist/adapters/cc-switch/mcp-claudecode.d.ts +0 -1
- package/dist/adapters/cc-switch/mcp-claudecode.js +2 -77
- package/dist/adapters/cc-switch/mcp-codex.d.ts +0 -1
- package/dist/adapters/cc-switch/mcp-codex.js +11 -249
- package/dist/adapters/cc-switch/mcp-opencode.d.ts +0 -1
- package/dist/adapters/cc-switch/mcp-opencode.js +2 -97
- package/dist/adapters/cc-switch/mcp-placeholder.d.ts +0 -1
- package/dist/adapters/cc-switch/mcp-placeholder.js +2 -19
- package/dist/adapters/cc-switch/skill-acode.d.ts +0 -1
- package/dist/adapters/cc-switch/skill-acode.js +2 -12
- package/dist/adapters/cc-switch/skill-claude.d.ts +0 -1
- package/dist/adapters/cc-switch/skill-claude.js +2 -11
- package/dist/adapters/cc-switch/skill-claudecode.d.ts +0 -1
- package/dist/adapters/cc-switch/skill-claudecode.js +2 -11
- package/dist/adapters/cc-switch/skill-codex.d.ts +0 -1
- package/dist/adapters/cc-switch/skill-codex.js +2 -6
- package/dist/adapters/cc-switch/skill-directory.d.ts +0 -1
- package/dist/adapters/cc-switch/skill-directory.js +3 -137
- package/dist/adapters/cc-switch/skill-opencode.d.ts +0 -1
- package/dist/adapters/cc-switch/skill-opencode.js +2 -11
- package/dist/adapters/cc-switch/skill-placeholder.d.ts +0 -1
- package/dist/adapters/cc-switch/skill-placeholder.js +2 -19
- package/dist/config.d.ts +0 -1
- package/dist/config.js +2 -290
- package/dist/index.d.ts +0 -1
- package/dist/index.js +2 -573
- package/dist/middleware/auth.d.ts +0 -1
- package/dist/middleware/auth.js +2 -98
- package/dist/routes/acode-todos.d.ts +0 -1
- package/dist/routes/acode-todos.js +2 -75
- package/dist/routes/ai-sources.d.ts +0 -1
- package/dist/routes/ai-sources.js +2 -132
- package/dist/routes/dashboard.d.ts +0 -1
- package/dist/routes/dashboard.js +182 -649
- package/dist/routes/events.d.ts +0 -1
- package/dist/routes/events.js +2 -22
- package/dist/routes/file-browser.d.ts +0 -1
- package/dist/routes/file-browser.js +2 -781
- package/dist/routes/file-diff-rollback.d.ts +0 -1
- package/dist/routes/file-diff-rollback.js +2 -301
- package/dist/routes/file-snapshot.d.ts +0 -1
- package/dist/routes/file-snapshot.js +2 -156
- package/dist/routes/git.d.ts +0 -1
- package/dist/routes/git.js +28 -1900
- package/dist/routes/instance.d.ts +0 -1
- package/dist/routes/instance.js +3 -748
- package/dist/routes/mcp.d.ts +0 -1
- package/dist/routes/mcp.js +2 -196
- package/dist/routes/processes.d.ts +0 -1
- package/dist/routes/processes.js +2 -252
- package/dist/routes/properties.d.ts +0 -1
- package/dist/routes/properties.js +2 -72
- package/dist/routes/pty.d.ts +0 -1
- package/dist/routes/pty.js +6 -560
- package/dist/routes/runtime-binding.d.ts +0 -1
- package/dist/routes/runtime-binding.js +2 -263
- package/dist/routes/sessions.d.ts +0 -1
- package/dist/routes/sessions.js +2 -758
- package/dist/routes/skills.d.ts +0 -1
- package/dist/routes/skills.js +2 -187
- package/dist/routes/sub-agents.d.ts +0 -1
- package/dist/routes/sub-agents.js +20 -279
- package/dist/routes/system-metrics.d.ts +0 -1
- package/dist/routes/system-metrics.js +2 -20
- package/dist/routes/task-context.d.ts +0 -1
- package/dist/routes/task-context.js +2 -78
- package/dist/routes/terminal.d.ts +0 -1
- package/dist/routes/terminal.js +54 -2882
- package/dist/routes/yml.d.ts +0 -1
- package/dist/routes/yml.js +2 -223
- package/dist/services/acode-package.d.ts +0 -1
- package/dist/services/acode-package.js +2 -111
- package/dist/services/agent-event-store.d.ts +0 -1
- package/dist/services/agent-event-store.js +2 -81
- package/dist/services/agent-process-manager.d.ts +0 -1
- package/dist/services/agent-process-manager.js +2 -796
- package/dist/services/ai-source-apply.d.ts +0 -1
- package/dist/services/ai-source-apply.js +2 -83
- package/dist/services/auto-register.d.ts +0 -1
- package/dist/services/auto-register.js +2 -1053
- package/dist/services/aws-client-agent-mcp.d.ts +0 -1
- package/dist/services/aws-client-agent-mcp.js +3 -337
- package/dist/services/bridge-package-publish-hooks.d.ts +0 -1
- package/dist/services/bridge-package-publish-hooks.js +2 -115
- package/dist/services/cc-switch-sdk.d.ts +0 -1
- package/dist/services/cc-switch-sdk.js +2 -135
- package/dist/services/cli-commands.d.ts +6 -1
- package/dist/services/cli-commands.js +6 -645
- package/dist/services/event-delta-pusher.d.ts +0 -1
- package/dist/services/event-delta-pusher.js +2 -95
- package/dist/services/fileSnapshotStore.d.ts +0 -1
- package/dist/services/fileSnapshotStore.js +2 -500
- package/dist/services/instance-ai-config-service.d.ts +0 -1
- package/dist/services/instance-ai-config-service.js +2 -174
- package/dist/services/instance-init-service.d.ts +0 -1
- package/dist/services/instance-init-service.js +2 -193
- package/dist/services/instance-service.d.ts +0 -1
- package/dist/services/instance-service.js +2 -237
- package/dist/services/instance-state.d.ts +0 -1
- package/dist/services/instance-state.js +2 -94
- package/dist/services/legacy-path-migration.d.ts +0 -1
- package/dist/services/legacy-path-migration.js +2 -66
- package/dist/services/lifecycle-state.d.ts +0 -1
- package/dist/services/lifecycle-state.js +3 -110
- package/dist/services/mcp-launch-binding-queue.d.ts +0 -1
- package/dist/services/mcp-launch-binding-queue.js +2 -175
- package/dist/services/orphan-monitor.d.ts +0 -1
- package/dist/services/orphan-monitor.js +2 -382
- package/dist/services/panel-auth.d.ts +0 -1
- package/dist/services/panel-auth.js +2 -168
- package/dist/services/process-detector.d.ts +0 -1
- package/dist/services/process-detector.js +18 -1019
- package/dist/services/process-registry.d.ts +0 -1
- package/dist/services/process-registry.js +2 -375
- package/dist/services/ring-buffer.d.ts +0 -1
- package/dist/services/ring-buffer.js +2 -103
- package/dist/services/runtime-binding.d.ts +0 -1
- package/dist/services/runtime-binding.js +6 -363
- package/dist/services/runtime-lifecycle-policy.d.ts +0 -1
- package/dist/services/runtime-lifecycle-policy.js +2 -35
- package/dist/services/session-output.d.ts +0 -1
- package/dist/services/session-output.js +5 -539
- package/dist/services/startup-config-wizard.d.ts +0 -1
- package/dist/services/startup-config-wizard.js +14 -258
- package/dist/services/sub-agent-manager-instance.d.ts +0 -1
- package/dist/services/sub-agent-manager-instance.js +2 -12
- package/dist/services/system-metrics.d.ts +0 -1
- package/dist/services/system-metrics.js +2 -252
- package/dist/services/terminal-persistence.d.ts +0 -1
- package/dist/services/terminal-persistence.js +2 -176
- package/dist/services/tool-installer.d.ts +0 -1
- package/dist/services/tool-installer.js +5 -751
- package/dist/services/tunnel-client.d.ts +0 -1
- package/dist/services/tunnel-client.js +2 -402
- package/dist/services/windows-service-manager.d.ts +3 -2
- package/dist/services/windows-service-manager.js +2 -54
- package/dist/services/workspace-files.d.ts +0 -1
- package/dist/services/workspace-files.js +2 -1275
- package/dist/services/workspace-watch.d.ts +0 -1
- package/dist/services/workspace-watch.js +2 -123
- package/dist/services/workspaceTaskChangeWatcher.d.ts +0 -1
- package/dist/services/workspaceTaskChangeWatcher.js +2 -483
- package/dist/types.d.ts +0 -1
- package/dist/types.js +1 -1
- package/dist/utils/file-utils.d.ts +0 -1
- package/dist/utils/file-utils.js +3 -208
- package/dist/utils/instance-ai-config-debug-logger.d.ts +0 -1
- package/dist/utils/instance-ai-config-debug-logger.js +4 -63
- package/dist/utils/logger.d.ts +0 -1
- package/dist/utils/logger.js +2 -154
- package/dist/utils/mcp-utils.d.ts +0 -1
- package/dist/utils/mcp-utils.js +2 -187
- package/dist/utils/path-utils.d.ts +0 -1
- package/dist/utils/path-utils.js +2 -44
- package/dist/utils/sdk-package-loader.d.ts +0 -1
- package/dist/utils/sdk-package-loader.js +2 -78
- package/dist/utils/validation.d.ts +0 -1
- package/dist/utils/validation.js +2 -53
- package/dist/utils/yaml-utils.d.ts +0 -1
- package/dist/utils/yaml-utils.js +2 -312
- package/node_modules/@cc-switch/sdk/dist/constants.d.ts +0 -1
- package/node_modules/@cc-switch/sdk/dist/constants.js +2 -63
- package/node_modules/@cc-switch/sdk/dist/errors.d.ts +0 -1
- package/node_modules/@cc-switch/sdk/dist/errors.js +2 -8
- package/node_modules/@cc-switch/sdk/dist/index.d.ts +0 -1
- package/node_modules/@cc-switch/sdk/dist/index.js +2 -9
- package/node_modules/@cc-switch/sdk/dist/schemas.d.ts +0 -1
- package/node_modules/@cc-switch/sdk/dist/schemas.js +2 -38
- package/node_modules/@cc-switch/sdk/dist/sdk.d.ts +0 -1
- package/node_modules/@cc-switch/sdk/dist/sdk.js +2 -470
- package/node_modules/@cc-switch/sdk/dist/services/ai-config-service.d.ts +0 -1
- package/node_modules/@cc-switch/sdk/dist/services/ai-config-service.js +4 -307
- package/node_modules/@cc-switch/sdk/dist/services/mcp-model.d.ts +0 -1
- package/node_modules/@cc-switch/sdk/dist/services/mcp-model.js +2 -37
- package/node_modules/@cc-switch/sdk/dist/services/mcp-service.d.ts +0 -1
- package/node_modules/@cc-switch/sdk/dist/services/mcp-service.js +2 -9
- package/node_modules/@cc-switch/sdk/dist/services/skill-model.d.ts +0 -1
- package/node_modules/@cc-switch/sdk/dist/services/skill-model.js +2 -41
- package/node_modules/@cc-switch/sdk/dist/services/skill-service.d.ts +0 -1
- package/node_modules/@cc-switch/sdk/dist/services/skill-service.js +2 -9
- package/node_modules/@cc-switch/sdk/dist/state.d.ts +0 -1
- package/node_modules/@cc-switch/sdk/dist/state.js +2 -19
- package/node_modules/@cc-switch/sdk/dist/types.d.ts +0 -1
- package/node_modules/@cc-switch/sdk/dist/types.js +1 -1
- package/node_modules/@cc-switch/sdk/dist/utils/fs.d.ts +0 -1
- package/node_modules/@cc-switch/sdk/dist/utils/fs.js +3 -114
- package/node_modules/@cc-switch/sdk/dist/utils/id.d.ts +0 -1
- package/node_modules/@cc-switch/sdk/dist/utils/id.js +2 -12
- package/node_modules/@cc-switch/sdk/package.json +1 -1
- package/package/acode/dist/ask-user-question-tool.d.ts +0 -1
- package/package/acode/dist/ask-user-question-tool.js +4 -233
- package/package/acode/dist/built-in-file-tools.d.ts +0 -1
- package/package/acode/dist/built-in-file-tools.js +4 -966
- package/package/acode/dist/built-in-symbols-tool.d.ts +0 -1
- package/package/acode/dist/built-in-symbols-tool.js +2 -117
- package/package/acode/dist/builtins/commandArtifacts.d.ts +0 -1
- package/package/acode/dist/builtins/commandArtifacts.js +9 -97
- package/package/acode/dist/builtins/commandOutputEvents.d.ts +0 -1
- package/package/acode/dist/builtins/commandOutputEvents.js +2 -56
- package/package/acode/dist/builtins/shellCommandPreparation.d.ts +0 -1
- package/package/acode/dist/builtins/shellCommandPreparation.js +5 -44
- package/package/acode/dist/builtins/shellCommandResult.d.ts +0 -1
- package/package/acode/dist/builtins/shellCommandResult.js +2 -71
- package/package/acode/dist/builtins/shellCommandRunner.d.ts +0 -1
- package/package/acode/dist/builtins/shellCommandRunner.js +2 -115
- package/package/acode/dist/builtins/shellOutputDecoder.d.ts +0 -1
- package/package/acode/dist/builtins/shellOutputDecoder.js +2 -94
- package/package/acode/dist/cli.d.ts +0 -1
- package/package/acode/dist/cli.js +4 -38
- package/package/acode/dist/commands/commandLoader.d.ts +0 -1
- package/package/acode/dist/commands/commandLoader.js +2 -33
- package/package/acode/dist/commands/commandParser.d.ts +0 -1
- package/package/acode/dist/commands/commandParser.js +3 -77
- package/package/acode/dist/commands/commandRegistry.d.ts +0 -1
- package/package/acode/dist/commands/commandRegistry.js +2 -30
- package/package/acode/dist/compaction.d.ts +0 -1
- package/package/acode/dist/compaction.js +7 -919
- package/package/acode/dist/config.d.ts +0 -1
- package/package/acode/dist/config.js +5 -105
- package/package/acode/dist/fs-utils.d.ts +0 -1
- package/package/acode/dist/fs-utils.js +2 -79
- package/package/acode/dist/hooks/hookBus.d.ts +0 -1
- package/package/acode/dist/hooks/hookBus.js +2 -40
- package/package/acode/dist/index.d.ts +0 -1
- package/package/acode/dist/index.js +2 -19
- package/package/acode/dist/mcpToolNames.d.ts +0 -1
- package/package/acode/dist/mcpToolNames.js +2 -17
- package/package/acode/dist/parsers/ast-cache.d.ts +0 -1
- package/package/acode/dist/parsers/ast-cache.js +2 -76
- package/package/acode/dist/parsers/language-detector.d.ts +0 -1
- package/package/acode/dist/parsers/language-detector.js +2 -44
- package/package/acode/dist/parsers/language-queries.d.ts +0 -1
- package/package/acode/dist/parsers/language-queries.js +12 -602
- package/package/acode/dist/parsers/symbol-extractor.d.ts +0 -1
- package/package/acode/dist/parsers/symbol-extractor.js +3 -164
- package/package/acode/dist/parsers/tree-sitter-loader.d.ts +0 -1
- package/package/acode/dist/parsers/tree-sitter-loader.js +2 -64
- package/package/acode/dist/parsers/types.d.ts +0 -1
- package/package/acode/dist/parsers/types.js +1 -7
- package/package/acode/dist/runtime.d.ts +0 -1
- package/package/acode/dist/runtime.js +86 -3771
- package/package/acode/dist/skill-tool.d.ts +0 -1
- package/package/acode/dist/skill-tool.js +5 -174
- package/package/acode/dist/sub-agent-manager.d.ts +0 -1
- package/package/acode/dist/sub-agent-manager.js +2 -1159
- package/package/acode/dist/sub-agent-store.d.ts +0 -1
- package/package/acode/dist/sub-agent-store.js +4 -236
- package/package/acode/dist/sub-agent-tools.d.ts +0 -1
- package/package/acode/dist/sub-agent-tools.js +15 -369
- package/package/acode/dist/sub-agent-types.d.ts +0 -1
- package/package/acode/dist/sub-agent-types.js +1 -8
- package/package/acode/dist/todo-store.d.ts +0 -1
- package/package/acode/dist/todo-store.js +2 -73
- package/package/acode/dist/todo-tool.d.ts +0 -1
- package/package/acode/dist/todo-tool.js +5 -121
- package/package/acode/dist/types.d.ts +0 -1
- package/package/acode/dist/types.js +1 -1
- package/package/acode/package.json +2 -2
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/agent-client.js +2 -890
- package/package/aws-client-agent-mcp/dist/agent-client.test.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/agent-client.test.js +0 -1
- package/package/aws-client-agent-mcp/dist/config.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/config.js +2 -243
- package/package/aws-client-agent-mcp/dist/config.test.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/config.test.js +0 -1
- package/package/aws-client-agent-mcp/dist/constants.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/constants.js +2 -24
- package/package/aws-client-agent-mcp/dist/context-manager.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/context-manager.js +2 -477
- package/package/aws-client-agent-mcp/dist/context-manager.test.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/context-manager.test.js +0 -1
- package/package/aws-client-agent-mcp/dist/http-client.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/http-client.js +2 -446
- package/package/aws-client-agent-mcp/dist/http-client.test.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/http-client.test.js +0 -1
- package/package/aws-client-agent-mcp/dist/index.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/index.js +2 -34
- package/package/aws-client-agent-mcp/dist/logger.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/logger.js +3 -178
- package/package/aws-client-agent-mcp/dist/logger.test.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/logger.test.js +0 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.js +38 -2709
- package/package/aws-client-agent-mcp/dist/mcp-server.test.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +0 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.js +22 -562
- package/package/aws-client-agent-mcp/dist/mcp-tools.test.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.test.js +0 -1
- package/package/aws-client-agent-mcp/dist/memory-store.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/memory-store.js +4 -826
- package/package/aws-client-agent-mcp/dist/memory-store.test.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/memory-store.test.js +0 -1
- package/package/aws-client-agent-mcp/dist/memory-tools.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/memory-tools.js +31 -294
- package/package/aws-client-agent-mcp/dist/message-buffer.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/message-buffer.js +2 -288
- package/package/aws-client-agent-mcp/dist/message-buffer.test.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/message-buffer.test.js +0 -1
- package/package/aws-client-agent-mcp/dist/messageContent.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/messageContent.js +2 -125
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js +2 -103
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js +0 -1
- package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/server-memory-store.js +2 -96
- package/package/aws-client-agent-mcp/dist/status-reporter.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.js +3 -327
- package/package/aws-client-agent-mcp/dist/status-reporter.test.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.test.js +0 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/types.js +2 -9
- package/package/aws-client-agent-mcp/dist/websocket-client.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.js +2 -677
- package/package/aws-client-agent-mcp/dist/websocket-client.test.d.ts +0 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.test.js +0 -1
- package/package/aws-client-agent-mcp/package.json +1 -1
- package/package/cc-switch-sdk/dist/constants.d.ts +0 -1
- package/package/cc-switch-sdk/dist/constants.js +2 -63
- package/package/cc-switch-sdk/dist/errors.d.ts +0 -1
- package/package/cc-switch-sdk/dist/errors.js +2 -8
- package/package/cc-switch-sdk/dist/index.d.ts +0 -1
- package/package/cc-switch-sdk/dist/index.js +2 -9
- package/package/cc-switch-sdk/dist/schemas.d.ts +0 -1
- package/package/cc-switch-sdk/dist/schemas.js +2 -38
- package/package/cc-switch-sdk/dist/sdk.d.ts +0 -1
- package/package/cc-switch-sdk/dist/sdk.js +2 -470
- package/package/cc-switch-sdk/dist/services/ai-config-service.d.ts +0 -1
- package/package/cc-switch-sdk/dist/services/ai-config-service.js +4 -307
- package/package/cc-switch-sdk/dist/services/mcp-model.d.ts +0 -1
- package/package/cc-switch-sdk/dist/services/mcp-model.js +2 -37
- package/package/cc-switch-sdk/dist/services/mcp-service.d.ts +0 -1
- package/package/cc-switch-sdk/dist/services/mcp-service.js +2 -9
- package/package/cc-switch-sdk/dist/services/skill-model.d.ts +0 -1
- package/package/cc-switch-sdk/dist/services/skill-model.js +2 -41
- package/package/cc-switch-sdk/dist/services/skill-service.d.ts +0 -1
- package/package/cc-switch-sdk/dist/services/skill-service.js +2 -9
- package/package/cc-switch-sdk/dist/state.d.ts +0 -1
- package/package/cc-switch-sdk/dist/state.js +2 -19
- package/package/cc-switch-sdk/dist/types.d.ts +0 -1
- package/package/cc-switch-sdk/dist/types.js +1 -1
- package/package/cc-switch-sdk/dist/utils/fs.d.ts +0 -1
- package/package/cc-switch-sdk/dist/utils/fs.js +3 -114
- package/package/cc-switch-sdk/dist/utils/id.d.ts +0 -1
- package/package/cc-switch-sdk/dist/utils/id.js +2 -12
- package/package/cc-switch-sdk/package.json +1 -1
- package/package.json +4 -2
- package/dist/adapter/AcodeSdkAdapter.d.ts.map +0 -1
- package/dist/adapter/AdapterRegistry.d.ts.map +0 -1
- package/dist/adapter/ClaudeSdkAdapter.d.ts.map +0 -1
- package/dist/adapter/CodexSdkAdapter.d.ts.map +0 -1
- package/dist/adapter/OpencodeSdkAdapter.d.ts.map +0 -1
- package/dist/adapter/SdkProviderSpi.d.ts.map +0 -1
- package/dist/adapter/acode-tool-names.d.ts.map +0 -1
- package/dist/adapter/idle-poll-loop.d.ts.map +0 -1
- package/dist/adapter/index.d.ts.map +0 -1
- package/dist/adapter/instance-ai-config-resolver.d.ts.map +0 -1
- package/dist/adapter/types.d.ts.map +0 -1
- package/dist/adapters/cc-switch/common.d.ts.map +0 -1
- package/dist/adapters/cc-switch/index.d.ts.map +0 -1
- package/dist/adapters/cc-switch/mcp-acode.d.ts.map +0 -1
- package/dist/adapters/cc-switch/mcp-claude.d.ts.map +0 -1
- package/dist/adapters/cc-switch/mcp-claudecode.d.ts.map +0 -1
- package/dist/adapters/cc-switch/mcp-codex.d.ts.map +0 -1
- package/dist/adapters/cc-switch/mcp-opencode.d.ts.map +0 -1
- package/dist/adapters/cc-switch/mcp-placeholder.d.ts.map +0 -1
- package/dist/adapters/cc-switch/skill-acode.d.ts.map +0 -1
- package/dist/adapters/cc-switch/skill-claude.d.ts.map +0 -1
- package/dist/adapters/cc-switch/skill-claudecode.d.ts.map +0 -1
- package/dist/adapters/cc-switch/skill-codex.d.ts.map +0 -1
- package/dist/adapters/cc-switch/skill-directory.d.ts.map +0 -1
- package/dist/adapters/cc-switch/skill-opencode.d.ts.map +0 -1
- package/dist/adapters/cc-switch/skill-placeholder.d.ts.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/middleware/auth.d.ts.map +0 -1
- package/dist/routes/acode-todos.d.ts.map +0 -1
- package/dist/routes/ai-sources.d.ts.map +0 -1
- package/dist/routes/dashboard.d.ts.map +0 -1
- package/dist/routes/events.d.ts.map +0 -1
- package/dist/routes/file-browser.d.ts.map +0 -1
- package/dist/routes/file-diff-rollback.d.ts.map +0 -1
- package/dist/routes/file-snapshot.d.ts.map +0 -1
- package/dist/routes/git.d.ts.map +0 -1
- package/dist/routes/instance.d.ts.map +0 -1
- package/dist/routes/mcp.d.ts.map +0 -1
- package/dist/routes/processes.d.ts.map +0 -1
- package/dist/routes/properties.d.ts.map +0 -1
- package/dist/routes/pty.d.ts.map +0 -1
- package/dist/routes/runtime-binding.d.ts.map +0 -1
- package/dist/routes/sessions.d.ts.map +0 -1
- package/dist/routes/skills.d.ts.map +0 -1
- package/dist/routes/sub-agents.d.ts.map +0 -1
- package/dist/routes/system-metrics.d.ts.map +0 -1
- package/dist/routes/task-context.d.ts.map +0 -1
- package/dist/routes/terminal.d.ts.map +0 -1
- package/dist/routes/yml.d.ts.map +0 -1
- package/dist/services/acode-package.d.ts.map +0 -1
- package/dist/services/agent-event-store.d.ts.map +0 -1
- package/dist/services/agent-process-manager.d.ts.map +0 -1
- package/dist/services/ai-source-apply.d.ts.map +0 -1
- package/dist/services/auto-register.d.ts.map +0 -1
- package/dist/services/aws-client-agent-mcp.d.ts.map +0 -1
- package/dist/services/bridge-package-publish-hooks.d.ts.map +0 -1
- package/dist/services/cc-switch-sdk.d.ts.map +0 -1
- package/dist/services/cli-commands.d.ts.map +0 -1
- package/dist/services/event-delta-pusher.d.ts.map +0 -1
- package/dist/services/fileSnapshotStore.d.ts.map +0 -1
- package/dist/services/instance-ai-config-service.d.ts.map +0 -1
- package/dist/services/instance-init-service.d.ts.map +0 -1
- package/dist/services/instance-service.d.ts.map +0 -1
- package/dist/services/instance-state.d.ts.map +0 -1
- package/dist/services/legacy-path-migration.d.ts.map +0 -1
- package/dist/services/lifecycle-state.d.ts.map +0 -1
- package/dist/services/mcp-launch-binding-queue.d.ts.map +0 -1
- package/dist/services/orphan-monitor.d.ts.map +0 -1
- package/dist/services/panel-auth.d.ts.map +0 -1
- package/dist/services/process-detector.d.ts.map +0 -1
- package/dist/services/process-registry.d.ts.map +0 -1
- package/dist/services/ring-buffer.d.ts.map +0 -1
- package/dist/services/runtime-binding.d.ts.map +0 -1
- package/dist/services/runtime-lifecycle-policy.d.ts.map +0 -1
- package/dist/services/session-output.d.ts.map +0 -1
- package/dist/services/startup-config-wizard.d.ts.map +0 -1
- package/dist/services/sub-agent-manager-instance.d.ts.map +0 -1
- package/dist/services/system-metrics.d.ts.map +0 -1
- package/dist/services/terminal-persistence.d.ts.map +0 -1
- package/dist/services/tool-installer.d.ts.map +0 -1
- package/dist/services/tunnel-client.d.ts.map +0 -1
- package/dist/services/windows-service-manager.d.ts.map +0 -1
- package/dist/services/workspace-files.d.ts.map +0 -1
- package/dist/services/workspace-watch.d.ts.map +0 -1
- package/dist/services/workspaceTaskChangeWatcher.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/utils/file-utils.d.ts.map +0 -1
- package/dist/utils/instance-ai-config-debug-logger.d.ts.map +0 -1
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/mcp-utils.d.ts.map +0 -1
- package/dist/utils/path-utils.d.ts.map +0 -1
- package/dist/utils/sdk-package-loader.d.ts.map +0 -1
- package/dist/utils/validation.d.ts.map +0 -1
- package/dist/utils/yaml-utils.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/constants.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/errors.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/index.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/schemas.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/sdk.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/services/ai-config-service.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/services/mcp-model.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/services/mcp-service.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/services/skill-model.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/services/skill-service.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/state.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/types.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/utils/fs.d.ts.map +0 -1
- package/node_modules/@cc-switch/sdk/dist/utils/id.d.ts.map +0 -1
- package/package/acode/dist/ask-user-question-tool.d.ts.map +0 -1
- package/package/acode/dist/built-in-file-tools.d.ts.map +0 -1
- package/package/acode/dist/built-in-symbols-tool.d.ts.map +0 -1
- package/package/acode/dist/builtins/commandArtifacts.d.ts.map +0 -1
- package/package/acode/dist/builtins/commandOutputEvents.d.ts.map +0 -1
- package/package/acode/dist/builtins/shellCommandPreparation.d.ts.map +0 -1
- package/package/acode/dist/builtins/shellCommandResult.d.ts.map +0 -1
- package/package/acode/dist/builtins/shellCommandRunner.d.ts.map +0 -1
- package/package/acode/dist/builtins/shellOutputDecoder.d.ts.map +0 -1
- package/package/acode/dist/cli.d.ts.map +0 -1
- package/package/acode/dist/commands/commandLoader.d.ts.map +0 -1
- package/package/acode/dist/commands/commandParser.d.ts.map +0 -1
- package/package/acode/dist/commands/commandRegistry.d.ts.map +0 -1
- package/package/acode/dist/compaction.d.ts.map +0 -1
- package/package/acode/dist/config.d.ts.map +0 -1
- package/package/acode/dist/fs-utils.d.ts.map +0 -1
- package/package/acode/dist/hooks/hookBus.d.ts.map +0 -1
- package/package/acode/dist/index.d.ts.map +0 -1
- package/package/acode/dist/mcpToolNames.d.ts.map +0 -1
- package/package/acode/dist/parsers/ast-cache.d.ts.map +0 -1
- package/package/acode/dist/parsers/language-detector.d.ts.map +0 -1
- package/package/acode/dist/parsers/language-queries.d.ts.map +0 -1
- package/package/acode/dist/parsers/symbol-extractor.d.ts.map +0 -1
- package/package/acode/dist/parsers/tree-sitter-loader.d.ts.map +0 -1
- package/package/acode/dist/parsers/types.d.ts.map +0 -1
- package/package/acode/dist/runtime.d.ts.map +0 -1
- package/package/acode/dist/skill-tool.d.ts.map +0 -1
- package/package/acode/dist/sub-agent-manager.d.ts.map +0 -1
- package/package/acode/dist/sub-agent-store.d.ts.map +0 -1
- package/package/acode/dist/sub-agent-tools.d.ts.map +0 -1
- package/package/acode/dist/sub-agent-types.d.ts.map +0 -1
- package/package/acode/dist/todo-store.d.ts.map +0 -1
- package/package/acode/dist/todo-tool.d.ts.map +0 -1
- package/package/acode/dist/types.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/agent-client.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/agent-client.test.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/agent-client.test.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/config.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/config.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/config.test.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/config.test.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/constants.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/constants.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/context-manager.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/context-manager.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/context-manager.test.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/context-manager.test.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/http-client.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/http-client.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/http-client.test.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/http-client.test.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/index.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/index.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/logger.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/logger.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/logger.test.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/logger.test.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.test.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.test.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.test.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/memory-store.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/memory-store.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/memory-store.test.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/memory-store.test.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/memory-tools.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/memory-tools.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/message-buffer.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/message-buffer.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/message-buffer.test.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/message-buffer.test.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/messageContent.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/messageContent.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/server-memory-store.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.test.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.test.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/types.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.js.map +0 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.test.d.ts.map +0 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.test.js.map +0 -1
- package/package/cc-switch-sdk/dist/constants.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/errors.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/index.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/schemas.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/sdk.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/services/ai-config-service.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/services/mcp-model.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/services/mcp-service.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/services/skill-model.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/services/skill-service.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/state.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/types.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/utils/fs.d.ts.map +0 -1
- package/package/cc-switch-sdk/dist/utils/id.d.ts.map +0 -1
|
@@ -1,1275 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
* 负责在工作区范围内安全地浏览、读取与保存文件。
|
|
4
|
-
*/
|
|
5
|
-
import { createReadStream, createWriteStream, promises as fs } from 'node:fs';
|
|
6
|
-
import path from 'node:path';
|
|
7
|
-
import { Readable } from 'node:stream';
|
|
8
|
-
import { pipeline } from 'node:stream/promises';
|
|
9
|
-
import { TextDecoder } from 'node:util';
|
|
10
|
-
import { createGunzip } from 'node:zlib';
|
|
11
|
-
import * as tar from 'tar';
|
|
12
|
-
import unzipper from 'unzipper';
|
|
13
|
-
const { ZipArchive } = await import('archiver');
|
|
14
|
-
const ARCHIVE_LIMITS = {
|
|
15
|
-
maxEntries: 10_000,
|
|
16
|
-
maxFileBytes: 512 * 1024 * 1024,
|
|
17
|
-
maxTotalBytes: 2 * 1024 * 1024 * 1024,
|
|
18
|
-
maxPathDepth: 64,
|
|
19
|
-
maxPathLength: 4096,
|
|
20
|
-
};
|
|
21
|
-
const GBK_DECODER = new TextDecoder('gbk');
|
|
22
|
-
function toDisplayPath(targetPath) {
|
|
23
|
-
return String(targetPath || '').replace(/\\/g, '/');
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* 规范化并校验工作区路径。
|
|
27
|
-
*/
|
|
28
|
-
function resolveWorkspaceRoot(workspacePath) {
|
|
29
|
-
const normalizedWorkspacePath = path.resolve(String(workspacePath || '').trim());
|
|
30
|
-
if (!normalizedWorkspacePath) {
|
|
31
|
-
throw new Error('workspacePath is required');
|
|
32
|
-
}
|
|
33
|
-
return normalizedWorkspacePath;
|
|
34
|
-
}
|
|
35
|
-
async function resolveWorkspaceRootRealpath(workspacePath) {
|
|
36
|
-
const workspaceRoot = resolveWorkspaceRoot(workspacePath);
|
|
37
|
-
const realWorkspaceRoot = await fs.realpath(workspaceRoot);
|
|
38
|
-
const stat = await fs.stat(realWorkspaceRoot);
|
|
39
|
-
if (!stat.isDirectory()) {
|
|
40
|
-
throw new Error(`Workspace path is not a directory: ${workspaceRoot}`);
|
|
41
|
-
}
|
|
42
|
-
return realWorkspaceRoot;
|
|
43
|
-
}
|
|
44
|
-
async function resolveWorkspaceRoots(workspacePath) {
|
|
45
|
-
const requestedRoot = resolveWorkspaceRoot(workspacePath);
|
|
46
|
-
return {
|
|
47
|
-
requestedRoot,
|
|
48
|
-
workspaceRoot: await resolveWorkspaceRootRealpath(workspacePath),
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* 判断目标路径是否位于工作区内。
|
|
53
|
-
*/
|
|
54
|
-
function ensureInsideWorkspace(workspaceRoot, targetPath) {
|
|
55
|
-
const resolvedTargetPath = path.resolve(targetPath);
|
|
56
|
-
const relativePath = path.relative(workspaceRoot, resolvedTargetPath);
|
|
57
|
-
const isOutside = relativePath.startsWith('..') || path.isAbsolute(relativePath);
|
|
58
|
-
if (isOutside) {
|
|
59
|
-
throw new Error(`Target path is outside workspace: ${resolvedTargetPath}`);
|
|
60
|
-
}
|
|
61
|
-
return resolvedTargetPath;
|
|
62
|
-
}
|
|
63
|
-
async function ensureRealPathInsideWorkspace(workspaceRoot, targetPath) {
|
|
64
|
-
const realTargetPath = await fs.realpath(targetPath);
|
|
65
|
-
return ensureInsideWorkspace(workspaceRoot, realTargetPath);
|
|
66
|
-
}
|
|
67
|
-
async function ensureWritableParentInsideWorkspace(workspaceRoot, targetPath) {
|
|
68
|
-
const realParentPath = await fs.realpath(path.dirname(targetPath));
|
|
69
|
-
ensureInsideWorkspace(workspaceRoot, realParentPath);
|
|
70
|
-
try {
|
|
71
|
-
const targetLinkStat = await fs.lstat(targetPath);
|
|
72
|
-
if (targetLinkStat.isSymbolicLink()) {
|
|
73
|
-
throw new Error(`Refusing to write through symbolic link: ${targetPath}`);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
const errorCode = typeof error === 'object' && error !== null ? Reflect.get(error, 'code') : undefined;
|
|
78
|
-
if (errorCode !== 'ENOENT') {
|
|
79
|
-
throw error;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
function resolveRequestedPathInsideWorkspace(requestedRoot, workspaceRoot, requestedPath) {
|
|
84
|
-
const resolvedRequestedPath = path.resolve(requestedPath);
|
|
85
|
-
const requestedRelativePath = path.relative(requestedRoot, resolvedRequestedPath);
|
|
86
|
-
if (requestedRelativePath.startsWith('..') || path.isAbsolute(requestedRelativePath)) {
|
|
87
|
-
throw new Error(`Target path is outside workspace: ${resolvedRequestedPath}`);
|
|
88
|
-
}
|
|
89
|
-
return ensureInsideWorkspace(workspaceRoot, path.join(workspaceRoot, requestedRelativePath));
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* 解析目录目标路径,空路径时回退到工作区根目录。
|
|
93
|
-
*/
|
|
94
|
-
async function resolveDirectoryTarget({ workspacePath, targetPath = '' }) {
|
|
95
|
-
const { requestedRoot, workspaceRoot } = await resolveWorkspaceRoots(workspacePath);
|
|
96
|
-
const requestedPath = String(targetPath || '').trim();
|
|
97
|
-
const lexicalTargetPath = requestedPath
|
|
98
|
-
? resolveRequestedPathInsideWorkspace(requestedRoot, workspaceRoot, requestedPath)
|
|
99
|
-
: workspaceRoot;
|
|
100
|
-
const resolvedTargetPath = await ensureRealPathInsideWorkspace(workspaceRoot, lexicalTargetPath);
|
|
101
|
-
return { workspaceRoot, resolvedTargetPath };
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* 校验工作区内的条目名称,避免跨目录操作与非法名称。
|
|
105
|
-
*/
|
|
106
|
-
function normalizeEntryName(rawName) {
|
|
107
|
-
const entryName = String(rawName || '').trim();
|
|
108
|
-
if (!entryName) {
|
|
109
|
-
throw new Error('entryName is required');
|
|
110
|
-
}
|
|
111
|
-
if (entryName === '.' || entryName === '..') {
|
|
112
|
-
throw new Error('entryName is invalid');
|
|
113
|
-
}
|
|
114
|
-
if (entryName.includes('/') || entryName.includes('\\') || path.basename(entryName) !== entryName) {
|
|
115
|
-
throw new Error(`entryName must be a plain file or directory name: ${entryName}`);
|
|
116
|
-
}
|
|
117
|
-
return entryName;
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* 修正 multipart 文件名在部分代理链路中被 latin1 解读造成的 UTF-8 中文乱码。
|
|
121
|
-
*/
|
|
122
|
-
function decodePossiblyLatin1Utf8FileName(rawName) {
|
|
123
|
-
const fileName = String(rawName || '');
|
|
124
|
-
if (!/[\u00c0-\u00ff]/.test(fileName)) {
|
|
125
|
-
return fileName;
|
|
126
|
-
}
|
|
127
|
-
const decodedFileName = Buffer.from(fileName, 'latin1').toString('utf8');
|
|
128
|
-
if (!decodedFileName || decodedFileName.includes('\uFFFD')) {
|
|
129
|
-
return fileName;
|
|
130
|
-
}
|
|
131
|
-
return decodedFileName;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* 将上传文件名规范化为单级文件名,避免浏览器提交的路径穿透目录。
|
|
135
|
-
*/
|
|
136
|
-
function normalizeUploadedFileName(rawName) {
|
|
137
|
-
const decodedName = decodePossiblyLatin1Utf8FileName(rawName);
|
|
138
|
-
return normalizeEntryName(path.basename(decodedName.replace(/\\/g, '/')));
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* 将浏览器传入的上传相对路径拆成安全路径片段,保留文件夹层级但拒绝穿透路径。
|
|
142
|
-
*/
|
|
143
|
-
function normalizeUploadedRelativePath(rawPath, fallbackName) {
|
|
144
|
-
if (!rawPath) {
|
|
145
|
-
return normalizeUploadedFileName(fallbackName);
|
|
146
|
-
}
|
|
147
|
-
const decodedPath = decodePossiblyLatin1Utf8FileName(String(rawPath || fallbackName || '')).replace(/\\/g, '/');
|
|
148
|
-
const segments = decodedPath.split('/').filter(Boolean).map((segment) => normalizeEntryName(segment));
|
|
149
|
-
if (segments.length === 0) {
|
|
150
|
-
return normalizeUploadedFileName(fallbackName);
|
|
151
|
-
}
|
|
152
|
-
return path.join(...segments);
|
|
153
|
-
}
|
|
154
|
-
function formatModeOctal(mode) {
|
|
155
|
-
return (mode & 0o7777).toString(8).padStart(4, '0');
|
|
156
|
-
}
|
|
157
|
-
function parsePermissionMode(rawMode) {
|
|
158
|
-
const modeText = String(rawMode || '').trim();
|
|
159
|
-
if (!/^[0-7]{3,4}$/.test(modeText)) {
|
|
160
|
-
throw new Error('mode must be a 3 or 4 digit octal string');
|
|
161
|
-
}
|
|
162
|
-
return Number.parseInt(modeText, 8);
|
|
163
|
-
}
|
|
164
|
-
function permissionTriplet(mode, readBit, writeBit, executeBit, specialBit, specialExecute, specialNoExecute) {
|
|
165
|
-
const read = (mode & readBit) !== 0 ? 'r' : '-';
|
|
166
|
-
const write = (mode & writeBit) !== 0 ? 'w' : '-';
|
|
167
|
-
const hasExecute = (mode & executeBit) !== 0;
|
|
168
|
-
const hasSpecial = (mode & specialBit) !== 0;
|
|
169
|
-
const execute = hasSpecial ? (hasExecute ? specialExecute : specialNoExecute) : (hasExecute ? 'x' : '-');
|
|
170
|
-
return `${read}${write}${execute}`;
|
|
171
|
-
}
|
|
172
|
-
function formatPermissions(mode) {
|
|
173
|
-
return [
|
|
174
|
-
permissionTriplet(mode, 0o400, 0o200, 0o100, 0o4000, 's', 'S'),
|
|
175
|
-
permissionTriplet(mode, 0o040, 0o020, 0o010, 0o2000, 's', 'S'),
|
|
176
|
-
permissionTriplet(mode, 0o004, 0o002, 0o001, 0o1000, 't', 'T'),
|
|
177
|
-
].join('');
|
|
178
|
-
}
|
|
179
|
-
function readPngDimensions(buffer) {
|
|
180
|
-
if (buffer.length < 24 || buffer.toString('hex', 0, 8) !== '89504e470d0a1a0a') {
|
|
181
|
-
return undefined;
|
|
182
|
-
}
|
|
183
|
-
return {
|
|
184
|
-
format: 'png',
|
|
185
|
-
width: buffer.readUInt32BE(16),
|
|
186
|
-
height: buffer.readUInt32BE(20),
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
function readGifDimensions(buffer) {
|
|
190
|
-
if (buffer.length < 10 || (buffer.toString('ascii', 0, 6) !== 'GIF87a' && buffer.toString('ascii', 0, 6) !== 'GIF89a')) {
|
|
191
|
-
return undefined;
|
|
192
|
-
}
|
|
193
|
-
return {
|
|
194
|
-
format: 'gif',
|
|
195
|
-
width: buffer.readUInt16LE(6),
|
|
196
|
-
height: buffer.readUInt16LE(8),
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
function readJpegDimensions(buffer) {
|
|
200
|
-
if (buffer.length < 4 || buffer[0] !== 0xff || buffer[1] !== 0xd8) {
|
|
201
|
-
return undefined;
|
|
202
|
-
}
|
|
203
|
-
let offset = 2;
|
|
204
|
-
while (offset + 9 < buffer.length) {
|
|
205
|
-
if (buffer[offset] !== 0xff) {
|
|
206
|
-
offset += 1;
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
const marker = buffer[offset + 1];
|
|
210
|
-
const segmentLength = buffer.readUInt16BE(offset + 2);
|
|
211
|
-
if (segmentLength < 2) {
|
|
212
|
-
return undefined;
|
|
213
|
-
}
|
|
214
|
-
const isStartOfFrame = marker >= 0xc0 && marker <= 0xcf && ![0xc4, 0xc8, 0xcc].includes(marker);
|
|
215
|
-
if (isStartOfFrame && offset + 8 < buffer.length) {
|
|
216
|
-
return {
|
|
217
|
-
format: 'jpeg',
|
|
218
|
-
height: buffer.readUInt16BE(offset + 5),
|
|
219
|
-
width: buffer.readUInt16BE(offset + 7),
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
offset += 2 + segmentLength;
|
|
223
|
-
}
|
|
224
|
-
return undefined;
|
|
225
|
-
}
|
|
226
|
-
function readWebpDimensions(buffer) {
|
|
227
|
-
if (buffer.length < 30 || buffer.toString('ascii', 0, 4) !== 'RIFF' || buffer.toString('ascii', 8, 12) !== 'WEBP') {
|
|
228
|
-
return undefined;
|
|
229
|
-
}
|
|
230
|
-
const chunkType = buffer.toString('ascii', 12, 16);
|
|
231
|
-
if (chunkType === 'VP8X' && buffer.length >= 30) {
|
|
232
|
-
return {
|
|
233
|
-
format: 'webp',
|
|
234
|
-
width: 1 + buffer.readUIntLE(24, 3),
|
|
235
|
-
height: 1 + buffer.readUIntLE(27, 3),
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
if (chunkType === 'VP8 ' && buffer.length >= 30) {
|
|
239
|
-
return {
|
|
240
|
-
format: 'webp',
|
|
241
|
-
width: buffer.readUInt16LE(26) & 0x3fff,
|
|
242
|
-
height: buffer.readUInt16LE(28) & 0x3fff,
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
if (chunkType === 'VP8L' && buffer.length >= 25) {
|
|
246
|
-
const bits = buffer.readUInt32LE(21);
|
|
247
|
-
return {
|
|
248
|
-
format: 'webp',
|
|
249
|
-
width: (bits & 0x3fff) + 1,
|
|
250
|
-
height: ((bits >> 14) & 0x3fff) + 1,
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
return undefined;
|
|
254
|
-
}
|
|
255
|
-
async function collectChmodTargets(workspaceRoot, targetPath, recursive) {
|
|
256
|
-
const linkStat = await fs.lstat(targetPath);
|
|
257
|
-
if (linkStat.isSymbolicLink()) {
|
|
258
|
-
throw new Error(`Refusing to change permissions on symbolic link: ${targetPath}`);
|
|
259
|
-
}
|
|
260
|
-
const stat = await fs.stat(targetPath);
|
|
261
|
-
if (!recursive || !stat.isDirectory()) {
|
|
262
|
-
return [targetPath];
|
|
263
|
-
}
|
|
264
|
-
const children = await fs.readdir(targetPath);
|
|
265
|
-
const nestedTargets = await Promise.all(children.map((childName) => {
|
|
266
|
-
const childPath = ensureInsideWorkspace(workspaceRoot, path.join(targetPath, childName));
|
|
267
|
-
return collectChmodTargets(workspaceRoot, childPath, true);
|
|
268
|
-
}));
|
|
269
|
-
return [targetPath, ...nestedTargets.flat()];
|
|
270
|
-
}
|
|
271
|
-
async function ensureSafeChmodTarget(workspaceRoot, targetPath) {
|
|
272
|
-
const linkStat = await fs.lstat(targetPath);
|
|
273
|
-
if (linkStat.isSymbolicLink()) {
|
|
274
|
-
throw new Error(`Refusing to change permissions on symbolic link: ${targetPath}`);
|
|
275
|
-
}
|
|
276
|
-
await ensureRealPathInsideWorkspace(workspaceRoot, targetPath);
|
|
277
|
-
}
|
|
278
|
-
async function readImageProperties(filePath) {
|
|
279
|
-
const header = await fs.open(filePath, 'r');
|
|
280
|
-
try {
|
|
281
|
-
const buffer = Buffer.alloc(512);
|
|
282
|
-
const { bytesRead } = await header.read(buffer, 0, buffer.length, 0);
|
|
283
|
-
const imageHeader = buffer.subarray(0, bytesRead);
|
|
284
|
-
return readPngDimensions(imageHeader)
|
|
285
|
-
|| readGifDimensions(imageHeader)
|
|
286
|
-
|| readJpegDimensions(imageHeader)
|
|
287
|
-
|| readWebpDimensions(imageHeader);
|
|
288
|
-
}
|
|
289
|
-
finally {
|
|
290
|
-
await header.close();
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* 解析并校验工作区内条目的目标路径。
|
|
295
|
-
*/
|
|
296
|
-
async function resolveEntryTarget({ workspacePath, targetPath }, options = {}) {
|
|
297
|
-
const { requestedRoot, workspaceRoot } = await resolveWorkspaceRoots(workspacePath);
|
|
298
|
-
const normalizedTargetPath = String(targetPath || '').trim();
|
|
299
|
-
if (!normalizedTargetPath) {
|
|
300
|
-
throw new Error('targetPath is required');
|
|
301
|
-
}
|
|
302
|
-
const lexicalTargetPath = resolveRequestedPathInsideWorkspace(requestedRoot, workspaceRoot, normalizedTargetPath);
|
|
303
|
-
const resolvedTargetPath = await ensureRealPathInsideWorkspace(workspaceRoot, lexicalTargetPath);
|
|
304
|
-
if (!options.allowWorkspaceRoot && resolvedTargetPath === workspaceRoot) {
|
|
305
|
-
throw new Error('Workspace root cannot be modified');
|
|
306
|
-
}
|
|
307
|
-
return { workspaceRoot, resolvedTargetPath };
|
|
308
|
-
}
|
|
309
|
-
async function resolveWritableDirectory(params) {
|
|
310
|
-
const { requestedRoot, workspaceRoot } = await resolveWorkspaceRoots(params.workspacePath);
|
|
311
|
-
const requestedPath = String(params.targetPath || '').trim();
|
|
312
|
-
if (!requestedPath) {
|
|
313
|
-
return { workspaceRoot, resolvedTargetPath: workspaceRoot };
|
|
314
|
-
}
|
|
315
|
-
const lexicalTargetPath = resolveRequestedPathInsideWorkspace(requestedRoot, workspaceRoot, requestedPath);
|
|
316
|
-
if (await pathExists(lexicalTargetPath)) {
|
|
317
|
-
return {
|
|
318
|
-
workspaceRoot,
|
|
319
|
-
resolvedTargetPath: await ensureRealPathInsideWorkspace(workspaceRoot, lexicalTargetPath),
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
const realParentPath = await fs.realpath(path.dirname(lexicalTargetPath));
|
|
323
|
-
ensureInsideWorkspace(workspaceRoot, realParentPath);
|
|
324
|
-
return { workspaceRoot, resolvedTargetPath: lexicalTargetPath };
|
|
325
|
-
}
|
|
326
|
-
function archiveKind(filePath) {
|
|
327
|
-
const lowerName = filePath.toLowerCase();
|
|
328
|
-
if (lowerName.endsWith('.zip'))
|
|
329
|
-
return 'zip';
|
|
330
|
-
if (lowerName.endsWith('.tar.gz') || lowerName.endsWith('.tgz'))
|
|
331
|
-
return 'tgz';
|
|
332
|
-
if (lowerName.endsWith('.tar'))
|
|
333
|
-
return 'tar';
|
|
334
|
-
if (lowerName.endsWith('.gz'))
|
|
335
|
-
return 'gz';
|
|
336
|
-
return null;
|
|
337
|
-
}
|
|
338
|
-
function stripArchiveExtension(fileName) {
|
|
339
|
-
return fileName.replace(/\.tar\.gz$/i, '').replace(/\.tgz$/i, '').replace(/\.zip$/i, '').replace(/\.tar$/i, '').replace(/\.gz$/i, '');
|
|
340
|
-
}
|
|
341
|
-
function resolveArchiveUploadOutputPath(uploadTargetDirectory, archiveFileName) {
|
|
342
|
-
const outputDirectoryName = normalizeEntryName(stripArchiveExtension(archiveFileName) || 'extracted');
|
|
343
|
-
return path.join(uploadTargetDirectory, outputDirectoryName);
|
|
344
|
-
}
|
|
345
|
-
function ensureArchiveEntryInside(targetRoot, entryPath) {
|
|
346
|
-
const normalizedEntryPath = String(entryPath || '').replace(/\\/g, '/');
|
|
347
|
-
const pathSegments = normalizedEntryPath.split('/').filter(Boolean);
|
|
348
|
-
if (!normalizedEntryPath || normalizedEntryPath.startsWith('/') || /^[A-Za-z]:/.test(normalizedEntryPath)) {
|
|
349
|
-
throw new Error(`Archive entry path is invalid: ${entryPath}`);
|
|
350
|
-
}
|
|
351
|
-
if (normalizedEntryPath.length > ARCHIVE_LIMITS.maxPathLength || pathSegments.length > ARCHIVE_LIMITS.maxPathDepth) {
|
|
352
|
-
throw new Error(`Archive entry path is too deep or too long: ${entryPath}`);
|
|
353
|
-
}
|
|
354
|
-
return ensureInsideWorkspace(targetRoot, path.join(targetRoot, normalizedEntryPath));
|
|
355
|
-
}
|
|
356
|
-
function decodeZipEntryPath(entry) {
|
|
357
|
-
const pathBuffer = entry.props?.pathBuffer;
|
|
358
|
-
if (!pathBuffer || entry.props?.flags?.isUnicode) {
|
|
359
|
-
return entry.path;
|
|
360
|
-
}
|
|
361
|
-
const decodedPath = GBK_DECODER.decode(pathBuffer);
|
|
362
|
-
if (!decodedPath || decodedPath.includes('\uFFFD')) {
|
|
363
|
-
return entry.path;
|
|
364
|
-
}
|
|
365
|
-
return decodedPath;
|
|
366
|
-
}
|
|
367
|
-
async function drainZipEntry(entry) {
|
|
368
|
-
const drainedStream = entry.autodrain?.();
|
|
369
|
-
if (!drainedStream) {
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
await new Promise((resolve, reject) => {
|
|
373
|
-
drainedStream.on('close', resolve);
|
|
374
|
-
drainedStream.on('end', resolve);
|
|
375
|
-
drainedStream.on('finish', resolve);
|
|
376
|
-
drainedStream.on('error', reject);
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
function trackArchiveEntry(meter) {
|
|
380
|
-
meter.entries += 1;
|
|
381
|
-
if (meter.entries > ARCHIVE_LIMITS.maxEntries) {
|
|
382
|
-
throw new Error(`Archive contains too many entries. Limit: ${ARCHIVE_LIMITS.maxEntries}`);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
function trackArchiveBytes(meter, bytes) {
|
|
386
|
-
meter.totalBytes += bytes;
|
|
387
|
-
if (bytes > ARCHIVE_LIMITS.maxFileBytes) {
|
|
388
|
-
throw new Error(`Archive entry exceeds max size. Limit: ${ARCHIVE_LIMITS.maxFileBytes} bytes`);
|
|
389
|
-
}
|
|
390
|
-
if (meter.totalBytes > ARCHIVE_LIMITS.maxTotalBytes) {
|
|
391
|
-
throw new Error(`Archive extraction exceeds total size. Limit: ${ARCHIVE_LIMITS.maxTotalBytes} bytes`);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
async function pipelineWithArchiveLimits(input, destinationPath, meter) {
|
|
395
|
-
let fileBytes = 0;
|
|
396
|
-
input.on('data', (chunk) => {
|
|
397
|
-
const chunkBytes = Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(chunk);
|
|
398
|
-
fileBytes += chunkBytes;
|
|
399
|
-
if (fileBytes > ARCHIVE_LIMITS.maxFileBytes || meter.totalBytes + fileBytes > ARCHIVE_LIMITS.maxTotalBytes) {
|
|
400
|
-
if (input instanceof Readable) {
|
|
401
|
-
input.destroy(new Error('Archive extraction size limit exceeded'));
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
});
|
|
405
|
-
await pipeline(input, createWriteStream(destinationPath));
|
|
406
|
-
trackArchiveBytes(meter, fileBytes);
|
|
407
|
-
}
|
|
408
|
-
async function pathExists(targetPath) {
|
|
409
|
-
try {
|
|
410
|
-
await fs.access(targetPath);
|
|
411
|
-
return true;
|
|
412
|
-
}
|
|
413
|
-
catch (error) {
|
|
414
|
-
const errorCode = typeof error === 'object' && error !== null ? Reflect.get(error, 'code') : undefined;
|
|
415
|
-
if (errorCode === 'ENOENT') {
|
|
416
|
-
return false;
|
|
417
|
-
}
|
|
418
|
-
throw error;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
/**
|
|
422
|
-
* 解析文件目标路径,并确保文件位于工作区中。
|
|
423
|
-
*/
|
|
424
|
-
async function resolveFileTarget({ workspacePath, filePath }) {
|
|
425
|
-
const { requestedRoot, workspaceRoot } = await resolveWorkspaceRoots(workspacePath);
|
|
426
|
-
const normalizedFilePath = String(filePath || '').trim();
|
|
427
|
-
if (!normalizedFilePath) {
|
|
428
|
-
throw new Error('filePath is required');
|
|
429
|
-
}
|
|
430
|
-
return {
|
|
431
|
-
workspaceRoot,
|
|
432
|
-
resolvedFilePath: resolveRequestedPathInsideWorkspace(requestedRoot, workspaceRoot, normalizedFilePath)
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
/**
|
|
436
|
-
* 列出工作区目录内容。
|
|
437
|
-
*/
|
|
438
|
-
export async function listWorkspaceDirectory(params) {
|
|
439
|
-
const { workspaceRoot, resolvedTargetPath } = await resolveDirectoryTarget(params);
|
|
440
|
-
const stat = await fs.stat(resolvedTargetPath);
|
|
441
|
-
if (!stat.isDirectory()) {
|
|
442
|
-
throw new Error(`Path is not a directory: ${resolvedTargetPath}`);
|
|
443
|
-
}
|
|
444
|
-
const entries = await fs.readdir(resolvedTargetPath, { withFileTypes: true });
|
|
445
|
-
const items = await Promise.all(entries.map(async (entry) => {
|
|
446
|
-
const entryPath = path.join(resolvedTargetPath, entry.name);
|
|
447
|
-
const entrySize = entry.isFile() ? (await fs.stat(entryPath)).size : undefined;
|
|
448
|
-
return {
|
|
449
|
-
name: entry.name,
|
|
450
|
-
path: toDisplayPath(entryPath),
|
|
451
|
-
isDirectory: entry.isDirectory(),
|
|
452
|
-
size: entrySize
|
|
453
|
-
};
|
|
454
|
-
}));
|
|
455
|
-
return {
|
|
456
|
-
currentPath: toDisplayPath(resolvedTargetPath),
|
|
457
|
-
workspacePath: toDisplayPath(workspaceRoot),
|
|
458
|
-
items
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
/**
|
|
462
|
-
* 读取工作区中的文本文件。
|
|
463
|
-
*/
|
|
464
|
-
export async function readWorkspaceFile(params) {
|
|
465
|
-
const { workspaceRoot, resolvedFilePath: lexicalFilePath } = await resolveFileTarget(params);
|
|
466
|
-
const resolvedFilePath = await ensureRealPathInsideWorkspace(workspaceRoot, lexicalFilePath);
|
|
467
|
-
const stat = await fs.stat(resolvedFilePath);
|
|
468
|
-
if (!stat.isFile()) {
|
|
469
|
-
throw new Error(`Path is not a file: ${resolvedFilePath}`);
|
|
470
|
-
}
|
|
471
|
-
const content = await fs.readFile(resolvedFilePath, 'utf-8');
|
|
472
|
-
return {
|
|
473
|
-
workspacePath: toDisplayPath(workspaceRoot),
|
|
474
|
-
filePath: toDisplayPath(resolvedFilePath),
|
|
475
|
-
content
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
/**
|
|
479
|
-
* 目录大小计算上限,避免在工作区体量异常时长时间阻塞属性查询。
|
|
480
|
-
*/
|
|
481
|
-
const DIRECTORY_SIZE_LIMITS = {
|
|
482
|
-
maxEntries: 100_000,
|
|
483
|
-
maxDepth: 32,
|
|
484
|
-
};
|
|
485
|
-
/**
|
|
486
|
-
* 递归累加目录内所有普通文件的总大小。
|
|
487
|
-
* 跳过符号链接以避免跟随循环,访问失败的单个条目直接忽略。
|
|
488
|
-
*/
|
|
489
|
-
async function calculateDirectorySize(workspaceRoot, directoryPath) {
|
|
490
|
-
let total = 0;
|
|
491
|
-
let entriesProcessed = 0;
|
|
492
|
-
async function walk(dirPath, depth) {
|
|
493
|
-
if (depth >= DIRECTORY_SIZE_LIMITS.maxDepth) {
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
let entries;
|
|
497
|
-
try {
|
|
498
|
-
entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
499
|
-
}
|
|
500
|
-
catch {
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
for (const entry of entries) {
|
|
504
|
-
entriesProcessed += 1;
|
|
505
|
-
if (entriesProcessed > DIRECTORY_SIZE_LIMITS.maxEntries) {
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
if (entry.isSymbolicLink()) {
|
|
509
|
-
continue;
|
|
510
|
-
}
|
|
511
|
-
const entryPath = ensureInsideWorkspace(workspaceRoot, path.join(dirPath, entry.name));
|
|
512
|
-
try {
|
|
513
|
-
if (entry.isDirectory()) {
|
|
514
|
-
await walk(entryPath, depth + 1);
|
|
515
|
-
}
|
|
516
|
-
else if (entry.isFile()) {
|
|
517
|
-
const stat = await fs.stat(entryPath);
|
|
518
|
-
total += stat.size;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
catch {
|
|
522
|
-
// 单个条目失败不影响整体统计
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
await walk(directoryPath, 0);
|
|
527
|
-
return total;
|
|
528
|
-
}
|
|
529
|
-
/**
|
|
530
|
-
* 读取工作区条目的属性信息。
|
|
531
|
-
* 主流程:解析安全路径 -> 读取 stat/lstat -> 文件额外尝试解析图片尺寸。
|
|
532
|
-
* 对于目录,stat.size 只是目录项元数据大小,需要递归统计子文件总和。
|
|
533
|
-
*/
|
|
534
|
-
export async function getWorkspaceEntryProperties(params) {
|
|
535
|
-
const { workspaceRoot, resolvedTargetPath } = await resolveEntryTarget(params, { allowWorkspaceRoot: true });
|
|
536
|
-
const [stat, linkStat] = await Promise.all([
|
|
537
|
-
fs.stat(resolvedTargetPath),
|
|
538
|
-
fs.lstat(resolvedTargetPath),
|
|
539
|
-
]);
|
|
540
|
-
const isDirectory = stat.isDirectory();
|
|
541
|
-
const image = stat.isFile() ? await readImageProperties(resolvedTargetPath) : undefined;
|
|
542
|
-
const size = isDirectory
|
|
543
|
-
? await calculateDirectorySize(workspaceRoot, resolvedTargetPath)
|
|
544
|
-
: stat.size;
|
|
545
|
-
return {
|
|
546
|
-
workspacePath: toDisplayPath(workspaceRoot),
|
|
547
|
-
targetPath: toDisplayPath(resolvedTargetPath),
|
|
548
|
-
name: path.basename(resolvedTargetPath),
|
|
549
|
-
isDirectory: stat.isDirectory(),
|
|
550
|
-
isFile: stat.isFile(),
|
|
551
|
-
isSymbolicLink: linkStat.isSymbolicLink(),
|
|
552
|
-
size,
|
|
553
|
-
mode: stat.mode,
|
|
554
|
-
modeOctal: formatModeOctal(stat.mode),
|
|
555
|
-
permissions: formatPermissions(stat.mode),
|
|
556
|
-
uid: stat.uid,
|
|
557
|
-
gid: stat.gid,
|
|
558
|
-
atimeMs: stat.atimeMs,
|
|
559
|
-
mtimeMs: stat.mtimeMs,
|
|
560
|
-
ctimeMs: stat.ctimeMs,
|
|
561
|
-
birthtimeMs: stat.birthtimeMs,
|
|
562
|
-
...(image ? { image } : {}),
|
|
563
|
-
};
|
|
564
|
-
}
|
|
565
|
-
/**
|
|
566
|
-
* 修改工作区条目权限。
|
|
567
|
-
* 主流程:校验八进制权限 -> 解析安全路径 -> 非递归只改目标,递归目录先收集所有非符号链接子项后逐一 chmod。
|
|
568
|
-
*/
|
|
569
|
-
export async function chmodWorkspaceEntry(params) {
|
|
570
|
-
const mode = parsePermissionMode(params.mode);
|
|
571
|
-
const recursive = Boolean(params.recursive);
|
|
572
|
-
const { workspaceRoot, resolvedTargetPath } = await resolveEntryTarget(params);
|
|
573
|
-
const targets = await collectChmodTargets(workspaceRoot, resolvedTargetPath, recursive);
|
|
574
|
-
for (const targetPath of targets) {
|
|
575
|
-
await ensureSafeChmodTarget(workspaceRoot, targetPath);
|
|
576
|
-
await fs.chmod(targetPath, mode);
|
|
577
|
-
}
|
|
578
|
-
return {
|
|
579
|
-
ok: true,
|
|
580
|
-
workspacePath: toDisplayPath(workspaceRoot),
|
|
581
|
-
targetPath: toDisplayPath(resolvedTargetPath),
|
|
582
|
-
mode,
|
|
583
|
-
modeOctal: formatModeOctal(mode),
|
|
584
|
-
recursive,
|
|
585
|
-
changedCount: targets.length,
|
|
586
|
-
};
|
|
587
|
-
}
|
|
588
|
-
/**
|
|
589
|
-
* 返回工作区内 Word 文档的预览元数据;docx 由前端下载原文件后渲染,旧版 doc 明确提示不支持。
|
|
590
|
-
*/
|
|
591
|
-
export async function previewWorkspaceDocument(params) {
|
|
592
|
-
const { workspaceRoot, resolvedFilePath: lexicalFilePath } = await resolveFileTarget(params);
|
|
593
|
-
const resolvedFilePath = await ensureRealPathInsideWorkspace(workspaceRoot, lexicalFilePath);
|
|
594
|
-
const stat = await fs.stat(resolvedFilePath);
|
|
595
|
-
if (!stat.isFile()) {
|
|
596
|
-
throw new Error(`Path is not a file: ${resolvedFilePath}`);
|
|
597
|
-
}
|
|
598
|
-
const extension = path.extname(resolvedFilePath).toLowerCase();
|
|
599
|
-
if (extension === '.doc') {
|
|
600
|
-
return {
|
|
601
|
-
workspacePath: toDisplayPath(workspaceRoot),
|
|
602
|
-
filePath: toDisplayPath(resolvedFilePath),
|
|
603
|
-
kind: 'unsupported',
|
|
604
|
-
content: '',
|
|
605
|
-
message: '暂不支持旧版 .doc 格式预览,请另存为 .docx 后再打开。',
|
|
606
|
-
size: stat.size,
|
|
607
|
-
mtimeMs: stat.mtimeMs,
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
if (extension !== '.docx') {
|
|
611
|
-
throw new Error('Document preview only supports .docx files');
|
|
612
|
-
}
|
|
613
|
-
return {
|
|
614
|
-
workspacePath: toDisplayPath(workspaceRoot),
|
|
615
|
-
filePath: toDisplayPath(resolvedFilePath),
|
|
616
|
-
kind: 'html',
|
|
617
|
-
content: '',
|
|
618
|
-
size: stat.size,
|
|
619
|
-
mtimeMs: stat.mtimeMs,
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
/**
|
|
623
|
-
* 将文本内容保存到工作区文件中。
|
|
624
|
-
*/
|
|
625
|
-
export async function writeWorkspaceFile(params) {
|
|
626
|
-
const { workspaceRoot, resolvedFilePath } = await resolveFileTarget(params);
|
|
627
|
-
await fs.mkdir(path.dirname(resolvedFilePath), { recursive: true });
|
|
628
|
-
await ensureWritableParentInsideWorkspace(workspaceRoot, resolvedFilePath);
|
|
629
|
-
await fs.writeFile(resolvedFilePath, String(params.content ?? ''), 'utf-8');
|
|
630
|
-
return {
|
|
631
|
-
ok: true,
|
|
632
|
-
workspacePath: toDisplayPath(workspaceRoot),
|
|
633
|
-
filePath: toDisplayPath(resolvedFilePath)
|
|
634
|
-
};
|
|
635
|
-
}
|
|
636
|
-
/**
|
|
637
|
-
* 在指定工作区目录下创建文件或目录。
|
|
638
|
-
*/
|
|
639
|
-
export async function createWorkspaceEntry(params) {
|
|
640
|
-
const { workspaceRoot, resolvedTargetPath } = await resolveDirectoryTarget(params);
|
|
641
|
-
const entryName = normalizeEntryName(params.entryName);
|
|
642
|
-
const targetPath = ensureInsideWorkspace(workspaceRoot, path.join(resolvedTargetPath, entryName));
|
|
643
|
-
const parentStat = await fs.stat(resolvedTargetPath);
|
|
644
|
-
if (!parentStat.isDirectory()) {
|
|
645
|
-
throw new Error(`Path is not a directory: ${resolvedTargetPath}`);
|
|
646
|
-
}
|
|
647
|
-
try {
|
|
648
|
-
await fs.access(targetPath);
|
|
649
|
-
throw new Error(`Path already exists: ${targetPath}`);
|
|
650
|
-
}
|
|
651
|
-
catch (error) {
|
|
652
|
-
const errorCode = typeof error === 'object' && error !== null ? Reflect.get(error, 'code') : undefined;
|
|
653
|
-
if (errorCode !== 'ENOENT') {
|
|
654
|
-
throw error;
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
if (params.isDirectory) {
|
|
658
|
-
await fs.mkdir(targetPath, { recursive: false });
|
|
659
|
-
}
|
|
660
|
-
else {
|
|
661
|
-
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
662
|
-
await ensureWritableParentInsideWorkspace(workspaceRoot, targetPath);
|
|
663
|
-
await fs.writeFile(targetPath, '', 'utf-8');
|
|
664
|
-
}
|
|
665
|
-
return {
|
|
666
|
-
ok: true,
|
|
667
|
-
workspacePath: toDisplayPath(workspaceRoot),
|
|
668
|
-
targetPath: toDisplayPath(targetPath),
|
|
669
|
-
isDirectory: params.isDirectory
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
/**
|
|
673
|
-
* 在工作区内重命名文件或目录,保持目标仍位于原父目录中。
|
|
674
|
-
*/
|
|
675
|
-
export async function renameWorkspaceEntry(params) {
|
|
676
|
-
const { workspaceRoot, resolvedTargetPath } = await resolveEntryTarget(params);
|
|
677
|
-
const newName = normalizeEntryName(params.newName);
|
|
678
|
-
const renamedPath = ensureInsideWorkspace(workspaceRoot, path.join(path.dirname(resolvedTargetPath), newName));
|
|
679
|
-
if (renamedPath === resolvedTargetPath) {
|
|
680
|
-
return {
|
|
681
|
-
ok: true,
|
|
682
|
-
workspacePath: toDisplayPath(workspaceRoot),
|
|
683
|
-
sourcePath: toDisplayPath(resolvedTargetPath),
|
|
684
|
-
targetPath: toDisplayPath(renamedPath)
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
await fs.stat(resolvedTargetPath);
|
|
688
|
-
try {
|
|
689
|
-
await fs.access(renamedPath);
|
|
690
|
-
throw new Error(`Path already exists: ${renamedPath}`);
|
|
691
|
-
}
|
|
692
|
-
catch (error) {
|
|
693
|
-
const errorCode = typeof error === 'object' && error !== null ? Reflect.get(error, 'code') : undefined;
|
|
694
|
-
if (errorCode !== 'ENOENT') {
|
|
695
|
-
throw error;
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
await fs.rename(resolvedTargetPath, renamedPath);
|
|
699
|
-
return {
|
|
700
|
-
ok: true,
|
|
701
|
-
workspacePath: toDisplayPath(workspaceRoot),
|
|
702
|
-
sourcePath: toDisplayPath(resolvedTargetPath),
|
|
703
|
-
targetPath: toDisplayPath(renamedPath)
|
|
704
|
-
};
|
|
705
|
-
}
|
|
706
|
-
/**
|
|
707
|
-
* 将工作区内文件或目录移动到另一个工作区目录,保持原文件名不变。
|
|
708
|
-
*/
|
|
709
|
-
export async function moveWorkspaceEntry(params) {
|
|
710
|
-
const { workspaceRoot, resolvedTargetPath } = await resolveEntryTarget(params);
|
|
711
|
-
const destination = await resolveDirectoryTarget({
|
|
712
|
-
workspacePath: params.workspacePath,
|
|
713
|
-
targetPath: params.destinationPath,
|
|
714
|
-
});
|
|
715
|
-
const destinationStat = await fs.stat(destination.resolvedTargetPath);
|
|
716
|
-
if (!destinationStat.isDirectory()) {
|
|
717
|
-
throw new Error(`Destination is not a directory: ${destination.resolvedTargetPath}`);
|
|
718
|
-
}
|
|
719
|
-
const targetStat = await fs.stat(resolvedTargetPath);
|
|
720
|
-
if (targetStat.isDirectory()) {
|
|
721
|
-
const relativeDestination = path.relative(resolvedTargetPath, destination.resolvedTargetPath);
|
|
722
|
-
if (relativeDestination === '' || (!relativeDestination.startsWith('..') && !path.isAbsolute(relativeDestination))) {
|
|
723
|
-
throw new Error('Cannot move a directory into itself or its descendant');
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
const movedPath = ensureInsideWorkspace(workspaceRoot, path.join(destination.resolvedTargetPath, path.basename(resolvedTargetPath)));
|
|
727
|
-
if (movedPath === resolvedTargetPath) {
|
|
728
|
-
return {
|
|
729
|
-
ok: true,
|
|
730
|
-
workspacePath: toDisplayPath(workspaceRoot),
|
|
731
|
-
sourcePath: toDisplayPath(resolvedTargetPath),
|
|
732
|
-
targetPath: toDisplayPath(movedPath),
|
|
733
|
-
destinationPath: toDisplayPath(destination.resolvedTargetPath),
|
|
734
|
-
};
|
|
735
|
-
}
|
|
736
|
-
try {
|
|
737
|
-
await fs.access(movedPath);
|
|
738
|
-
throw new Error(`Path already exists: ${movedPath}`);
|
|
739
|
-
}
|
|
740
|
-
catch (error) {
|
|
741
|
-
const errorCode = typeof error === 'object' && error !== null ? Reflect.get(error, 'code') : undefined;
|
|
742
|
-
if (errorCode !== 'ENOENT') {
|
|
743
|
-
throw error;
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
await fs.rename(resolvedTargetPath, movedPath);
|
|
747
|
-
return {
|
|
748
|
-
ok: true,
|
|
749
|
-
workspacePath: toDisplayPath(workspaceRoot),
|
|
750
|
-
sourcePath: toDisplayPath(resolvedTargetPath),
|
|
751
|
-
targetPath: toDisplayPath(movedPath),
|
|
752
|
-
destinationPath: toDisplayPath(destination.resolvedTargetPath),
|
|
753
|
-
};
|
|
754
|
-
}
|
|
755
|
-
/**
|
|
756
|
-
* 删除工作区内的文件或目录,并阻止误删工作区根目录。
|
|
757
|
-
*/
|
|
758
|
-
export async function deleteWorkspaceEntry(params) {
|
|
759
|
-
const { workspaceRoot, resolvedTargetPath } = await resolveEntryTarget(params);
|
|
760
|
-
const targetStat = await fs.stat(resolvedTargetPath);
|
|
761
|
-
if (targetStat.isDirectory()) {
|
|
762
|
-
await fs.rm(resolvedTargetPath, { recursive: true, force: false });
|
|
763
|
-
}
|
|
764
|
-
else {
|
|
765
|
-
await fs.unlink(resolvedTargetPath);
|
|
766
|
-
}
|
|
767
|
-
return {
|
|
768
|
-
ok: true,
|
|
769
|
-
workspacePath: toDisplayPath(workspaceRoot),
|
|
770
|
-
targetPath: toDisplayPath(resolvedTargetPath)
|
|
771
|
-
};
|
|
772
|
-
}
|
|
773
|
-
/**
|
|
774
|
-
* 上传文件到工作区指定目录,并可在上传后自动解压常见归档。
|
|
775
|
-
*/
|
|
776
|
-
export async function uploadWorkspaceFiles(params) {
|
|
777
|
-
if (!Array.isArray(params.files) || params.files.length === 0) {
|
|
778
|
-
throw new Error('files are required');
|
|
779
|
-
}
|
|
780
|
-
const { workspaceRoot, resolvedTargetPath } = await resolveWritableDirectory(params);
|
|
781
|
-
const targetStat = await fs.stat(resolvedTargetPath);
|
|
782
|
-
if (!targetStat.isDirectory()) {
|
|
783
|
-
throw new Error(`Path is not a directory: ${resolvedTargetPath}`);
|
|
784
|
-
}
|
|
785
|
-
const files = [];
|
|
786
|
-
for (const file of params.files) {
|
|
787
|
-
const fileName = normalizeUploadedFileName(file.originalname);
|
|
788
|
-
const uploadRelativePath = normalizeUploadedRelativePath(file.relativePath, file.originalname);
|
|
789
|
-
const destinationPath = ensureInsideWorkspace(workspaceRoot, path.join(resolvedTargetPath, uploadRelativePath));
|
|
790
|
-
await fs.mkdir(path.dirname(destinationPath), { recursive: true });
|
|
791
|
-
await ensureWritableParentInsideWorkspace(workspaceRoot, destinationPath);
|
|
792
|
-
await fs.copyFile(file.path, destinationPath);
|
|
793
|
-
let extracted;
|
|
794
|
-
if (params.extractArchives && archiveKind(destinationPath)) {
|
|
795
|
-
extracted = await extractWorkspaceArchive({
|
|
796
|
-
workspacePath: workspaceRoot,
|
|
797
|
-
archivePath: destinationPath,
|
|
798
|
-
outputPath: resolveArchiveUploadOutputPath(resolvedTargetPath, fileName)
|
|
799
|
-
});
|
|
800
|
-
}
|
|
801
|
-
files.push({
|
|
802
|
-
fileName,
|
|
803
|
-
targetPath: toDisplayPath(destinationPath),
|
|
804
|
-
size: file.size,
|
|
805
|
-
extracted
|
|
806
|
-
});
|
|
807
|
-
}
|
|
808
|
-
return {
|
|
809
|
-
ok: true,
|
|
810
|
-
workspacePath: toDisplayPath(workspaceRoot),
|
|
811
|
-
targetPath: toDisplayPath(resolvedTargetPath),
|
|
812
|
-
files
|
|
813
|
-
};
|
|
814
|
-
}
|
|
815
|
-
/**
|
|
816
|
-
* 解析工作区下载目标;目录由路由层打包为 zip,文件直接流式下载。
|
|
817
|
-
*/
|
|
818
|
-
export async function resolveWorkspaceDownloadTarget(params) {
|
|
819
|
-
const { workspaceRoot, resolvedTargetPath } = await resolveEntryTarget(params);
|
|
820
|
-
const targetStat = await fs.stat(resolvedTargetPath);
|
|
821
|
-
const baseName = path.basename(resolvedTargetPath);
|
|
822
|
-
return {
|
|
823
|
-
workspacePath: toDisplayPath(workspaceRoot),
|
|
824
|
-
targetPath: toDisplayPath(resolvedTargetPath),
|
|
825
|
-
resolvedTargetPath,
|
|
826
|
-
fileName: targetStat.isDirectory() ? `${baseName || 'workspace'}.zip` : baseName,
|
|
827
|
-
contentType: targetStat.isDirectory() ? 'application/zip' : 'application/octet-stream',
|
|
828
|
-
isDirectory: targetStat.isDirectory()
|
|
829
|
-
};
|
|
830
|
-
}
|
|
831
|
-
/**
|
|
832
|
-
* 将目录内容以 zip 格式写入输出流,避免在磁盘生成临时包。
|
|
833
|
-
*/
|
|
834
|
-
export async function streamWorkspaceDirectoryZip(directoryPath, output) {
|
|
835
|
-
const archive = new ZipArchive({ zlib: { level: 9 } });
|
|
836
|
-
const completion = new Promise((resolve, reject) => {
|
|
837
|
-
output.on('close', resolve);
|
|
838
|
-
output.on('finish', resolve);
|
|
839
|
-
archive.on('error', reject);
|
|
840
|
-
output.on('error', reject);
|
|
841
|
-
});
|
|
842
|
-
archive.pipe(output);
|
|
843
|
-
archive.directory(directoryPath, false);
|
|
844
|
-
await archive.finalize();
|
|
845
|
-
await completion;
|
|
846
|
-
}
|
|
847
|
-
async function extractZipArchive(archivePath, outputRoot, meter) {
|
|
848
|
-
let extractedEntries = 0;
|
|
849
|
-
let extractionError;
|
|
850
|
-
const sourceStream = createReadStream(archivePath);
|
|
851
|
-
const parsedStream = sourceStream.pipe(unzipper.Parse({ forceStream: true }));
|
|
852
|
-
try {
|
|
853
|
-
for await (const entry of parsedStream) {
|
|
854
|
-
if (extractionError) {
|
|
855
|
-
await drainZipEntry(entry);
|
|
856
|
-
continue;
|
|
857
|
-
}
|
|
858
|
-
const entryPath = decodeZipEntryPath(entry);
|
|
859
|
-
try {
|
|
860
|
-
const destinationPath = ensureArchiveEntryInside(outputRoot, entryPath);
|
|
861
|
-
trackArchiveEntry(meter);
|
|
862
|
-
if (entry.type === 'Directory') {
|
|
863
|
-
await fs.mkdir(destinationPath, { recursive: true });
|
|
864
|
-
continue;
|
|
865
|
-
}
|
|
866
|
-
if (entry.type !== 'File') {
|
|
867
|
-
throw new Error(`Unsupported archive entry type: ${entryPath}`);
|
|
868
|
-
}
|
|
869
|
-
await fs.mkdir(path.dirname(destinationPath), { recursive: true });
|
|
870
|
-
await ensureWritableParentInsideWorkspace(outputRoot, destinationPath);
|
|
871
|
-
await pipelineWithArchiveLimits(entry, destinationPath, meter);
|
|
872
|
-
extractedEntries += 1;
|
|
873
|
-
}
|
|
874
|
-
catch (error) {
|
|
875
|
-
extractionError = error;
|
|
876
|
-
await drainZipEntry(entry);
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
finally {
|
|
881
|
-
sourceStream.destroy();
|
|
882
|
-
parsedStream.destroy();
|
|
883
|
-
}
|
|
884
|
-
if (extractionError) {
|
|
885
|
-
throw extractionError;
|
|
886
|
-
}
|
|
887
|
-
return extractedEntries;
|
|
888
|
-
}
|
|
889
|
-
async function extractTarArchive(archivePath, outputRoot, gzip, meter) {
|
|
890
|
-
let extractedEntries = 0;
|
|
891
|
-
let extractionError;
|
|
892
|
-
/**
|
|
893
|
-
* 校验 tar 条目并通过 filter 返回值控制是否解压。
|
|
894
|
-
* tar 的 filter 回调位于流解析链路中,直接抛错会变成未处理异常;这里先记录错误,待流结束后统一 reject。
|
|
895
|
-
*/
|
|
896
|
-
const shouldExtractTarEntry = (entryPath, entry) => {
|
|
897
|
-
if (extractionError) {
|
|
898
|
-
return false;
|
|
899
|
-
}
|
|
900
|
-
try {
|
|
901
|
-
ensureArchiveEntryInside(outputRoot, entryPath);
|
|
902
|
-
trackArchiveEntry(meter);
|
|
903
|
-
const entryType = String(entry.type || '').toLowerCase();
|
|
904
|
-
if (entryType === 'symboliclink' || entryType === 'link') {
|
|
905
|
-
throw new Error(`Archive links are not supported: ${entryPath}`);
|
|
906
|
-
}
|
|
907
|
-
if (entryType !== 'file' && entryType !== 'oldfile' && entryType !== 'contiguousfile' && entryType !== 'directory') {
|
|
908
|
-
throw new Error(`Unsupported archive entry type: ${entryPath}`);
|
|
909
|
-
}
|
|
910
|
-
if (typeof entry.size === 'number') {
|
|
911
|
-
trackArchiveBytes(meter, entry.size);
|
|
912
|
-
}
|
|
913
|
-
if (entryType !== 'directory') {
|
|
914
|
-
extractedEntries += 1;
|
|
915
|
-
}
|
|
916
|
-
return true;
|
|
917
|
-
}
|
|
918
|
-
catch (error) {
|
|
919
|
-
extractionError = error;
|
|
920
|
-
return false;
|
|
921
|
-
}
|
|
922
|
-
};
|
|
923
|
-
await tar.x({
|
|
924
|
-
file: archivePath,
|
|
925
|
-
cwd: outputRoot,
|
|
926
|
-
gzip,
|
|
927
|
-
strict: true,
|
|
928
|
-
filter: (entryPath, entry) => {
|
|
929
|
-
return shouldExtractTarEntry(entryPath, entry);
|
|
930
|
-
}
|
|
931
|
-
});
|
|
932
|
-
if (extractionError) {
|
|
933
|
-
throw extractionError;
|
|
934
|
-
}
|
|
935
|
-
return extractedEntries;
|
|
936
|
-
}
|
|
937
|
-
async function extractGzipFile(archivePath, outputRoot, meter) {
|
|
938
|
-
const outputName = stripArchiveExtension(path.basename(archivePath));
|
|
939
|
-
const destinationPath = ensureArchiveEntryInside(outputRoot, outputName || 'decompressed');
|
|
940
|
-
trackArchiveEntry(meter);
|
|
941
|
-
await fs.mkdir(path.dirname(destinationPath), { recursive: true });
|
|
942
|
-
await ensureWritableParentInsideWorkspace(outputRoot, destinationPath);
|
|
943
|
-
await pipelineWithArchiveLimits(createReadStream(archivePath).pipe(createGunzip()), destinationPath, meter);
|
|
944
|
-
return 1;
|
|
945
|
-
}
|
|
946
|
-
/**
|
|
947
|
-
* 解压工作区中的归档文件到指定目录,支持 zip、tar、tar.gz/tgz 与单文件 gz。
|
|
948
|
-
*/
|
|
949
|
-
export async function extractWorkspaceArchive(params) {
|
|
950
|
-
const { requestedRoot, workspaceRoot } = await resolveWorkspaceRoots(params.workspacePath);
|
|
951
|
-
const { resolvedTargetPath: outputRoot } = await resolveWritableDirectory({
|
|
952
|
-
workspacePath: params.workspacePath,
|
|
953
|
-
targetPath: params.outputPath || ''
|
|
954
|
-
});
|
|
955
|
-
const lexicalArchivePath = resolveRequestedPathInsideWorkspace(requestedRoot, workspaceRoot, String(params.archivePath || '').trim());
|
|
956
|
-
const archivePath = await ensureRealPathInsideWorkspace(workspaceRoot, lexicalArchivePath);
|
|
957
|
-
const archiveStat = await fs.stat(archivePath);
|
|
958
|
-
if (!archiveStat.isFile()) {
|
|
959
|
-
throw new Error(`Archive path is not a file: ${archivePath}`);
|
|
960
|
-
}
|
|
961
|
-
if (!(await pathExists(outputRoot))) {
|
|
962
|
-
await fs.mkdir(outputRoot, { recursive: true });
|
|
963
|
-
}
|
|
964
|
-
const outputStat = await fs.stat(outputRoot);
|
|
965
|
-
if (!outputStat.isDirectory()) {
|
|
966
|
-
throw new Error(`Output path is not a directory: ${outputRoot}`);
|
|
967
|
-
}
|
|
968
|
-
const kind = archiveKind(archivePath);
|
|
969
|
-
if (!kind) {
|
|
970
|
-
throw new Error('Unsupported archive type. Supported: .zip, .tar, .tar.gz, .tgz, .gz');
|
|
971
|
-
}
|
|
972
|
-
let extractedEntries = 0;
|
|
973
|
-
const meter = { entries: 0, totalBytes: 0 };
|
|
974
|
-
if (kind === 'zip') {
|
|
975
|
-
extractedEntries = await extractZipArchive(archivePath, outputRoot, meter);
|
|
976
|
-
}
|
|
977
|
-
else if (kind === 'tar' || kind === 'tgz') {
|
|
978
|
-
extractedEntries = await extractTarArchive(archivePath, outputRoot, kind === 'tgz', meter);
|
|
979
|
-
}
|
|
980
|
-
else {
|
|
981
|
-
extractedEntries = await extractGzipFile(archivePath, outputRoot, meter);
|
|
982
|
-
}
|
|
983
|
-
return {
|
|
984
|
-
ok: true,
|
|
985
|
-
workspacePath: toDisplayPath(workspaceRoot),
|
|
986
|
-
archivePath: toDisplayPath(archivePath),
|
|
987
|
-
targetPath: toDisplayPath(outputRoot),
|
|
988
|
-
extractedEntries
|
|
989
|
-
};
|
|
990
|
-
}
|
|
991
|
-
/**
|
|
992
|
-
* 展开简单花括号模式,如 *.{ts,tsx} -> *.ts, *.tsx。
|
|
993
|
-
*/
|
|
994
|
-
function expandBracePattern(pattern) {
|
|
995
|
-
const match = pattern.match(/^(.*?)\{([^}]+)\}(.*)$/);
|
|
996
|
-
if (!match) {
|
|
997
|
-
return [pattern];
|
|
998
|
-
}
|
|
999
|
-
const [, prefix, options, suffix] = match;
|
|
1000
|
-
return options.split(',').flatMap((option) => expandBracePattern(prefix + option.trim() + suffix));
|
|
1001
|
-
}
|
|
1002
|
-
/**
|
|
1003
|
-
* 判断文件名是否匹配简单 glob 掩码(支持 *、?、** 与 {a,b,c})。
|
|
1004
|
-
*/
|
|
1005
|
-
function matchesFileMask(fileName, mask) {
|
|
1006
|
-
const normalizedMask = String(mask || '').trim();
|
|
1007
|
-
if (!normalizedMask || normalizedMask === '*' || normalizedMask === '*.*') {
|
|
1008
|
-
return true;
|
|
1009
|
-
}
|
|
1010
|
-
const normalizedFileName = fileName.toLowerCase();
|
|
1011
|
-
const patterns = expandBracePattern(normalizedMask.toLowerCase());
|
|
1012
|
-
return patterns.some((pattern) => {
|
|
1013
|
-
if (pattern === '*')
|
|
1014
|
-
return true;
|
|
1015
|
-
const regexSource = pattern
|
|
1016
|
-
.replace(/\./g, '\\.')
|
|
1017
|
-
.replace(/\*\*/g, '<<GLOBSTAR>>')
|
|
1018
|
-
.replace(/\*/g, '[^/]*')
|
|
1019
|
-
.replace(/\?/g, '[^/]')
|
|
1020
|
-
.replace(/<<GLOBSTAR>>/g, '.*');
|
|
1021
|
-
const regex = new RegExp(`^${regexSource}$`);
|
|
1022
|
-
return regex.test(normalizedFileName);
|
|
1023
|
-
});
|
|
1024
|
-
}
|
|
1025
|
-
/**
|
|
1026
|
-
* 跳过明显不适合文本搜索/替换的目录。
|
|
1027
|
-
*/
|
|
1028
|
-
function isSkippedSearchDirectory(name) {
|
|
1029
|
-
return name === '.git' || name === 'node_modules' || name === '.svn' || name === '.hg';
|
|
1030
|
-
}
|
|
1031
|
-
const SEARCH_MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
1032
|
-
const SEARCH_DEFAULT_MAX_RESULTS = 1000;
|
|
1033
|
-
/**
|
|
1034
|
-
* 在工作区范围内搜索文件内容。
|
|
1035
|
-
* 主流程:安全解析工作区根 -> 递归遍历目录 -> 按文件掩码过滤 -> 逐行匹配关键词 -> 返回匹配列表。
|
|
1036
|
-
*/
|
|
1037
|
-
export async function searchWorkspaceFiles(params) {
|
|
1038
|
-
const { workspaceRoot } = await resolveWorkspaceRoots(params.workspacePath);
|
|
1039
|
-
const keyword = String(params.keyword || '').trim();
|
|
1040
|
-
if (!keyword) {
|
|
1041
|
-
throw new Error('keyword is required');
|
|
1042
|
-
}
|
|
1043
|
-
const ignoreCase = Boolean(params.ignoreCase);
|
|
1044
|
-
const fileMask = String(params.fileMask || '').trim();
|
|
1045
|
-
const maxResults = typeof params.maxResults === 'number' && params.maxResults > 0
|
|
1046
|
-
? params.maxResults
|
|
1047
|
-
: SEARCH_DEFAULT_MAX_RESULTS;
|
|
1048
|
-
const searchKeyword = ignoreCase ? keyword.toLowerCase() : keyword;
|
|
1049
|
-
const mode = params.mode === 'filename' ? 'filename' : 'content';
|
|
1050
|
-
const isFilenameMode = mode === 'filename';
|
|
1051
|
-
const matches = [];
|
|
1052
|
-
let totalFiles = 0;
|
|
1053
|
-
let truncated = false;
|
|
1054
|
-
async function walk(directoryPath) {
|
|
1055
|
-
if (truncated)
|
|
1056
|
-
return;
|
|
1057
|
-
let entries;
|
|
1058
|
-
try {
|
|
1059
|
-
entries = await fs.readdir(directoryPath, { withFileTypes: true });
|
|
1060
|
-
}
|
|
1061
|
-
catch {
|
|
1062
|
-
return;
|
|
1063
|
-
}
|
|
1064
|
-
for (const entry of entries) {
|
|
1065
|
-
if (truncated)
|
|
1066
|
-
return;
|
|
1067
|
-
const entryPath = ensureInsideWorkspace(workspaceRoot, path.join(directoryPath, entry.name));
|
|
1068
|
-
if (entry.isSymbolicLink())
|
|
1069
|
-
continue;
|
|
1070
|
-
if (entry.isDirectory()) {
|
|
1071
|
-
if (isSkippedSearchDirectory(entry.name))
|
|
1072
|
-
continue;
|
|
1073
|
-
await walk(entryPath);
|
|
1074
|
-
continue;
|
|
1075
|
-
}
|
|
1076
|
-
if (!entry.isFile())
|
|
1077
|
-
continue;
|
|
1078
|
-
if (fileMask && !matchesFileMask(entry.name, fileMask))
|
|
1079
|
-
continue;
|
|
1080
|
-
totalFiles += 1;
|
|
1081
|
-
let stat;
|
|
1082
|
-
try {
|
|
1083
|
-
stat = await fs.stat(entryPath);
|
|
1084
|
-
}
|
|
1085
|
-
catch {
|
|
1086
|
-
continue;
|
|
1087
|
-
}
|
|
1088
|
-
if (stat.size > SEARCH_MAX_FILE_BYTES)
|
|
1089
|
-
continue;
|
|
1090
|
-
if (isFilenameMode) {
|
|
1091
|
-
const searchName = ignoreCase ? entry.name.toLowerCase() : entry.name;
|
|
1092
|
-
const matchIndex = searchName.indexOf(searchKeyword);
|
|
1093
|
-
if (matchIndex !== -1) {
|
|
1094
|
-
matches.push({
|
|
1095
|
-
filePath: toDisplayPath(entryPath),
|
|
1096
|
-
lineNumber: 0,
|
|
1097
|
-
lineContent: entry.name,
|
|
1098
|
-
matchStart: matchIndex,
|
|
1099
|
-
matchEnd: matchIndex + keyword.length
|
|
1100
|
-
});
|
|
1101
|
-
if (matches.length >= maxResults) {
|
|
1102
|
-
truncated = true;
|
|
1103
|
-
return;
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
continue;
|
|
1107
|
-
}
|
|
1108
|
-
let content;
|
|
1109
|
-
try {
|
|
1110
|
-
content = await fs.readFile(entryPath, 'utf-8');
|
|
1111
|
-
}
|
|
1112
|
-
catch {
|
|
1113
|
-
continue;
|
|
1114
|
-
}
|
|
1115
|
-
const lines = content.split(/\r?\n/);
|
|
1116
|
-
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
1117
|
-
const line = lines[lineIndex];
|
|
1118
|
-
const searchLine = ignoreCase ? line.toLowerCase() : line;
|
|
1119
|
-
let searchIndex = 0;
|
|
1120
|
-
while (true) {
|
|
1121
|
-
const matchIndex = searchLine.indexOf(searchKeyword, searchIndex);
|
|
1122
|
-
if (matchIndex === -1)
|
|
1123
|
-
break;
|
|
1124
|
-
matches.push({
|
|
1125
|
-
filePath: toDisplayPath(entryPath),
|
|
1126
|
-
lineNumber: lineIndex + 1,
|
|
1127
|
-
lineContent: line,
|
|
1128
|
-
matchStart: matchIndex,
|
|
1129
|
-
matchEnd: matchIndex + keyword.length
|
|
1130
|
-
});
|
|
1131
|
-
if (matches.length >= maxResults) {
|
|
1132
|
-
truncated = true;
|
|
1133
|
-
return;
|
|
1134
|
-
}
|
|
1135
|
-
searchIndex = matchIndex + 1;
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
await walk(workspaceRoot);
|
|
1141
|
-
return {
|
|
1142
|
-
workspacePath: toDisplayPath(workspaceRoot),
|
|
1143
|
-
keyword,
|
|
1144
|
-
totalFiles,
|
|
1145
|
-
totalMatches: matches.length,
|
|
1146
|
-
matches,
|
|
1147
|
-
truncated
|
|
1148
|
-
};
|
|
1149
|
-
}
|
|
1150
|
-
/**
|
|
1151
|
-
* 在单个文件内执行安全替换,只写入确实有变化的内容。
|
|
1152
|
-
*/
|
|
1153
|
-
async function replaceInFile(workspaceRoot, filePath, keyword, replacement, ignoreCase) {
|
|
1154
|
-
let content;
|
|
1155
|
-
try {
|
|
1156
|
-
content = await fs.readFile(filePath, 'utf-8');
|
|
1157
|
-
}
|
|
1158
|
-
catch {
|
|
1159
|
-
return { changed: false, count: 0 };
|
|
1160
|
-
}
|
|
1161
|
-
const originalContent = content;
|
|
1162
|
-
let count = 0;
|
|
1163
|
-
if (ignoreCase) {
|
|
1164
|
-
const lowerKeyword = keyword.toLowerCase();
|
|
1165
|
-
let index = 0;
|
|
1166
|
-
while (true) {
|
|
1167
|
-
const lowerContent = content.toLowerCase();
|
|
1168
|
-
const matchIndex = lowerContent.indexOf(lowerKeyword, index);
|
|
1169
|
-
if (matchIndex === -1)
|
|
1170
|
-
break;
|
|
1171
|
-
content = content.slice(0, matchIndex) + replacement + content.slice(matchIndex + keyword.length);
|
|
1172
|
-
count += 1;
|
|
1173
|
-
const step = replacement.length || 1;
|
|
1174
|
-
index = matchIndex + step;
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
else {
|
|
1178
|
-
let index = 0;
|
|
1179
|
-
while (true) {
|
|
1180
|
-
const matchIndex = content.indexOf(keyword, index);
|
|
1181
|
-
if (matchIndex === -1)
|
|
1182
|
-
break;
|
|
1183
|
-
content = content.slice(0, matchIndex) + replacement + content.slice(matchIndex + keyword.length);
|
|
1184
|
-
count += 1;
|
|
1185
|
-
const step = replacement.length || 1;
|
|
1186
|
-
index = matchIndex + step;
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
if (content === originalContent) {
|
|
1190
|
-
return { changed: false, count: 0 };
|
|
1191
|
-
}
|
|
1192
|
-
await ensureWritableParentInsideWorkspace(workspaceRoot, filePath);
|
|
1193
|
-
await fs.writeFile(filePath, content, 'utf-8');
|
|
1194
|
-
return { changed: true, count };
|
|
1195
|
-
}
|
|
1196
|
-
/**
|
|
1197
|
-
* 在工作区范围内批量替换文件内容。
|
|
1198
|
-
* 可指定 filePath 只替换单个文件;未指定时递归替换所有匹配掩码的文件。
|
|
1199
|
-
*/
|
|
1200
|
-
export async function replaceWorkspaceFiles(params) {
|
|
1201
|
-
const { workspaceRoot } = await resolveWorkspaceRoots(params.workspacePath);
|
|
1202
|
-
const keyword = String(params.keyword || '').trim();
|
|
1203
|
-
if (!keyword) {
|
|
1204
|
-
throw new Error('keyword is required');
|
|
1205
|
-
}
|
|
1206
|
-
const replacement = String(params.replacement ?? '');
|
|
1207
|
-
const ignoreCase = Boolean(params.ignoreCase);
|
|
1208
|
-
const fileMask = String(params.fileMask || '').trim();
|
|
1209
|
-
const singleFilePath = String(params.filePath || '').trim();
|
|
1210
|
-
let changedFiles = 0;
|
|
1211
|
-
let changedMatches = 0;
|
|
1212
|
-
if (singleFilePath) {
|
|
1213
|
-
const { resolvedFilePath } = await resolveFileTarget({
|
|
1214
|
-
workspacePath: params.workspacePath,
|
|
1215
|
-
filePath: singleFilePath
|
|
1216
|
-
});
|
|
1217
|
-
const result = await replaceInFile(workspaceRoot, resolvedFilePath, keyword, replacement, ignoreCase);
|
|
1218
|
-
if (result.changed) {
|
|
1219
|
-
changedFiles = 1;
|
|
1220
|
-
changedMatches = result.count;
|
|
1221
|
-
}
|
|
1222
|
-
return {
|
|
1223
|
-
workspacePath: toDisplayPath(workspaceRoot),
|
|
1224
|
-
keyword,
|
|
1225
|
-
replacement,
|
|
1226
|
-
changedFiles,
|
|
1227
|
-
changedMatches
|
|
1228
|
-
};
|
|
1229
|
-
}
|
|
1230
|
-
async function walk(directoryPath) {
|
|
1231
|
-
let entries;
|
|
1232
|
-
try {
|
|
1233
|
-
entries = await fs.readdir(directoryPath, { withFileTypes: true });
|
|
1234
|
-
}
|
|
1235
|
-
catch {
|
|
1236
|
-
return;
|
|
1237
|
-
}
|
|
1238
|
-
for (const entry of entries) {
|
|
1239
|
-
const entryPath = ensureInsideWorkspace(workspaceRoot, path.join(directoryPath, entry.name));
|
|
1240
|
-
if (entry.isSymbolicLink())
|
|
1241
|
-
continue;
|
|
1242
|
-
if (entry.isDirectory()) {
|
|
1243
|
-
if (isSkippedSearchDirectory(entry.name))
|
|
1244
|
-
continue;
|
|
1245
|
-
await walk(entryPath);
|
|
1246
|
-
continue;
|
|
1247
|
-
}
|
|
1248
|
-
if (!entry.isFile())
|
|
1249
|
-
continue;
|
|
1250
|
-
if (fileMask && !matchesFileMask(entry.name, fileMask))
|
|
1251
|
-
continue;
|
|
1252
|
-
try {
|
|
1253
|
-
const stat = await fs.stat(entryPath);
|
|
1254
|
-
if (stat.size > SEARCH_MAX_FILE_BYTES)
|
|
1255
|
-
continue;
|
|
1256
|
-
}
|
|
1257
|
-
catch {
|
|
1258
|
-
continue;
|
|
1259
|
-
}
|
|
1260
|
-
const result = await replaceInFile(workspaceRoot, entryPath, keyword, replacement, ignoreCase);
|
|
1261
|
-
if (result.changed) {
|
|
1262
|
-
changedFiles += 1;
|
|
1263
|
-
changedMatches += result.count;
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
await walk(workspaceRoot);
|
|
1268
|
-
return {
|
|
1269
|
-
workspacePath: toDisplayPath(workspaceRoot),
|
|
1270
|
-
keyword,
|
|
1271
|
-
replacement,
|
|
1272
|
-
changedFiles,
|
|
1273
|
-
changedMatches
|
|
1274
|
-
};
|
|
1275
|
-
}
|
|
1
|
+
import{createReadStream as U,createWriteStream as ot,promises as o}from"node:fs";import d from"node:path";import{Readable as st}from"node:stream";import{pipeline as ct}from"node:stream/promises";import{TextDecoder as lt}from"node:util";import{createGunzip as ht}from"node:zlib";import*as ut from"tar";import dt from"unzipper";const{ZipArchive:wt}=await import("archiver"),y={maxEntries:1e4,maxFileBytes:512*1024*1024,maxTotalBytes:2*1024*1024*1024,maxPathDepth:64,maxPathLength:4096},ft=new lt("gbk");function h(t){return String(t||"").replace(/\\/g,"/")}function j(t){const e=d.resolve(String(t||"").trim());if(!e)throw new Error("workspacePath is required");return e}async function pt(t){const e=j(t),r=await o.realpath(e);if(!(await o.stat(r)).isDirectory())throw new Error(`Workspace path is not a directory: ${e}`);return r}async function k(t){return{requestedRoot:j(t),workspaceRoot:await pt(t)}}function p(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,e){const r=await o.realpath(e);return p(t,r)}async function v(t,e){const r=await o.realpath(d.dirname(e));p(t,r);try{if((await o.lstat(e)).isSymbolicLink())throw new Error(`Refusing to write through symbolic link: ${e}`)}catch(i){if((typeof i=="object"&&i!==null?Reflect.get(i,"code"):void 0)!=="ENOENT")throw i}}function z(t,e,r){const i=d.resolve(r),a=d.relative(t,i);if(a.startsWith("..")||d.isAbsolute(a))throw new Error(`Target path is outside workspace: ${i}`);return p(e,d.join(e,a))}async function $({workspacePath:t,targetPath:e=""}){const{requestedRoot:r,workspaceRoot:i}=await k(t),a=String(e||"").trim(),n=a?z(r,i,a):i,s=await x(i,n);return{workspaceRoot:i,resolvedTargetPath:s}}function D(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 q(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=q(t);return D(d.basename(e.replace(/\\/g,"/")))}function mt(t,e){if(!t)return B(e);const i=q(String(t||e||"")).replace(/\\/g,"/").split("/").filter(Boolean).map(a=>D(a));return i.length===0?B(e):d.join(...i)}function _(t){return(t&4095).toString(8).padStart(4,"0")}function gt(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 C(t,e,r,i,a,n,s){const c=t&e?"r":"-",u=t&r?"w":"-",l=(t&i)!==0,w=(t&a)!==0?l?n:s:l?"x":"-";return`${c}${u}${w}`}function yt(t){return[C(t,256,128,64,2048,"s","S"),C(t,32,16,8,1024,"s","S"),C(t,4,2,1,512,"t","T")].join("")}function Pt(t){if(!(t.length<24||t.toString("hex",0,8)!=="89504e470d0a1a0a"))return{format:"png",width:t.readUInt32BE(16),height:t.readUInt32BE(20)}}function kt(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 xt(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,r){if((await o.lstat(e)).isSymbolicLink())throw new Error(`Refusing to change permissions on symbolic link: ${e}`);const a=await o.stat(e);if(!r||!a.isDirectory())return[e];const n=await o.readdir(e),s=await Promise.all(n.map(c=>{const u=p(t,d.join(e,c));return G(t,u,!0)}));return[e,...s.flat()]}async function vt(t,e){if((await o.lstat(e)).isSymbolicLink())throw new Error(`Refusing to change permissions on symbolic link: ${e}`);await x(t,e)}async function St(t){const e=await o.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 Pt(a)||kt(a)||xt(a)||Et(a)}finally{await e.close()}}async function S({workspacePath:t,targetPath:e},r={}){const{requestedRoot:i,workspaceRoot:a}=await k(t),n=String(e||"").trim();if(!n)throw new Error("targetPath is required");const s=z(i,a,n),c=await x(a,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 k(t.workspacePath),i=String(t.targetPath||"").trim();if(!i)return{workspaceRoot:r,resolvedTargetPath:r};const a=z(e,r,i);if(await J(a))return{workspaceRoot:r,resolvedTargetPath:await x(r,a)};const n=await o.realpath(d.dirname(a));return p(r,n),{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=D(K(e)||"extracted");return d.join(t,r)}function I(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>y.maxPathLength||i.length>y.maxPathDepth)throw new Error(`Archive entry path is too deep or too long: ${e}`);return p(t,d.join(t,r))}function Tt(t){const e=t.props?.pathBuffer;if(!e||t.props?.flags?.isUnicode)return t.path;const r=ft.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 A(t){if(t.entries+=1,t.entries>y.maxEntries)throw new Error(`Archive contains too many entries. Limit: ${y.maxEntries}`)}function X(t,e){if(t.totalBytes+=e,e>y.maxFileBytes)throw new Error(`Archive entry exceeds max size. Limit: ${y.maxFileBytes} bytes`);if(t.totalBytes>y.maxTotalBytes)throw new Error(`Archive extraction exceeds total size. Limit: ${y.maxTotalBytes} bytes`)}async function Y(t,e,r){let i=0;t.on("data",a=>{const n=Buffer.isBuffer(a)?a.length:Buffer.byteLength(a);i+=n,(i>y.maxFileBytes||r.totalBytes+i>y.maxTotalBytes)&&t instanceof st&&t.destroy(new Error("Archive extraction size limit exceeded"))}),await ct(t,ot(e)),X(r,i)}async function J(t){try{return await o.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 k(t),a=String(e||"").trim();if(!a)throw new Error("filePath is required");return{workspaceRoot:i,resolvedFilePath:z(r,i,a)}}async function Ot(t){const{workspaceRoot:e,resolvedTargetPath:r}=await $(t);if(!(await o.stat(r)).isDirectory())throw new Error(`Path is not a directory: ${r}`);const a=await o.readdir(r,{withFileTypes:!0}),n=await Promise.all(a.map(async s=>{const c=d.join(r,s.name),u=s.isFile()?(await o.stat(c)).size:void 0;return{name:s.name,path:h(c),isDirectory:s.isDirectory(),size:u}}));return{currentPath:h(r),workspacePath:h(e),items:n}}async function Ut(t){const{workspaceRoot:e,resolvedFilePath:r}=await W(t),i=await x(e,r);if(!(await o.stat(i)).isFile())throw new Error(`Path is not a file: ${i}`);const n=await o.readFile(i,"utf-8");return{workspacePath:h(e),filePath:h(i),content:n}}const Q={maxEntries:1e5,maxDepth:32};async function Rt(t,e){let r=0,i=0;async function a(n,s){if(s>=Q.maxDepth)return;let c;try{c=await o.readdir(n,{withFileTypes:!0})}catch{return}for(const u of c){if(i+=1,i>Q.maxEntries)return;if(u.isSymbolicLink())continue;const l=p(t,d.join(n,u.name));try{if(u.isDirectory())await a(l,s+1);else if(u.isFile()){const f=await o.stat(l);r+=f.size}}catch{}}}return await a(e,0),r}async function jt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await S(t,{allowWorkspaceRoot:!0}),[i,a]=await Promise.all([o.stat(r),o.lstat(r)]),n=i.isDirectory(),s=i.isFile()?await St(r):void 0,c=n?await Rt(e,r):i.size;return{workspacePath:h(e),targetPath:h(r),name:d.basename(r),isDirectory:i.isDirectory(),isFile:i.isFile(),isSymbolicLink:a.isSymbolicLink(),size:c,mode:i.mode,modeOctal:_(i.mode),permissions:yt(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 qt(t){const e=gt(t.mode),r=!!t.recursive,{workspaceRoot:i,resolvedTargetPath:a}=await S(t),n=await G(i,a,r);for(const s of n)await vt(i,s),await o.chmod(s,e);return{ok:!0,workspacePath:h(i),targetPath:h(a),mode:e,modeOctal:_(e),recursive:r,changedCount:n.length}}async function _t(t){const{workspaceRoot:e,resolvedFilePath:r}=await W(t),i=await x(e,r),a=await o.stat(i);if(!a.isFile())throw new Error(`Path is not a file: ${i}`);const n=d.extname(i).toLowerCase();if(n===".doc")return{workspacePath:h(e),filePath:h(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(n!==".docx")throw new Error("Document preview only supports .docx files");return{workspacePath:h(e),filePath:h(i),kind:"html",content:"",size:a.size,mtimeMs:a.mtimeMs}}async function Gt(t){const{workspaceRoot:e,resolvedFilePath:r}=await W(t);return await o.mkdir(d.dirname(r),{recursive:!0}),await v(e,r),await o.writeFile(r,String(t.content??""),"utf-8"),{ok:!0,workspacePath:h(e),filePath:h(r)}}async function Zt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await $(t),i=D(t.entryName),a=p(e,d.join(r,i));if(!(await o.stat(r)).isDirectory())throw new Error(`Path is not a directory: ${r}`);try{throw await o.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 o.mkdir(a,{recursive:!1}):(await o.mkdir(d.dirname(a),{recursive:!0}),await v(e,a),await o.writeFile(a,"","utf-8")),{ok:!0,workspacePath:h(e),targetPath:h(a),isDirectory:t.isDirectory}}async function Ht(t){const{workspaceRoot:e,resolvedTargetPath:r}=await S(t),i=D(t.newName),a=p(e,d.join(d.dirname(r),i));if(a===r)return{ok:!0,workspacePath:h(e),sourcePath:h(r),targetPath:h(a)};await o.stat(r);try{throw await o.access(a),new Error(`Path already exists: ${a}`)}catch(n){if((typeof n=="object"&&n!==null?Reflect.get(n,"code"):void 0)!=="ENOENT")throw n}return await o.rename(r,a),{ok:!0,workspacePath:h(e),sourcePath:h(r),targetPath:h(a)}}async function Kt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await S(t),i=await $({workspacePath:t.workspacePath,targetPath:t.destinationPath});if(!(await o.stat(i.resolvedTargetPath)).isDirectory())throw new Error(`Destination is not a directory: ${i.resolvedTargetPath}`);if((await o.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=p(e,d.join(i.resolvedTargetPath,d.basename(r)));if(s===r)return{ok:!0,workspacePath:h(e),sourcePath:h(r),targetPath:h(s),destinationPath:h(i.resolvedTargetPath)};try{throw await o.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 o.rename(r,s),{ok:!0,workspacePath:h(e),sourcePath:h(r),targetPath:h(s),destinationPath:h(i.resolvedTargetPath)}}async function Vt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await S(t);return(await o.stat(r)).isDirectory()?await o.rm(r,{recursive:!0,force:!1}):await o.unlink(r),{ok:!0,workspacePath:h(e),targetPath:h(r)}}async function Xt(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 o.stat(r)).isDirectory())throw new Error(`Path is not a directory: ${r}`);const a=[];for(const n of t.files){const s=B(n.originalname),c=mt(n.relativePath,n.originalname),u=p(e,d.join(r,c));await o.mkdir(d.dirname(u),{recursive:!0}),await v(e,u),await o.copyFile(n.path,u);let l;t.extractArchives&&H(u)&&(l=await Wt({workspacePath:e,archivePath:u,outputPath:Ft(r,s)})),a.push({fileName:s,targetPath:h(u),size:n.size,extracted:l})}return{ok:!0,workspacePath:h(e),targetPath:h(r),files:a}}async function Yt(t){const{workspaceRoot:e,resolvedTargetPath:r}=await S(t),i=await o.stat(r),a=d.basename(r);return{workspacePath:h(e),targetPath:h(r),resolvedTargetPath:r,fileName:i.isDirectory()?`${a||"workspace"}.zip`:a,contentType:i.isDirectory()?"application/zip":"application/octet-stream",isDirectory:i.isDirectory()}}async function Jt(t,e){const r=new wt({zlib:{level:9}}),i=new Promise((a,n)=>{e.on("close",a),e.on("finish",a),r.on("error",n),e.on("error",n)});r.pipe(e),r.directory(t,!1),await r.finalize(),await i}async function zt(t,e,r){let i=0,a;const n=U(t),s=n.pipe(dt.Parse({forceStream:!0}));try{for await(const c of s){if(a){await V(c);continue}const u=Tt(c);try{const l=I(e,u);if(A(r),c.type==="Directory"){await o.mkdir(l,{recursive:!0});continue}if(c.type!=="File")throw new Error(`Unsupported archive entry type: ${u}`);await o.mkdir(d.dirname(l),{recursive:!0}),await v(e,l),await Y(c,l,r),i+=1}catch(l){a=l,await V(c)}}}finally{n.destroy(),s.destroy()}if(a)throw a;return i}async function Dt(t,e,r,i){let a=0,n;const s=(c,u)=>{if(n)return!1;try{I(e,c),A(i);const l=String(u.type||"").toLowerCase();if(l==="symboliclink"||l==="link")throw new Error(`Archive links are not supported: ${c}`);if(l!=="file"&&l!=="oldfile"&&l!=="contiguousfile"&&l!=="directory")throw new Error(`Unsupported archive entry type: ${c}`);return typeof u.size=="number"&&X(i,u.size),l!=="directory"&&(a+=1),!0}catch(l){return n=l,!1}};if(await ut.x({file:t,cwd:e,gzip:r,strict:!0,filter:(c,u)=>s(c,u)}),n)throw n;return a}async function Lt(t,e,r){const i=K(d.basename(t)),a=I(e,i||"decompressed");return A(r),await o.mkdir(d.dirname(a),{recursive:!0}),await v(e,a),await Y(U(t).pipe(ht()),a,r),1}async function Wt(t){const{requestedRoot:e,workspaceRoot:r}=await k(t.workspacePath),{resolvedTargetPath:i}=await Z({workspacePath:t.workspacePath,targetPath:t.outputPath||""}),a=z(e,r,String(t.archivePath||"").trim()),n=await x(r,a);if(!(await o.stat(n)).isFile())throw new Error(`Archive path is not a file: ${n}`);if(await J(i)||await o.mkdir(i,{recursive:!0}),!(await o.stat(i)).isDirectory())throw new Error(`Output path is not a directory: ${i}`);const u=H(n);if(!u)throw new Error("Unsupported archive type. Supported: .zip, .tar, .tar.gz, .tgz, .gz");let l=0;const f={entries:0,totalBytes:0};return u==="zip"?l=await zt(n,i,f):u==="tar"||u==="tgz"?l=await Dt(n,i,u==="tgz",f):l=await Lt(n,i,f),{ok:!0,workspacePath:h(r),archivePath:h(n),targetPath:h(i),extractedEntries:l}}function tt(t){const e=t.match(/^(.*?)\{([^}]+)\}(.*)$/);if(!e)return[t];const[,r,i,a]=e;return i.split(",").flatMap(n=>tt(r+n.trim()+a))}function et(t,e){const r=String(e||"").trim();if(!r||r==="*"||r==="*.*")return!0;const i=t.toLowerCase();return tt(r.toLowerCase()).some(n=>{if(n==="*")return!0;const s=n.replace(/\./g,"\\.").replace(/\*\*/g,"<<GLOBSTAR>>").replace(/\*/g,"[^/]*").replace(/\?/g,"[^/]").replace(/<<GLOBSTAR>>/g,".*");return new RegExp(`^${s}$`).test(i)})}function rt(t){return t===".git"||t==="node_modules"||t===".svn"||t===".hg"}const it=10*1024*1024,$t=1e3;async function Qt(t){const{workspaceRoot:e}=await k(t.workspacePath),r=String(t.keyword||"").trim();if(!r)throw new Error("keyword is required");const i=!!t.ignoreCase,a=String(t.fileMask||"").trim(),n=typeof t.maxResults=="number"&&t.maxResults>0?t.maxResults:$t,s=i?r.toLowerCase():r,u=(t.mode==="filename"?"filename":"content")==="filename",l=[];let f=0,w=!1;async function m(E){if(w)return;let F;try{F=await o.readdir(E,{withFileTypes:!0})}catch{return}for(const g of F){if(w)return;const T=p(e,d.join(E,g.name));if(g.isSymbolicLink())continue;if(g.isDirectory()){if(rt(g.name))continue;await m(T);continue}if(!g.isFile()||a&&!et(g.name,a))continue;f+=1;let N;try{N=await o.stat(T)}catch{continue}if(N.size>it)continue;if(u){const P=(i?g.name.toLowerCase():g.name).indexOf(s);if(P!==-1&&(l.push({filePath:h(T),lineNumber:0,lineContent:g.name,matchStart:P,matchEnd:P+r.length}),l.length>=n)){w=!0;return}continue}let M;try{M=await o.readFile(T,"utf-8")}catch{continue}const b=M.split(/\r?\n/);for(let R=0;R<b.length;R+=1){const P=b[R],nt=i?P.toLowerCase():P;let O=0;for(;;){const L=nt.indexOf(s,O);if(L===-1)break;if(l.push({filePath:h(T),lineNumber:R+1,lineContent:P,matchStart:L,matchEnd:L+r.length}),l.length>=n){w=!0;return}O=L+1}}}}return await m(e),{workspacePath:h(e),keyword:r,totalFiles:f,totalMatches:l.length,matches:l,truncated:w}}async function at(t,e,r,i,a){let n;try{n=await o.readFile(e,"utf-8")}catch{return{changed:!1,count:0}}const s=n;let c=0;if(a){const u=r.toLowerCase();let l=0;for(;;){const w=n.toLowerCase().indexOf(u,l);if(w===-1)break;n=n.slice(0,w)+i+n.slice(w+r.length),c+=1;const m=i.length||1;l=w+m}}else{let u=0;for(;;){const l=n.indexOf(r,u);if(l===-1)break;n=n.slice(0,l)+i+n.slice(l+r.length),c+=1;const f=i.length||1;u=l+f}}return n===s?{changed:!1,count:0}:(await v(t,e),await o.writeFile(e,n,"utf-8"),{changed:!0,count:c})}async function te(t){const{workspaceRoot:e}=await k(t.workspacePath),r=String(t.keyword||"").trim();if(!r)throw new Error("keyword is required");const i=String(t.replacement??""),a=!!t.ignoreCase,n=String(t.fileMask||"").trim(),s=String(t.filePath||"").trim();let c=0,u=0;if(s){const{resolvedFilePath:f}=await W({workspacePath:t.workspacePath,filePath:s}),w=await at(e,f,r,i,a);return w.changed&&(c=1,u=w.count),{workspacePath:h(e),keyword:r,replacement:i,changedFiles:c,changedMatches:u}}async function l(f){let w;try{w=await o.readdir(f,{withFileTypes:!0})}catch{return}for(const m of w){const E=p(e,d.join(f,m.name));if(m.isSymbolicLink())continue;if(m.isDirectory()){if(rt(m.name))continue;await l(E);continue}if(!m.isFile()||n&&!et(m.name,n))continue;try{if((await o.stat(E)).size>it)continue}catch{continue}const F=await at(e,E,r,i,a);F.changed&&(c+=1,u+=F.count)}}return await l(e),{workspacePath:h(e),keyword:r,replacement:i,changedFiles:c,changedMatches:u}}export{qt as chmodWorkspaceEntry,Zt as createWorkspaceEntry,Vt as deleteWorkspaceEntry,Wt as extractWorkspaceArchive,jt as getWorkspaceEntryProperties,Ot as listWorkspaceDirectory,Kt as moveWorkspaceEntry,_t as previewWorkspaceDocument,Ut as readWorkspaceFile,Ht as renameWorkspaceEntry,te as replaceWorkspaceFiles,Yt as resolveWorkspaceDownloadTarget,Qt as searchWorkspaceFiles,Jt as streamWorkspaceDirectoryZip,Xt as uploadWorkspaceFiles,Gt as writeWorkspaceFile};
|
|
2
|
+
|