windows-development-sandbox 0.0.0-stage → 1.1.0
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/.sandbox-config.example.json +14 -0
- package/LICENSE +21 -0
- package/README.md +94 -2
- package/config.example.json +349 -0
- package/dist/api.d.ts +31 -0
- package/dist/api.js +31 -0
- package/dist/bin/agent-sandbox.d.ts +2 -0
- package/dist/bin/agent-sandbox.js +3 -0
- package/dist/bin/claude-host.d.ts +2 -0
- package/dist/bin/claude-host.js +5 -0
- package/dist/bin/claude-sbx.d.ts +2 -0
- package/dist/bin/claude-sbx.js +3 -0
- package/dist/bin/codex-sbx.d.ts +2 -0
- package/dist/bin/codex-sbx.js +3 -0
- package/dist/bin/gemini-sbx.d.ts +2 -0
- package/dist/bin/gemini-sbx.js +3 -0
- package/dist/bin/sandbox.d.ts +2 -0
- package/dist/bin/sandbox.js +4 -0
- package/dist/bin/sbx-hide.d.ts +2 -0
- package/dist/bin/sbx-hide.js +3 -0
- package/dist/bin/sbx-show.d.ts +2 -0
- package/dist/bin/sbx-show.js +3 -0
- package/dist/cli/args.d.ts +52 -0
- package/dist/cli/args.js +143 -0
- package/dist/cli/colors.d.ts +24 -0
- package/dist/cli/colors.js +42 -0
- package/dist/cli/commands/accounts.d.ts +2 -0
- package/dist/cli/commands/accounts.js +52 -0
- package/dist/cli/commands/agent-commands.d.ts +6 -0
- package/dist/cli/commands/agent-commands.js +61 -0
- package/dist/cli/commands/context.d.ts +23 -0
- package/dist/cli/commands/context.js +57 -0
- package/dist/cli/commands/data/clean.d.ts +15 -0
- package/dist/cli/commands/data/clean.js +101 -0
- package/dist/cli/commands/data/collect.d.ts +11 -0
- package/dist/cli/commands/data/collect.js +54 -0
- package/dist/cli/commands/data/index.d.ts +4 -0
- package/dist/cli/commands/data/index.js +45 -0
- package/dist/cli/commands/data/remove.d.ts +2 -0
- package/dist/cli/commands/data/remove.js +30 -0
- package/dist/cli/commands/index.d.ts +10 -0
- package/dist/cli/commands/index.js +10 -0
- package/dist/cli/commands/launch.d.ts +29 -0
- package/dist/cli/commands/launch.js +333 -0
- package/dist/cli/commands/maintenance.d.ts +8 -0
- package/dist/cli/commands/maintenance.js +154 -0
- package/dist/cli/commands/project.d.ts +29 -0
- package/dist/cli/commands/project.js +63 -0
- package/dist/cli/commands/projects.d.ts +14 -0
- package/dist/cli/commands/projects.js +146 -0
- package/dist/cli/commands/repair/checks.d.ts +2 -0
- package/dist/cli/commands/repair/checks.js +170 -0
- package/dist/cli/commands/repair/finders.d.ts +30 -0
- package/dist/cli/commands/repair/finders.js +108 -0
- package/dist/cli/commands/repair/index.d.ts +12 -0
- package/dist/cli/commands/repair/index.js +61 -0
- package/dist/cli/commands/repair/types.d.ts +26 -0
- package/dist/cli/commands/repair/types.js +3 -0
- package/dist/cli/commands/restart.d.ts +6 -0
- package/dist/cli/commands/restart.js +85 -0
- package/dist/cli/commands/sessions.d.ts +12 -0
- package/dist/cli/commands/sessions.js +257 -0
- package/dist/cli/commands/setup/checks.d.ts +13 -0
- package/dist/cli/commands/setup/checks.js +71 -0
- package/dist/cli/commands/setup/facts.d.ts +31 -0
- package/dist/cli/commands/setup/facts.js +60 -0
- package/dist/cli/commands/setup/index.d.ts +2 -0
- package/dist/cli/commands/setup/index.js +43 -0
- package/dist/cli/commands/user-config.d.ts +8 -0
- package/dist/cli/commands/user-config.js +41 -0
- package/dist/cli/history.d.ts +11 -0
- package/dist/cli/history.js +21 -0
- package/dist/cli/index.d.ts +20 -0
- package/dist/cli/index.js +121 -0
- package/dist/cli/manifest.d.ts +103 -0
- package/dist/cli/manifest.js +107 -0
- package/dist/cli/menu-multi.d.ts +12 -0
- package/dist/cli/menu-multi.js +82 -0
- package/dist/cli/menu.d.ts +14 -0
- package/dist/cli/menu.js +290 -0
- package/dist/cli/progress.d.ts +9 -0
- package/dist/cli/progress.js +49 -0
- package/dist/cli/settings/edit.d.ts +13 -0
- package/dist/cli/settings/edit.js +19 -0
- package/dist/cli/settings/index.d.ts +8 -0
- package/dist/cli/settings/index.js +131 -0
- package/dist/cli/settings/prompts.d.ts +7 -0
- package/dist/cli/settings/prompts.js +48 -0
- package/dist/cli/settings/schema.d.ts +33 -0
- package/dist/cli/settings/schema.js +29 -0
- package/dist/cli/validate.d.ts +17 -0
- package/dist/cli/validate.js +89 -0
- package/dist/core/config.d.ts +17 -0
- package/dist/core/config.js +106 -0
- package/dist/core/detect-databases.d.ts +15 -0
- package/dist/core/detect-databases.js +147 -0
- package/dist/core/detect-pwsh.d.ts +18 -0
- package/dist/core/detect-pwsh.js +111 -0
- package/dist/core/detect-toolchains.d.ts +30 -0
- package/dist/core/detect-toolchains.js +58 -0
- package/dist/core/disk.d.ts +16 -0
- package/dist/core/disk.js +55 -0
- package/dist/core/local-config.d.ts +20 -0
- package/dist/core/local-config.js +77 -0
- package/dist/core/project-policy.d.ts +15 -0
- package/dist/core/project-policy.js +35 -0
- package/dist/core/prompt.d.ts +4 -0
- package/dist/core/prompt.js +15 -0
- package/dist/core/safe-paths.d.ts +4 -0
- package/dist/core/safe-paths.js +37 -0
- package/dist/core/schema/common.d.ts +17 -0
- package/dist/core/schema/common.js +15 -0
- package/dist/core/schema/config.d.ts +594 -0
- package/dist/core/schema/config.js +63 -0
- package/dist/core/schema/databases.d.ts +33 -0
- package/dist/core/schema/databases.js +35 -0
- package/dist/core/schema/harness.d.ts +12 -0
- package/dist/core/schema/harness.js +17 -0
- package/dist/core/schema/json-schema.d.ts +5 -0
- package/dist/core/schema/json-schema.js +25 -0
- package/dist/core/schema/profile.d.ts +27 -0
- package/dist/core/schema/profile.js +29 -0
- package/dist/core/schema/projects.d.ts +9 -0
- package/dist/core/schema/projects.js +11 -0
- package/dist/core/schema/sandbox.d.ts +50 -0
- package/dist/core/schema/sandbox.js +36 -0
- package/dist/core/schema/toolchains.d.ts +93 -0
- package/dist/core/schema/toolchains.js +83 -0
- package/dist/core/schema.d.ts +6 -0
- package/dist/core/schema.js +8 -0
- package/dist/core/types.d.ts +190 -0
- package/dist/core/types.js +1 -0
- package/dist/core/util.d.ts +60 -0
- package/dist/core/util.js +154 -0
- package/dist/core/workspace.d.ts +18 -0
- package/dist/core/workspace.js +128 -0
- package/dist/guest/boot-env.d.ts +4 -0
- package/dist/guest/boot-env.js +25 -0
- package/dist/guest/cmdline.d.ts +12 -0
- package/dist/guest/cmdline.js +23 -0
- package/dist/guest/daemon/exec.d.ts +5 -0
- package/dist/guest/daemon/exec.js +32 -0
- package/dist/guest/daemon/project.d.ts +14 -0
- package/dist/guest/daemon/project.js +132 -0
- package/dist/guest/daemon/session.d.ts +13 -0
- package/dist/guest/daemon/session.js +139 -0
- package/dist/guest/daemon/shutdown.d.ts +1 -0
- package/dist/guest/daemon/shutdown.js +21 -0
- package/dist/guest/daemon/state.d.ts +17 -0
- package/dist/guest/daemon/state.js +31 -0
- package/dist/guest/daemon.d.ts +1 -0
- package/dist/guest/daemon.js +53 -0
- package/dist/guest/db.d.ts +1 -0
- package/dist/guest/db.js +75 -0
- package/dist/guest/env.d.ts +51 -0
- package/dist/guest/env.js +101 -0
- package/dist/guest/gitguard.d.ts +7 -0
- package/dist/guest/gitguard.js +60 -0
- package/dist/guest/gitrules.d.ts +2 -0
- package/dist/guest/gitrules.js +11 -0
- package/dist/guest/guide.d.ts +16 -0
- package/dist/guest/guide.js +69 -0
- package/dist/guest/helpers.d.ts +3 -0
- package/dist/guest/helpers.js +26 -0
- package/dist/guest/main.d.ts +1 -0
- package/dist/guest/main.js +130 -0
- package/dist/guest/ports.d.ts +6 -0
- package/dist/guest/ports.js +22 -0
- package/dist/guest/project-context.d.ts +7 -0
- package/dist/guest/project-context.js +10 -0
- package/dist/guest/project-env.d.ts +20 -0
- package/dist/guest/project-env.js +20 -0
- package/dist/guest/project-status.d.ts +5 -0
- package/dist/guest/project-status.js +17 -0
- package/dist/guest/ptyhost.d.ts +22 -0
- package/dist/guest/ptyhost.js +36 -0
- package/dist/guest/setup/agents.d.ts +11 -0
- package/dist/guest/setup/agents.js +87 -0
- package/dist/guest/setup/claude.d.ts +21 -0
- package/dist/guest/setup/claude.js +146 -0
- package/dist/guest/setup/environment.d.ts +16 -0
- package/dist/guest/setup/environment.js +69 -0
- package/dist/guest/setup/hooks.d.ts +9 -0
- package/dist/guest/setup/hooks.js +50 -0
- package/dist/guest/setup/network.d.ts +10 -0
- package/dist/guest/setup/network.js +99 -0
- package/dist/guest/setup/policy.d.ts +3 -0
- package/dist/guest/setup/policy.js +18 -0
- package/dist/guest/setup/user.d.ts +7 -0
- package/dist/guest/setup/user.js +47 -0
- package/dist/guest/toolchains/catalogue.d.ts +55 -0
- package/dist/guest/toolchains/catalogue.js +31 -0
- package/dist/guest/toolchains/cli.d.ts +10 -0
- package/dist/guest/toolchains/cli.js +72 -0
- package/dist/guest/toolchains/common.d.ts +54 -0
- package/dist/guest/toolchains/common.js +147 -0
- package/dist/guest/toolchains/databases/dynamodb.d.ts +8 -0
- package/dist/guest/toolchains/databases/dynamodb.js +52 -0
- package/dist/guest/toolchains/databases/index.d.ts +6 -0
- package/dist/guest/toolchains/databases/index.js +62 -0
- package/dist/guest/toolchains/databases/mongodb.d.ts +3 -0
- package/dist/guest/toolchains/databases/mongodb.js +31 -0
- package/dist/guest/toolchains/databases/mysql.d.ts +3 -0
- package/dist/guest/toolchains/databases/mysql.js +38 -0
- package/dist/guest/toolchains/databases/postgres.d.ts +5 -0
- package/dist/guest/toolchains/databases/postgres.js +38 -0
- package/dist/guest/toolchains/databases/redis.d.ts +3 -0
- package/dist/guest/toolchains/databases/redis.js +16 -0
- package/dist/guest/toolchains/databases/server.d.ts +14 -0
- package/dist/guest/toolchains/databases/server.js +52 -0
- package/dist/guest/toolchains/git.d.ts +3 -0
- package/dist/guest/toolchains/git.js +121 -0
- package/dist/guest/toolchains/go.d.ts +3 -0
- package/dist/guest/toolchains/go.js +33 -0
- package/dist/guest/toolchains/index.d.ts +17 -0
- package/dist/guest/toolchains/index.js +80 -0
- package/dist/guest/toolchains/java.d.ts +8 -0
- package/dist/guest/toolchains/java.js +96 -0
- package/dist/guest/toolchains/leftovers.d.ts +2 -0
- package/dist/guest/toolchains/leftovers.js +26 -0
- package/dist/guest/toolchains/managers.d.ts +16 -0
- package/dist/guest/toolchains/managers.js +84 -0
- package/dist/guest/toolchains/native/binstall.d.ts +7 -0
- package/dist/guest/toolchains/native/binstall.js +52 -0
- package/dist/guest/toolchains/native/cpp.d.ts +4 -0
- package/dist/guest/toolchains/native/cpp.js +38 -0
- package/dist/guest/toolchains/native/dotnet.d.ts +4 -0
- package/dist/guest/toolchains/native/dotnet.js +103 -0
- package/dist/guest/toolchains/native/mesa.d.ts +7 -0
- package/dist/guest/toolchains/native/mesa.js +46 -0
- package/dist/guest/toolchains/native/msvc/environment.d.ts +16 -0
- package/dist/guest/toolchains/native/msvc/environment.js +57 -0
- package/dist/guest/toolchains/native/msvc/index.d.ts +6 -0
- package/dist/guest/toolchains/native/msvc/index.js +48 -0
- package/dist/guest/toolchains/native/msvc/packages.d.ts +6 -0
- package/dist/guest/toolchains/native/msvc/packages.js +156 -0
- package/dist/guest/toolchains/native/msvc/tree.d.ts +15 -0
- package/dist/guest/toolchains/native/msvc/tree.js +21 -0
- package/dist/guest/toolchains/native/rust.d.ts +3 -0
- package/dist/guest/toolchains/native/rust.js +45 -0
- package/dist/guest/toolchains/node.d.ts +3 -0
- package/dist/guest/toolchains/node.js +152 -0
- package/dist/guest/toolchains/plan.d.ts +22 -0
- package/dist/guest/toolchains/plan.js +96 -0
- package/dist/guest/toolchains/pwsh.d.ts +4 -0
- package/dist/guest/toolchains/pwsh.js +14 -0
- package/dist/guest/toolchains/python.d.ts +3 -0
- package/dist/guest/toolchains/python.js +54 -0
- package/dist/guest/toolchains/runtimes.d.ts +19 -0
- package/dist/guest/toolchains/runtimes.js +38 -0
- package/dist/guest/toolchains/scripting/php.d.ts +3 -0
- package/dist/guest/toolchains/scripting/php.js +71 -0
- package/dist/guest/toolchains/scripting/ruby.d.ts +3 -0
- package/dist/guest/toolchains/scripting/ruby.js +47 -0
- package/dist/guest/toolchains/scripting/vcruntime.d.ts +7 -0
- package/dist/guest/toolchains/scripting/vcruntime.js +45 -0
- package/dist/guest/toolchains/versions.d.ts +47 -0
- package/dist/guest/toolchains/versions.js +77 -0
- package/dist/guest/toolchains/vscode.d.ts +2 -0
- package/dist/guest/toolchains/vscode.js +53 -0
- package/dist/guest/view.d.ts +1 -0
- package/dist/guest/view.js +42 -0
- package/dist/host/accounts.d.ts +32 -0
- package/dist/host/accounts.js +128 -0
- package/dist/host/agent-commands.d.ts +25 -0
- package/dist/host/agent-commands.js +145 -0
- package/dist/host/attach.d.ts +17 -0
- package/dist/host/attach.js +69 -0
- package/dist/host/channel.d.ts +38 -0
- package/dist/host/channel.js +162 -0
- package/dist/host/claude-profile.d.ts +15 -0
- package/dist/host/claude-profile.js +107 -0
- package/dist/host/cleanup.d.ts +23 -0
- package/dist/host/cleanup.js +57 -0
- package/dist/host/database-plan.d.ts +12 -0
- package/dist/host/database-plan.js +52 -0
- package/dist/host/generate.d.ts +7 -0
- package/dist/host/generate.js +98 -0
- package/dist/host/join.d.ts +28 -0
- package/dist/host/join.js +76 -0
- package/dist/host/launch/mappings.d.ts +3 -0
- package/dist/host/launch/mappings.js +50 -0
- package/dist/host/launch/projects.d.ts +15 -0
- package/dist/host/launch/projects.js +56 -0
- package/dist/host/launch/stage.d.ts +11 -0
- package/dist/host/launch/stage.js +66 -0
- package/dist/host/launch/wsb.d.ts +10 -0
- package/dist/host/launch/wsb.js +34 -0
- package/dist/host/pc-scan.d.ts +6 -0
- package/dist/host/pc-scan.js +37 -0
- package/dist/host/personalize.d.ts +10 -0
- package/dist/host/personalize.js +97 -0
- package/dist/host/platform.d.ts +19 -0
- package/dist/host/platform.js +126 -0
- package/dist/host/project-scan.d.ts +7 -0
- package/dist/host/project-scan.js +89 -0
- package/dist/host/projects.d.ts +100 -0
- package/dist/host/projects.js +147 -0
- package/dist/host/repo-guard.d.ts +36 -0
- package/dist/host/repo-guard.js +219 -0
- package/dist/host/secrets.d.ts +14 -0
- package/dist/host/secrets.js +133 -0
- package/dist/host/terminal.d.ts +32 -0
- package/dist/host/terminal.js +130 -0
- package/dist/host/test-guard.d.ts +6 -0
- package/dist/host/test-guard.js +15 -0
- package/dist/host/window.d.ts +4 -0
- package/dist/host/window.js +72 -0
- package/dist/host/wsb.d.ts +37 -0
- package/dist/host/wsb.js +101 -0
- package/dist/runtime/daemon.js +28494 -0
- package/dist/runtime/db.js +26387 -0
- package/dist/runtime/gitguard.js +84 -0
- package/dist/runtime/main.js +28622 -0
- package/dist/runtime/view.js +25841 -0
- package/dist/selftest/accounts.test.d.ts +2 -0
- package/dist/selftest/accounts.test.js +44 -0
- package/dist/selftest/agent-commands.test.d.ts +2 -0
- package/dist/selftest/agent-commands.test.js +47 -0
- package/dist/selftest/channel.test.d.ts +2 -0
- package/dist/selftest/channel.test.js +37 -0
- package/dist/selftest/cli.test.d.ts +2 -0
- package/dist/selftest/cli.test.js +118 -0
- package/dist/selftest/cmdline.test.d.ts +2 -0
- package/dist/selftest/cmdline.test.js +45 -0
- package/dist/selftest/config.test.d.ts +2 -0
- package/dist/selftest/config.test.js +38 -0
- package/dist/selftest/data.test.d.ts +2 -0
- package/dist/selftest/data.test.js +80 -0
- package/dist/selftest/detect.test.d.ts +2 -0
- package/dist/selftest/detect.test.js +58 -0
- package/dist/selftest/fixtures.d.ts +272 -0
- package/dist/selftest/fixtures.js +80 -0
- package/dist/selftest/generate.test.d.ts +2 -0
- package/dist/selftest/generate.test.js +63 -0
- package/dist/selftest/git-access.test.d.ts +2 -0
- package/dist/selftest/git-access.test.js +26 -0
- package/dist/selftest/guest-boot.test.d.ts +2 -0
- package/dist/selftest/guest-boot.test.js +76 -0
- package/dist/selftest/harness.d.ts +51 -0
- package/dist/selftest/harness.js +77 -0
- package/dist/selftest/host-sim.test.d.ts +2 -0
- package/dist/selftest/host-sim.test.js +93 -0
- package/dist/selftest/hostile-input.test.d.ts +2 -0
- package/dist/selftest/hostile-input.test.js +54 -0
- package/dist/selftest/index.d.ts +8 -0
- package/dist/selftest/index.js +155 -0
- package/dist/selftest/links.test.d.ts +2 -0
- package/dist/selftest/links.test.js +50 -0
- package/dist/selftest/live-daemon.test.d.ts +2 -0
- package/dist/selftest/live-daemon.test.js +91 -0
- package/dist/selftest/live-git.test.d.ts +2 -0
- package/dist/selftest/live-git.test.js +60 -0
- package/dist/selftest/live-projects.test.d.ts +2 -0
- package/dist/selftest/live-projects.test.js +127 -0
- package/dist/selftest/live.d.ts +65 -0
- package/dist/selftest/live.js +109 -0
- package/dist/selftest/local-config.test.d.ts +2 -0
- package/dist/selftest/local-config.test.js +107 -0
- package/dist/selftest/mirror.test.d.ts +2 -0
- package/dist/selftest/mirror.test.js +101 -0
- package/dist/selftest/network.test.d.ts +2 -0
- package/dist/selftest/network.test.js +28 -0
- package/dist/selftest/personalization.test.d.ts +2 -0
- package/dist/selftest/personalization.test.js +53 -0
- package/dist/selftest/projects.test.d.ts +2 -0
- package/dist/selftest/projects.test.js +254 -0
- package/dist/selftest/repair.test.d.ts +2 -0
- package/dist/selftest/repair.test.js +42 -0
- package/dist/selftest/repo-guard.test.d.ts +2 -0
- package/dist/selftest/repo-guard.test.js +63 -0
- package/dist/selftest/secrets.test.d.ts +2 -0
- package/dist/selftest/secrets.test.js +71 -0
- package/dist/selftest/setup.test.d.ts +2 -0
- package/dist/selftest/setup.test.js +46 -0
- package/dist/selftest/suite.d.ts +13 -0
- package/dist/selftest/suite.js +1 -0
- package/dist/selftest/sync.test.d.ts +2 -0
- package/dist/selftest/sync.test.js +94 -0
- package/dist/selftest/test-guard.test.d.ts +2 -0
- package/dist/selftest/test-guard.test.js +49 -0
- package/dist/selftest/toolchains.test.d.ts +2 -0
- package/dist/selftest/toolchains.test.js +103 -0
- package/dist/selftest/workspace.test.d.ts +2 -0
- package/dist/selftest/workspace.test.js +53 -0
- package/dist/shared/download.d.ts +60 -0
- package/dist/shared/download.js +215 -0
- package/dist/shared/guest-paths.d.ts +30 -0
- package/dist/shared/guest-paths.js +61 -0
- package/dist/shared/netrules.d.ts +18 -0
- package/dist/shared/netrules.js +98 -0
- package/dist/shared/paths.d.ts +14 -0
- package/dist/shared/paths.js +28 -0
- package/dist/shared/projects.d.ts +53 -0
- package/dist/shared/projects.js +67 -0
- package/dist/shared/pty-client.d.ts +86 -0
- package/dist/shared/pty-client.js +227 -0
- package/dist/shared/rundir.d.ts +39 -0
- package/dist/shared/rundir.js +94 -0
- package/dist/shared/safe-remove.d.ts +10 -0
- package/dist/shared/safe-remove.js +66 -0
- package/dist/shared/schema.d.ts +527 -0
- package/dist/shared/schema.js +90 -0
- package/dist/shared/system.d.ts +15 -0
- package/dist/shared/system.js +19 -0
- package/dist/shared/test-guard.d.ts +4 -0
- package/dist/shared/test-guard.js +37 -0
- package/dist/sync/claude-mem.d.ts +8 -0
- package/dist/sync/claude-mem.js +79 -0
- package/dist/sync/history.d.ts +41 -0
- package/dist/sync/history.js +161 -0
- package/dist/sync/watcher.d.ts +21 -0
- package/dist/sync/watcher.js +135 -0
- package/hooks/post-agent/README.txt +1 -0
- package/hooks/pre-provision/README.txt +1 -0
- package/package.json +91 -4
- package/sandbox/bootstrap.ps1 +258 -0
- package/sandbox/cpucap.ps1 +51 -0
- package/sandbox/gitshim.cs +35 -0
- package/sandbox/logon.cmd +18 -0
- package/sandbox/openssl.cs +42 -0
- package/sandbox/ptyhost.cs +614 -0
- package/sandbox/trust.cmd +35 -0
- package/sandbox/vswhere.cs +83 -0
- package/schemas/config.schema.json +1161 -0
- package/schemas/sandbox-config.schema.json +968 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// PostgreSQL from EnterpriseDB's Windows binaries (without pgAdmin).
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fetchJson, installArchive } from '../../../shared/download.js';
|
|
4
|
+
import { execAsync, exists, log, logsDir } from '../../env.js';
|
|
5
|
+
import { resolveCached, tool } from '../common.js';
|
|
6
|
+
import { portOpen } from '../../ports.js';
|
|
7
|
+
import { clearStaleLocks, createOnce, dataFor } from './server.js';
|
|
8
|
+
/** The data folder of a PostgreSQL major version (one cluster per major). */
|
|
9
|
+
export const postgresData = (version) => dataFor('postgres-' + version.split('.')[0]);
|
|
10
|
+
export const setupPostgres = async (env, plan) => {
|
|
11
|
+
const exact = await resolveCached('postgres:' + plan.version, async () => {
|
|
12
|
+
const versions = (await fetchJson('https://www.postgresql.org/versions.json')).filter((v) => v.supported);
|
|
13
|
+
const hit = plan.version === 'latest' ? versions.find((v) => v.current) ?? versions[versions.length - 1] : versions.find((v) => v.major === plan.version.split('.')[0]);
|
|
14
|
+
return hit ? hit.major + '.' + hit.latestMinor : Promise.reject(new Error('No supported PostgreSQL ' + plan.version));
|
|
15
|
+
});
|
|
16
|
+
const home = tool('postgres', exact);
|
|
17
|
+
const bin = path.join(home, 'bin');
|
|
18
|
+
if (!exists(path.join(bin, 'pg_ctl.exe'))) {
|
|
19
|
+
log('Downloading PostgreSQL ' + exact + ' (~330 MB once) ...');
|
|
20
|
+
// EDB publishes no checksums; the download is HTTPS from EnterpriseDB, PostgreSQL's official Windows packager.
|
|
21
|
+
await installArchive('https://get.enterprisedb.com/postgresql/postgresql-' + exact + '-1-windows-x64-binaries.zip', home, {
|
|
22
|
+
locate: 'pg_ctl.exe', up: 1, fileName: 'postgresql-' + exact + '.zip', members: ['pgsql/bin', 'pgsql/lib', 'pgsql/share', 'pgsql/include'],
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const data = postgresData(exact);
|
|
26
|
+
await createOnce(data, 'PG_VERSION', async () => {
|
|
27
|
+
log('Creating the PostgreSQL database (first start) ...');
|
|
28
|
+
await execAsync(path.join(bin, 'initdb.exe'), ['-D', data, '-U', 'postgres', '-A', 'trust', '-E', 'UTF8', '--locale=C'], { stdio: 'ignore' });
|
|
29
|
+
});
|
|
30
|
+
if (await portOpen(plan.port))
|
|
31
|
+
throw new Error('port ' + plan.port + ' is already in use (change databases.postgres.port)');
|
|
32
|
+
await clearStaleLocks(data, ['postmaster.pid'], plan.port);
|
|
33
|
+
// postgres.exe refuses to run as an administrator (the sandbox user is one); pg_ctl drops the rights first.
|
|
34
|
+
await execAsync(path.join(bin, 'pg_ctl.exe'), ['start', '-D', data, '-w', '-t', '60', '-l', path.join(logsDir, 'postgres.log'), '-o', '-p ' + plan.port + ' -c listen_addresses=localhost'], { stdio: 'ignore' });
|
|
35
|
+
Object.assign(env.vars, { PGHOST: 'localhost', PGPORT: String(plan.port), PGUSER: 'postgres' });
|
|
36
|
+
env.prepend.push(bin);
|
|
37
|
+
env.summary.push('postgres ' + exact + ': localhost:' + plan.port + ', user postgres, no password (postgres://postgres@localhost:' + plan.port + '/postgres)');
|
|
38
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Redis (the maintained community Windows build of upstream Redis).
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { exists, mkdirp } from '../../env.js';
|
|
4
|
+
import { installFromGithub, tool } from '../common.js';
|
|
5
|
+
import { dataFor, startServer } from './server.js';
|
|
6
|
+
export const setupRedis = async (env, plan) => {
|
|
7
|
+
const home = tool('redis');
|
|
8
|
+
if (!exists(path.join(home, 'redis-server.exe'))) {
|
|
9
|
+
// Redis has no official Windows build; this one is built from upstream Redis with MSYS2.
|
|
10
|
+
await installFromGithub('redis-windows/redis-windows', /^Redis-[\d.]+-Windows-x64-msys2\.zip$/, { dest: home, what: (a) => 'Redis (' + a.name + ', ~10 MB once)', locate: 'redis-server.exe' });
|
|
11
|
+
}
|
|
12
|
+
const data = mkdirp(dataFor('redis'));
|
|
13
|
+
await startServer('redis', path.join(home, 'redis-server.exe'), ['--port', String(plan.port), '--bind', '127.0.0.1', '--dir', data.replace(/\\/g, '/'), '--appendonly', 'yes'], plan.port);
|
|
14
|
+
env.prepend.push(home);
|
|
15
|
+
env.summary.push('redis: redis://127.0.0.1:' + plan.port + ' (redis-cli on PATH)');
|
|
16
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** C:\AgentData\databases\<name>: the data survives the sandbox. */
|
|
2
|
+
export declare const dataFor: (name: string) => string;
|
|
3
|
+
/** Starts a server detached (it outlives the boot script) with its output in logs\<name>.log, and waits for its port. */
|
|
4
|
+
export declare const startServer: (name: string, exe: string, args: string[], port: number) => Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Lock files a sandbox that was closed without shutting the server down leaves behind. Only when nothing listens on
|
|
7
|
+
* the port: a server already running in this VM keeps its lock.
|
|
8
|
+
*/
|
|
9
|
+
export declare const clearStaleLocks: (dir: string, files: string[], port: number) => Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Creates a database folder with `create` (initdb, install-db) unless `marker` is there. A failed or interrupted run
|
|
12
|
+
* removes the folder again when this call made it, so the half-made one is not taken for a finished database next boot.
|
|
13
|
+
*/
|
|
14
|
+
export declare const createOnce: (data: string, marker: string, create: () => Promise<void>) => Promise<void>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// What every database module shares: where data lives, starting a server detached, and safe first-time creation.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { safeRemove } from '../../../shared/safe-remove.js';
|
|
6
|
+
import { dataDir, exists, logsDir, mkdirp } from '../../env.js';
|
|
7
|
+
import { portOpen, waitForPort } from '../../ports.js';
|
|
8
|
+
/** C:\AgentData\databases\<name>: the data survives the sandbox. */
|
|
9
|
+
export const dataFor = (name) => path.join(dataDir, 'databases', name);
|
|
10
|
+
/** Starts a server detached (it outlives the boot script) with its output in logs\<name>.log, and waits for its port. */
|
|
11
|
+
export const startServer = async (name, exe, args, port) => {
|
|
12
|
+
if (await portOpen(port))
|
|
13
|
+
throw new Error('port ' + port + ' is already in use (change databases.' + name + '.port)');
|
|
14
|
+
const logFile = path.join(logsDir, name + '.log');
|
|
15
|
+
const out = fs.openSync(logFile, 'a');
|
|
16
|
+
spawn(exe, args, { detached: true, stdio: ['ignore', out, out], windowsHide: true }).unref();
|
|
17
|
+
fs.closeSync(out);
|
|
18
|
+
if (!(await waitForPort(port, 60)))
|
|
19
|
+
throw new Error(name + ' did not start listening on port ' + port + '; see ' + logFile);
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Lock files a sandbox that was closed without shutting the server down leaves behind. Only when nothing listens on
|
|
23
|
+
* the port: a server already running in this VM keeps its lock.
|
|
24
|
+
*/
|
|
25
|
+
export const clearStaleLocks = async (dir, files, port) => {
|
|
26
|
+
if (await portOpen(port))
|
|
27
|
+
return;
|
|
28
|
+
for (const f of files)
|
|
29
|
+
fs.rmSync(path.join(dir, f), { force: true });
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Creates a database folder with `create` (initdb, install-db) unless `marker` is there. A failed or interrupted run
|
|
33
|
+
* removes the folder again when this call made it, so the half-made one is not taken for a finished database next boot.
|
|
34
|
+
*/
|
|
35
|
+
export const createOnce = async (data, marker, create) => {
|
|
36
|
+
if (exists(path.join(data, marker)))
|
|
37
|
+
return;
|
|
38
|
+
const existed = exists(data);
|
|
39
|
+
mkdirp(path.dirname(data));
|
|
40
|
+
try {
|
|
41
|
+
await create();
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
if (!existed) {
|
|
45
|
+
try {
|
|
46
|
+
safeRemove(data, dataDir);
|
|
47
|
+
}
|
|
48
|
+
catch { /* the original error is the one to report */ }
|
|
49
|
+
}
|
|
50
|
+
throw e;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Full Git for Windows (with bash: Claude Code on Windows needs Git Bash). Copied from the host when installed there.
|
|
2
|
+
// provisioning.git.access decides what the agent gets: 'full' git, a 'readonly' guarded git, or 'none' (bash only).
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { download, githubAsset, moveIntoPlace, stagingFolder } from '../../shared/download.js';
|
|
6
|
+
import { safeRemove } from '../../shared/safe-remove.js';
|
|
7
|
+
import { dataDir, dryRun, errMsg, execAsync, execOutput, exists, hostToolsDir, launchDir, log, mkdirp, robocopy, toolsDir, warn } from '../env.js';
|
|
8
|
+
import { ensureCompiled } from '../helpers.js';
|
|
9
|
+
import { arch, tool } from './common.js';
|
|
10
|
+
import { duringSession } from '../project-context.js';
|
|
11
|
+
const BLOCKED_HOOKS = ['pre-commit', 'commit-msg', 'pre-merge-commit', 'pre-push', 'pre-rebase', 'pre-applypatch', 'applypatch-msg'];
|
|
12
|
+
const posix = (p) => p.replace(/\\/g, '/');
|
|
13
|
+
const installPortableGit = async (root) => {
|
|
14
|
+
const asset = await githubAsset('git-for-windows/git', arch === 'arm64' ? /^PortableGit-[\d.]+-arm64\.7z\.exe$/ : /^PortableGit-[\d.]+-64-bit\.7z\.exe$/);
|
|
15
|
+
const sfx = tool(asset.name);
|
|
16
|
+
log('Downloading Git for Windows (' + asset.name + ', ~60 MB once) ...');
|
|
17
|
+
try {
|
|
18
|
+
await download(asset.url, sfx, asset.sha256);
|
|
19
|
+
await execAsync(sfx, ['-o' + root, '-y'], { stdio: 'ignore' });
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
fs.rmSync(sfx, { force: true });
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* readonly / none: a `git` wrapper first on PATH and as a bash function (Git Bash would otherwise find the real
|
|
27
|
+
* git.exe), plus git config from the environment (which repository config cannot override).
|
|
28
|
+
*/
|
|
29
|
+
const installGuard = (env, access, realGit) => {
|
|
30
|
+
const dir = mkdirp(tool('gitguard'));
|
|
31
|
+
const guard = path.join(launchDir, 'runtime', 'gitguard.js');
|
|
32
|
+
// A real git.exe (programs like Claude Code only look for .exe); a .cmd only if the C# compiler is unavailable.
|
|
33
|
+
// A project added later reuses the boot's: sessions may be running it, and Windows cannot replace a running exe.
|
|
34
|
+
if (duringSession() && (exists(path.join(dir, 'git.exe')) || exists(path.join(dir, 'git.cmd')))) { /* keep it */ }
|
|
35
|
+
else
|
|
36
|
+
try {
|
|
37
|
+
fs.copyFileSync(ensureCompiled('gitshim'), path.join(dir, 'git.exe'));
|
|
38
|
+
fs.rmSync(path.join(dir, 'git.cmd'), { force: true });
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
warn('git guard: falling back to git.cmd (' + errMsg(e) + ')');
|
|
42
|
+
fs.writeFileSync(path.join(dir, 'git.cmd'), '@"' + process.execPath + '" "' + guard + '" %*\r\n');
|
|
43
|
+
}
|
|
44
|
+
fs.writeFileSync(path.join(dir, 'bashenv.sh'), 'git() { "' + posix(process.execPath) + '" "' + posix(guard) + '" "$@"; }\nexport -f git\n');
|
|
45
|
+
env.prepend.unshift(dir);
|
|
46
|
+
Object.assign(env.vars, {
|
|
47
|
+
AGENT_GIT_ACCESS: access, AGENT_REAL_GIT: access === 'readonly' ? realGit : '',
|
|
48
|
+
AGENT_GUARD_NODE: process.execPath, AGENT_GUARD_SCRIPT: guard, BASH_ENV: posix(path.join(dir, 'bashenv.sh')),
|
|
49
|
+
});
|
|
50
|
+
if (access === 'readonly') {
|
|
51
|
+
const hooks = mkdirp(path.join(dir, 'hooks'));
|
|
52
|
+
for (const hook of BLOCKED_HOOKS)
|
|
53
|
+
fs.writeFileSync(path.join(hooks, hook), '#!/bin/sh\necho "git is read-only in this sandbox: ' + hook + ' refused." >&2\nexit 1\n');
|
|
54
|
+
Object.assign(env.vars, { GIT_CONFIG_COUNT: '1', GIT_CONFIG_KEY_0: 'core.hooksPath', GIT_CONFIG_VALUE_0: posix(hooks) });
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Even a git.exe found some other way sees no repository.
|
|
58
|
+
env.vars.GIT_DIR = path.join(dataDir, 'git-disabled');
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Git copied from this PC keeps its installer's absolute paths in etc\gitconfig, e.g. http.sslCAInfo =
|
|
63
|
+
* C:/Program Files/Git/mingw64/etc/ssl/certs/ca-bundle.crt, which does not exist here: every HTTPS clone/fetch failed
|
|
64
|
+
* with "error setting certificate file". Point it at the bundle inside this copy.
|
|
65
|
+
*/
|
|
66
|
+
const pointAtOwnCertificates = (root) => {
|
|
67
|
+
const config = path.join(root, 'etc', 'gitconfig');
|
|
68
|
+
const bundle = path.join(root, 'mingw64', 'etc', 'ssl', 'certs', 'ca-bundle.crt');
|
|
69
|
+
if (!exists(config) || !exists(bundle))
|
|
70
|
+
return;
|
|
71
|
+
const text = fs.readFileSync(config, 'utf8');
|
|
72
|
+
const fixed = text.replace(/^(\s*sslCAInfo\s*=\s*).*$/gim, (_m, key) => key + posix(bundle));
|
|
73
|
+
if (fixed !== text)
|
|
74
|
+
fs.writeFileSync(config, fixed);
|
|
75
|
+
};
|
|
76
|
+
export const setupGit = async (env, hostCopy, refresh, access) => {
|
|
77
|
+
const root = tool('git');
|
|
78
|
+
const bash = path.join(root, 'bin', 'bash.exe');
|
|
79
|
+
const gitExe = path.join(root, 'cmd', 'git.exe');
|
|
80
|
+
const hostGit = path.join(hostToolsDir, 'git');
|
|
81
|
+
if (exists(root) && !exists(bash)) {
|
|
82
|
+
log('Replacing the old MinGit (no bash) with full Git for Windows.');
|
|
83
|
+
safeRemove(root, toolsDir);
|
|
84
|
+
}
|
|
85
|
+
if (!exists(bash) || refresh) {
|
|
86
|
+
if (dryRun) {
|
|
87
|
+
log('(dry run) would provision Git for Windows' + (hostCopy ? ' from this PC' : '') + ' (access: ' + access + ')');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
// Into a staging folder, then renamed: an interrupted copy/extraction must not leave a tools\git that has bash.exe
|
|
91
|
+
// (so looks installed) but is missing the rest.
|
|
92
|
+
const work = stagingFolder(root);
|
|
93
|
+
try {
|
|
94
|
+
if (hostCopy && exists(path.join(hostGit, 'bin', 'bash.exe'))) {
|
|
95
|
+
log('Copying Git for Windows from this PC ...');
|
|
96
|
+
robocopy(hostGit, work);
|
|
97
|
+
}
|
|
98
|
+
else
|
|
99
|
+
await installPortableGit(work);
|
|
100
|
+
if (!exists(path.join(work, 'bin', 'bash.exe')))
|
|
101
|
+
throw new Error('bash.exe missing after installing Git');
|
|
102
|
+
moveIntoPlace(work, root);
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
safeRemove(work, toolsDir);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (!exists(bash))
|
|
109
|
+
throw new Error('bash.exe missing after installing Git');
|
|
110
|
+
pointAtOwnCertificates(root);
|
|
111
|
+
env.vars.CLAUDE_CODE_GIT_BASH_PATH = bash;
|
|
112
|
+
env.append.push(path.join(root, 'usr', 'bin'));
|
|
113
|
+
const version = execOutput(gitExe, ['--version']).replace(/^git version /, '');
|
|
114
|
+
if (access === 'full') {
|
|
115
|
+
env.prepend.push(path.join(root, 'cmd'));
|
|
116
|
+
env.summary.push('git: ' + version);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
installGuard(env, access, gitExe);
|
|
120
|
+
env.summary.push(access === 'readonly' ? 'git: ' + version + ' READ-ONLY (status/log/diff/show/blame; no commit, push, checkout, ...)' : 'git: DISABLED (bash only; the agent cannot use git)');
|
|
121
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Go from go.dev (checksum-verified zip). Modules, build cache and `go install` binaries live in persistent storage;
|
|
2
|
+
// GOTOOLCHAIN=auto lets a go.mod that needs a newer Go fetch it itself.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fetchJson, installArchive } from '../../shared/download.js';
|
|
6
|
+
import { dryRun, execAsync, exists, log } from '../env.js';
|
|
7
|
+
import { arch, installOnce, resolveCached, tool, versionOf } from './common.js';
|
|
8
|
+
import { pickGoRelease } from './versions.js';
|
|
9
|
+
const findRelease = async (spec) => pickGoRelease(await fetchJson('https://go.dev/dl/?mode=json&include=all'), spec) ?? Promise.reject(new Error('No Go release matches "' + spec + '"'));
|
|
10
|
+
export const setupGo = async (env, config) => {
|
|
11
|
+
if (dryRun) {
|
|
12
|
+
log('(dry run) would provision Go ' + config.version);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const version = await resolveCached('go:' + config.version, async () => (await findRelease(config.version)).version);
|
|
16
|
+
const root = tool('go', version);
|
|
17
|
+
if (!exists(path.join(root, 'bin', 'go.exe'))) {
|
|
18
|
+
const goArch = arch === 'arm64' ? 'arm64' : 'amd64';
|
|
19
|
+
const file = (await findRelease(version)).files.find((f) => f.os === 'windows' && f.arch === goArch && f.kind === 'archive');
|
|
20
|
+
if (!file)
|
|
21
|
+
throw new Error(version + ' has no Windows ' + goArch + ' zip');
|
|
22
|
+
log('Downloading Go ' + version.replace(/^go/, '') + ' (~80 MB once) ...');
|
|
23
|
+
await installArchive('https://go.dev/dl/' + file.filename, root, { sha256: file.sha256, locate: 'VERSION', fileName: file.filename });
|
|
24
|
+
}
|
|
25
|
+
const GOPATH = tool('go-path');
|
|
26
|
+
const vars = { GOROOT: root, GOPATH, GOBIN: path.join(GOPATH, 'bin'), GOMODCACHE: path.join(GOPATH, 'pkg', 'mod'), GOCACHE: tool('go-cache'), GOTOOLCHAIN: 'auto', GOTELEMETRY: 'off' };
|
|
27
|
+
fs.mkdirSync(vars.GOBIN, { recursive: true });
|
|
28
|
+
Object.assign(env.vars, vars);
|
|
29
|
+
env.prepend.push(path.join(root, 'bin'), vars.GOBIN);
|
|
30
|
+
const go = path.join(root, 'bin', 'go.exe');
|
|
31
|
+
env.summary.push('go: ' + versionOf(go, ['version']));
|
|
32
|
+
await installOnce(env, 'go', config.tools, (pkg) => execAsync(go, ['install', pkg.includes('@') ? pkg : pkg + '@latest'], { env: { ...process.env, ...vars } }));
|
|
33
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Toolchains, ToolVersions } from '../../core/types.js';
|
|
2
|
+
import { type ToolEnv } from './common.js';
|
|
3
|
+
import { type ToolchainOptions } from './plan.js';
|
|
4
|
+
export type { ToolEnv } from './common.js';
|
|
5
|
+
export type { ToolchainOptions } from './plan.js';
|
|
6
|
+
export interface RunOptions {
|
|
7
|
+
/** Progress lines ("Installing Go, Rust (3 of 9 done)"); default: the sandbox's boot status. */
|
|
8
|
+
report?: (message: string) => void;
|
|
9
|
+
/** What machine-wide (shared) steps contributed at boot: reused instead of run again. */
|
|
10
|
+
reuse?: Record<string, ToolEnv>;
|
|
11
|
+
}
|
|
12
|
+
/** The combined environment, plus what each step contributed (the boot keeps it so later projects can reuse shared steps). */
|
|
13
|
+
export interface ToolchainResult extends ToolEnv {
|
|
14
|
+
steps: Record<string, ToolEnv>;
|
|
15
|
+
}
|
|
16
|
+
/** Provisions every enabled toolchain and database, and returns the combined environment for the agent. */
|
|
17
|
+
export declare const setupToolchains: (config: Toolchains, versions: ToolVersions, options: ToolchainOptions, run?: RunOptions) => Promise<ToolchainResult>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { log, mkdirp, setSandboxStatus } from '../env.js';
|
|
2
|
+
import { emptyEnv, step, tool } from './common.js';
|
|
3
|
+
import { plan } from './plan.js';
|
|
4
|
+
/** How many installers run at once: enough to overlap downloads with installs, few enough not to thrash the disk. */
|
|
5
|
+
const PARALLEL = 4;
|
|
6
|
+
/** One lock per kind of step ("Node 20" and "Node 22" share nvm's folders): held while that step runs, in any run. */
|
|
7
|
+
const locks = new Map();
|
|
8
|
+
const lockKey = (name) => name.split(' ')[0].toLowerCase();
|
|
9
|
+
const exclusive = async (name, work) => {
|
|
10
|
+
const key = lockKey(name);
|
|
11
|
+
const previous = locks.get(key) ?? Promise.resolve();
|
|
12
|
+
let release = () => undefined;
|
|
13
|
+
const mine = new Promise((r) => { release = r; });
|
|
14
|
+
const chained = previous.then(() => mine);
|
|
15
|
+
locks.set(key, chained);
|
|
16
|
+
await previous;
|
|
17
|
+
try {
|
|
18
|
+
return await work();
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
release();
|
|
22
|
+
if (locks.get(key) === chained)
|
|
23
|
+
locks.delete(key);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
/** Runs the steps (each with its own environment) at most PARALLEL at a time, after the steps they depend on. */
|
|
27
|
+
const runSteps = async (steps, o) => {
|
|
28
|
+
const envs = new Map(steps.map((s) => [s.name, emptyEnv()]));
|
|
29
|
+
const finished = new Map();
|
|
30
|
+
const running = new Set();
|
|
31
|
+
const waiting = [];
|
|
32
|
+
let done = 0;
|
|
33
|
+
const say = o.report ?? ((message) => setSandboxStatus('provisioning', message));
|
|
34
|
+
const report = () => { if (running.size)
|
|
35
|
+
say('Installing ' + [...running].join(', ') + ' (' + done + ' of ' + steps.length + ' done)'); };
|
|
36
|
+
for (const s of steps) {
|
|
37
|
+
// Dependencies first (they are earlier in the plan, so their promises exist), then a free slot.
|
|
38
|
+
finished.set(s.name, (async () => {
|
|
39
|
+
await Promise.all((s.after ?? []).map((name) => finished.get(name)));
|
|
40
|
+
const reused = s.shared ? o.reuse?.[s.name] : undefined;
|
|
41
|
+
if (reused) {
|
|
42
|
+
envs.set(s.name, structuredClone(reused));
|
|
43
|
+
done++;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
while (running.size >= PARALLEL)
|
|
47
|
+
await new Promise((r) => waiting.push(r));
|
|
48
|
+
running.add(s.name);
|
|
49
|
+
report();
|
|
50
|
+
try {
|
|
51
|
+
await exclusive(s.name, () => step(s.name, envs.get(s.name), async () => s.run(envs.get(s.name))));
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
running.delete(s.name);
|
|
55
|
+
done++;
|
|
56
|
+
report();
|
|
57
|
+
waiting.shift()?.();
|
|
58
|
+
}
|
|
59
|
+
})());
|
|
60
|
+
}
|
|
61
|
+
await Promise.all(finished.values());
|
|
62
|
+
return envs;
|
|
63
|
+
};
|
|
64
|
+
/** Provisions every enabled toolchain and database, and returns the combined environment for the agent. */
|
|
65
|
+
export const setupToolchains = async (config, versions, options, run = {}) => {
|
|
66
|
+
const steps = plan(config, versions, options).filter((s) => s.on);
|
|
67
|
+
const envs = await runSteps(steps, run);
|
|
68
|
+
// Merged in plan order, so PATH order never depends on which download finished first.
|
|
69
|
+
const env = { ...emptyEnv(), steps: Object.fromEntries(envs) };
|
|
70
|
+
env.prepend.push(mkdirp(tool('bin')));
|
|
71
|
+
for (const part of envs.values()) {
|
|
72
|
+
Object.assign(env.vars, part.vars);
|
|
73
|
+
env.prepend.push(...part.prepend);
|
|
74
|
+
env.append.push(...part.append);
|
|
75
|
+
env.summary.push(...part.summary);
|
|
76
|
+
}
|
|
77
|
+
Object.assign(env.vars, config.environment);
|
|
78
|
+
log('Set up ' + steps.length + ' toolchains and databases.');
|
|
79
|
+
return env;
|
|
80
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Toolchains } from '../../core/types.js';
|
|
2
|
+
import { type ToolEnv } from './common.js';
|
|
3
|
+
/**
|
|
4
|
+
* A JDK for tools that run on Java (DynamoDB Local): the newest one already installed, else the newest LTS. Returns its
|
|
5
|
+
* folder; the agent's Java on PATH stays whatever toolchains.java says.
|
|
6
|
+
*/
|
|
7
|
+
export declare const ensureJdk: () => Promise<string>;
|
|
8
|
+
export declare const setupJava: (env: ToolEnv, config: Toolchains["java"]) => Promise<void>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Java: Eclipse Temurin JDK (Adoptium), plus Maven and Gradle. All checksum-verified; the Maven repository and Gradle
|
|
2
|
+
// caches live in persistent storage, so dependencies download once.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fetchJson, fetchText, installArchive } from '../../shared/download.js';
|
|
6
|
+
import { dryRun, errMsg, exists, log, warn } from '../env.js';
|
|
7
|
+
import { arch, emptyEnv, resolveCached, tool, versionFromFiles } from './common.js';
|
|
8
|
+
import { javaMajorFrom, pickPackageVersion } from './versions.js';
|
|
9
|
+
const ADOPTIUM = 'https://api.adoptium.net/v3';
|
|
10
|
+
const MAVEN = 'https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/';
|
|
11
|
+
const temurin = async (major) => {
|
|
12
|
+
const assets = await fetchJson(ADOPTIUM + '/assets/latest/' + major + '/hotspot?image_type=jdk&os=windows&vendor=eclipse&architecture=' + (arch === 'arm64' ? 'aarch64' : 'x64'));
|
|
13
|
+
return assets.find((a) => a.binary.package.name.endsWith('.zip')) ?? Promise.reject(new Error('No Temurin ' + major + ' JDK zip for Windows'));
|
|
14
|
+
};
|
|
15
|
+
/** A project's .java-version / .sdkmanrc, else the configured version ("lts" = the newest LTS). */
|
|
16
|
+
const javaMajor = async (config) => {
|
|
17
|
+
const fromProject = versionFromFiles(['.java-version', '.sdkmanrc'], (text, name) => javaMajorFrom(text, name === '.sdkmanrc'));
|
|
18
|
+
if (fromProject)
|
|
19
|
+
return { major: fromProject.spec, source: fromProject.source };
|
|
20
|
+
const spec = config.version.trim().toLowerCase();
|
|
21
|
+
if (spec !== 'lts')
|
|
22
|
+
return { major: javaMajorFrom(spec) ?? spec, source: 'config' };
|
|
23
|
+
const lts = await resolveCached('java:lts', async () => String((await fetchJson(ADOPTIUM + '/info/available_releases')).most_recent_lts));
|
|
24
|
+
return { major: lts, source: 'newest LTS' };
|
|
25
|
+
};
|
|
26
|
+
const setupJdk = async (env, config) => {
|
|
27
|
+
const { major, source } = await javaMajor(config);
|
|
28
|
+
const release = await resolveCached('java:' + major, async () => (await temurin(major)).release_name);
|
|
29
|
+
const home = tool('java', release.replace(/[^A-Za-z0-9.-]/g, '_'));
|
|
30
|
+
if (!exists(path.join(home, 'bin', 'java.exe'))) {
|
|
31
|
+
const asset = await temurin(major);
|
|
32
|
+
log('Downloading Java ' + release + ' (JDK, ~200 MB once) ...');
|
|
33
|
+
await installArchive(asset.binary.package.link, home, { sha256: asset.binary.package.checksum, locate: 'release', fileName: asset.binary.package.name });
|
|
34
|
+
}
|
|
35
|
+
env.vars.JAVA_HOME = home;
|
|
36
|
+
env.prepend.push(path.join(home, 'bin'));
|
|
37
|
+
env.summary.push('java: ' + release + ' (Temurin, from ' + source + ')');
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* A JDK for tools that run on Java (DynamoDB Local): the newest one already installed, else the newest LTS. Returns its
|
|
41
|
+
* folder; the agent's Java on PATH stays whatever toolchains.java says.
|
|
42
|
+
*/
|
|
43
|
+
export const ensureJdk = async () => {
|
|
44
|
+
const root = tool('java');
|
|
45
|
+
const installed = exists(root) ? fs.readdirSync(root).filter((d) => exists(path.join(root, d, 'bin', 'java.exe'))).sort((a, b) => b.localeCompare(a, undefined, { numeric: true })) : [];
|
|
46
|
+
if (installed.length)
|
|
47
|
+
return path.join(root, installed[0]);
|
|
48
|
+
const scratch = emptyEnv();
|
|
49
|
+
await setupJdk(scratch, { enabled: true, version: 'lts', maven: false, gradle: false });
|
|
50
|
+
return scratch.vars.JAVA_HOME;
|
|
51
|
+
};
|
|
52
|
+
const setupMaven = async (env) => {
|
|
53
|
+
const version = await resolveCached('maven:3', async () => {
|
|
54
|
+
const versions = [...(await fetchText(MAVEN + 'maven-metadata.xml')).matchAll(/<version>([^<]+)<\/version>/g)].map((m) => m[1]);
|
|
55
|
+
return pickPackageVersion(versions, '3') ?? Promise.reject(new Error('No Maven 3 release found'));
|
|
56
|
+
});
|
|
57
|
+
const home = tool('maven', version);
|
|
58
|
+
if (!exists(path.join(home, 'bin', 'mvn.cmd'))) {
|
|
59
|
+
const url = MAVEN + version + '/apache-maven-' + version + '-bin.zip';
|
|
60
|
+
log('Downloading Maven ' + version + ' (~10 MB once) ...');
|
|
61
|
+
await installArchive(url, home, { sha512: (await fetchText(url + '.sha512')).trim().split(/\s+/)[0], locate: 'LICENSE' });
|
|
62
|
+
}
|
|
63
|
+
const repo = tool('maven-repository');
|
|
64
|
+
Object.assign(env.vars, { MAVEN_HOME: home, M2_HOME: home, MAVEN_OPTS: '-Dmaven.repo.local=' + repo });
|
|
65
|
+
env.prepend.push(path.join(home, 'bin'));
|
|
66
|
+
env.summary.push('maven: ' + version);
|
|
67
|
+
};
|
|
68
|
+
const setupGradle = async (env) => {
|
|
69
|
+
const current = await fetchJson('https://services.gradle.org/versions/current').catch(() => null);
|
|
70
|
+
const version = await resolveCached('gradle', async () => current?.version ?? Promise.reject(new Error('Gradle version lookup failed')));
|
|
71
|
+
const home = tool('gradle', version);
|
|
72
|
+
if (!exists(path.join(home, 'bin', 'gradle.bat'))) {
|
|
73
|
+
if (!current || current.version !== version)
|
|
74
|
+
throw new Error('Gradle ' + version + ' is not installed and cannot be downloaded right now');
|
|
75
|
+
log('Downloading Gradle ' + version + ' (~130 MB once) ...');
|
|
76
|
+
await installArchive(current.downloadUrl, home, { sha256: (await fetchText(current.checksumUrl)).trim(), locate: 'LICENSE', fileName: 'gradle-' + version + '.zip' });
|
|
77
|
+
}
|
|
78
|
+
const userHome = tool('gradle-home');
|
|
79
|
+
fs.mkdirSync(userHome, { recursive: true });
|
|
80
|
+
// File-system watching does not work on mapped folders (harmless but noisy "Couldn't add watch" on every build).
|
|
81
|
+
Object.assign(env.vars, { GRADLE_HOME: home, GRADLE_USER_HOME: userHome, GRADLE_OPTS: '-Dorg.gradle.vfs.watch=false' });
|
|
82
|
+
env.prepend.push(path.join(home, 'bin'));
|
|
83
|
+
env.summary.push('gradle: ' + version);
|
|
84
|
+
};
|
|
85
|
+
export const setupJava = async (env, config) => {
|
|
86
|
+
if (dryRun) {
|
|
87
|
+
log('(dry run) would provision Java ' + config.version + (config.maven ? ' + Maven' : '') + (config.gradle ? ' + Gradle' : ''));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
await setupJdk(env, config);
|
|
91
|
+
// Maven and Gradle are extras: one failing must not take the JDK down with it.
|
|
92
|
+
for (const [on, name, work] of [[config.maven, 'Maven', setupMaven], [config.gradle, 'Gradle', setupGradle]]) {
|
|
93
|
+
if (on)
|
|
94
|
+
await work(env).catch((e) => { env.summary.push(name.toLowerCase() + ': FAILED'); warn(name + ' setup failed (continuing without it): ' + errMsg(e)); });
|
|
95
|
+
}
|
|
96
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Clean-up of what interrupted or older installs left in the tools folder.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { safeRemove } from '../../shared/safe-remove.js';
|
|
5
|
+
import { toolsDir } from '../env.js';
|
|
6
|
+
const STAGING = /\.staging-[0-9a-f]+$/i;
|
|
7
|
+
const entries = (dir) => { try {
|
|
8
|
+
return fs.readdirSync(dir, { withFileTypes: true });
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return [];
|
|
12
|
+
} };
|
|
13
|
+
/** Removes helper builds older versions left in persistent storage, and interrupted installs' staging folders. */
|
|
14
|
+
export const pruneToolLeftovers = () => {
|
|
15
|
+
for (const legacy of ['ptyhost', 'gitshim'])
|
|
16
|
+
safeRemove(path.join(toolsDir, legacy), toolsDir);
|
|
17
|
+
// Staging folders sit next to their destination: tools\x.staging-ab12 and one level down (tools\nvm\v22.staging-ab12).
|
|
18
|
+
for (const top of entries(toolsDir)) {
|
|
19
|
+
const dir = path.join(toolsDir, top.name);
|
|
20
|
+
if (STAGING.test(top.name))
|
|
21
|
+
safeRemove(dir, toolsDir);
|
|
22
|
+
else if (top.isDirectory())
|
|
23
|
+
for (const sub of entries(dir).filter((e) => STAGING.test(e.name)))
|
|
24
|
+
safeRemove(path.join(dir, sub.name), dir);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Toolchains } from '../../core/types.js';
|
|
2
|
+
import { type ToolEnv } from './common.js';
|
|
3
|
+
/** The "volta" block of the workspace's package.json ({ node, npm, yarn, pnpm }), if any. */
|
|
4
|
+
export declare const voltaPins: () => Record<string, string>;
|
|
5
|
+
export declare const wantsVolta: (setting: Toolchains["node"]["volta"]) => boolean;
|
|
6
|
+
export declare const wantsPyenv: (setting: Toolchains["python"]["pyenv"]) => boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Volta from its portable zip. With pins, its shims go first on PATH (the project gets exactly what package.json says,
|
|
9
|
+
* fetched ahead of time); without, Volta is just available and nvm's Node stays the default.
|
|
10
|
+
*/
|
|
11
|
+
export declare const setupVolta: (env: ToolEnv) => Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* pyenv-win from its source archive (it ships no release assets). Its versions persist in tools\pyenv. It comes after
|
|
14
|
+
* the Python step on PATH, so `python` is the sandbox's own until you `pyenv install` / `pyenv local`.
|
|
15
|
+
*/
|
|
16
|
+
export declare const setupPyenv: (env: ToolEnv) => Promise<void>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Version managers people already use: Volta (Node, npm, yarn, pnpm pinned in package.json) and pyenv-win. nvm and
|
|
2
|
+
// the Python step still provide the default versions; these come along when the project or this PC uses them.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { installArchive, latestGithubTag } from '../../shared/download.js';
|
|
6
|
+
import { dryRun, errMsg, execAsync, exists, log, mkdirp, session, warn } from '../env.js';
|
|
7
|
+
import { projectWorkspace } from '../project-context.js';
|
|
8
|
+
import { arch, installFromGithub, resolveCached, tool, versionOf } from './common.js';
|
|
9
|
+
/** The "volta" block of the workspace's package.json ({ node, npm, yarn, pnpm }), if any. */
|
|
10
|
+
export const voltaPins = () => {
|
|
11
|
+
try {
|
|
12
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(projectWorkspace(), 'package.json'), 'utf8').replace(/^/, ''));
|
|
13
|
+
return Object.fromEntries(Object.entries(pkg.volta ?? {}).filter((e) => ['node', 'npm', 'yarn', 'pnpm'].includes(e[0]) && typeof e[1] === 'string'));
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
export const wantsVolta = (setting) => setting === 'auto' ? Object.keys(voltaPins()).length > 0 || Boolean(session.hostTools.volta) : setting;
|
|
20
|
+
export const wantsPyenv = (setting) => setting === 'auto' ? Boolean(session.hostTools.pyenv) : setting;
|
|
21
|
+
/**
|
|
22
|
+
* Volta from its portable zip. With pins, its shims go first on PATH (the project gets exactly what package.json says,
|
|
23
|
+
* fetched ahead of time); without, Volta is just available and nvm's Node stays the default.
|
|
24
|
+
*/
|
|
25
|
+
export const setupVolta = async (env) => {
|
|
26
|
+
if (dryRun) {
|
|
27
|
+
log('(dry run) would provision Volta');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const dir = tool('volta');
|
|
31
|
+
if (!exists(path.join(dir, 'volta.exe'))) {
|
|
32
|
+
const pattern = arch === 'arm64' ? /^volta-[\d.]+-windows-arm64\.zip$/ : /^volta-[\d.]+-windows\.zip$/;
|
|
33
|
+
await installFromGithub('volta-cli/volta', pattern, { dest: dir, what: 'Volta (~15 MB once)', locate: 'volta.exe' });
|
|
34
|
+
}
|
|
35
|
+
// The MSI puts these shims (copies of volta-shim.exe) next to volta.exe; the portable zip leaves that to us.
|
|
36
|
+
for (const shim of ['node', 'npm', 'npx', 'pnpm', 'pnpx', 'yarn', 'yarnpkg']) {
|
|
37
|
+
const target = path.join(dir, shim + '.exe');
|
|
38
|
+
if (!exists(target))
|
|
39
|
+
fs.copyFileSync(path.join(dir, 'volta-shim.exe'), target);
|
|
40
|
+
}
|
|
41
|
+
const home = mkdirp(tool('volta-home'));
|
|
42
|
+
const shims = mkdirp(path.join(home, 'bin'));
|
|
43
|
+
const vars = { VOLTA_HOME: home, VOLTA_FEATURE_PNPM: '1' };
|
|
44
|
+
Object.assign(env.vars, vars);
|
|
45
|
+
const pins = voltaPins();
|
|
46
|
+
if (Object.keys(pins).length)
|
|
47
|
+
env.prepend.push(dir, shims);
|
|
48
|
+
else
|
|
49
|
+
env.append.push(dir, shims);
|
|
50
|
+
const volta = path.join(dir, 'volta.exe');
|
|
51
|
+
// `volta install` fetches the pinned versions now (not on the agent's first `node`) and makes them the defaults.
|
|
52
|
+
for (const [toolName, version] of Object.entries(pins)) {
|
|
53
|
+
try {
|
|
54
|
+
await execAsync(volta, ['install', toolName + '@' + version], { env: { ...process.env, ...vars }, cwd: projectWorkspace(), stdio: 'ignore' });
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
warn('Volta could not install ' + toolName + '@' + version + ': ' + errMsg(e));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const pinned = Object.entries(pins).map(([k, v]) => k + '@' + v);
|
|
61
|
+
const note = pinned.length ? ' (pins from package.json: ' + pinned.join(', ') + ')' : ' (no pins in this project; nvm\'s node is the default)';
|
|
62
|
+
env.summary.push('volta: ' + versionOf(volta) + note);
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* pyenv-win from its source archive (it ships no release assets). Its versions persist in tools\pyenv. It comes after
|
|
66
|
+
* the Python step on PATH, so `python` is the sandbox's own until you `pyenv install` / `pyenv local`.
|
|
67
|
+
*/
|
|
68
|
+
export const setupPyenv = async (env) => {
|
|
69
|
+
if (dryRun) {
|
|
70
|
+
log('(dry run) would provision pyenv-win');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const root = tool('pyenv', 'pyenv-win');
|
|
74
|
+
if (!exists(path.join(root, 'bin', 'pyenv.bat'))) {
|
|
75
|
+
const tag = await resolveCached('pyenv-win', () => latestGithubTag('pyenv-win/pyenv-win'));
|
|
76
|
+
log('Downloading pyenv-win ' + tag + ' (~5 MB once) ...');
|
|
77
|
+
// GitHub's tag archive: no published checksum, but HTTPS from github.com for the project's own tag.
|
|
78
|
+
await installArchive('https://github.com/pyenv-win/pyenv-win/archive/refs/tags/' + encodeURIComponent(tag) + '.zip', root, { locate: 'pyenv.bat', up: 1, fileName: 'pyenv-win.zip' });
|
|
79
|
+
}
|
|
80
|
+
const home = root + path.sep;
|
|
81
|
+
Object.assign(env.vars, { PYENV: home, PYENV_ROOT: home, PYENV_HOME: home });
|
|
82
|
+
env.append.push(path.join(root, 'bin'), path.join(root, 'shims'));
|
|
83
|
+
env.summary.push('pyenv-win: pyenv install <version> / pyenv local <version> (versions persist)');
|
|
84
|
+
};
|