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,7 @@
|
|
|
1
|
+
import type { SpawnOptions } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Returns the installer for one crate: `cargo binstall` (prebuilt only: its own compile fallback is off, so a crate
|
|
4
|
+
* without a Windows build is compiled by our `cargo install` with the linker settings in `cargoEnv`), then
|
|
5
|
+
* `cargo install --locked`. cargo-binstall is fetched on first use, so boots with every crate installed download nothing.
|
|
6
|
+
*/
|
|
7
|
+
export declare const crateInstaller: (cargoHome: string, cargoEnv: SpawnOptions) => ((crate: string) => Promise<void>);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// toolchains.rust.crates with prebuilt binaries: cargo-binstall (from its own GitHub release) fetches a crate's
|
|
2
|
+
// published Windows build in seconds; only crates without one are compiled with `cargo install --locked`.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { errMsg, execAsync, exists, log, warn } from '../../env.js';
|
|
6
|
+
import { binstallAsset } from '../catalogue.js';
|
|
7
|
+
import { arch, installFromGithub, tool } from '../common.js';
|
|
8
|
+
/**
|
|
9
|
+
* cargo-binstall.exe into CARGO_HOME\bin (so `cargo binstall` works for the agent too). Downloaded into its own
|
|
10
|
+
* tools\cargo-binstall folder first: installArchive replaces its destination, which must never be cargo's bin.
|
|
11
|
+
*/
|
|
12
|
+
const ensureBinstall = async (cargoBin) => {
|
|
13
|
+
const target = path.join(cargoBin, 'cargo-binstall.exe');
|
|
14
|
+
if (exists(target))
|
|
15
|
+
return true;
|
|
16
|
+
try {
|
|
17
|
+
const dir = tool('cargo-binstall');
|
|
18
|
+
if (!exists(path.join(dir, 'cargo-binstall.exe'))) {
|
|
19
|
+
await installFromGithub('cargo-bins/cargo-binstall', binstallAsset(arch), { dest: dir, what: 'cargo-binstall for prebuilt Rust tools (~10 MB once)', locate: 'cargo-binstall.exe' });
|
|
20
|
+
}
|
|
21
|
+
fs.copyFileSync(path.join(dir, 'cargo-binstall.exe'), target);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
warn('cargo-binstall unavailable, compiling crates with cargo install instead: ' + errMsg(e));
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Returns the installer for one crate: `cargo binstall` (prebuilt only: its own compile fallback is off, so a crate
|
|
31
|
+
* without a Windows build is compiled by our `cargo install` with the linker settings in `cargoEnv`), then
|
|
32
|
+
* `cargo install --locked`. cargo-binstall is fetched on first use, so boots with every crate installed download nothing.
|
|
33
|
+
*/
|
|
34
|
+
export const crateInstaller = (cargoHome, cargoEnv) => {
|
|
35
|
+
const cargo = path.join(cargoHome, 'bin', 'cargo.exe');
|
|
36
|
+
let binstall = null;
|
|
37
|
+
// quick-install's install statistics are opt-out; the sandbox opts out.
|
|
38
|
+
const binstallEnv = { ...cargoEnv, env: { ...(cargoEnv.env ?? process.env), BINSTALL_DISABLE_TELEMETRY: 'true' } };
|
|
39
|
+
return async (crate) => {
|
|
40
|
+
binstall ??= ensureBinstall(path.join(cargoHome, 'bin'));
|
|
41
|
+
if (await binstall) {
|
|
42
|
+
try {
|
|
43
|
+
await execAsync(cargo, ['binstall', '--no-confirm', '--locked', '--disable-strategies', 'compile', crate], binstallEnv);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
log('No prebuilt ' + crate + ' for Windows; compiling it with cargo (takes a few minutes) ...');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
await execAsync(cargo, ['install', '--locked', crate], cargoEnv);
|
|
51
|
+
};
|
|
52
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Toolchains } from '../../../core/types.js';
|
|
2
|
+
import { type ToolEnv } from '../common.js';
|
|
3
|
+
/** MinGW-w64 GCC from WinLibs: gcc/g++/mingw32-make for the agent, and the linker for Rust's GNU host. */
|
|
4
|
+
export declare const setupCpp: (env: ToolEnv, config: Toolchains["cpp"]) => Promise<void>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// C/C++: MinGW-w64 GCC (with CMake, Ninja, make) and the flex/bison parser generators.
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { dryRun, exists, log, warn } from '../../env.js';
|
|
4
|
+
import { arch, installFromGithub, tool, versionOf } from '../common.js';
|
|
5
|
+
import { CMAKE_FLOOR, cmakeTooOld } from '../versions.js';
|
|
6
|
+
/** MinGW-w64 GCC from WinLibs: gcc/g++/mingw32-make for the agent, and the linker for Rust's GNU host. */
|
|
7
|
+
export const setupCpp = async (env, config) => {
|
|
8
|
+
const bin = tool('mingw', 'mingw64', 'bin');
|
|
9
|
+
const gcc = path.join(bin, 'gcc.exe');
|
|
10
|
+
if (dryRun) {
|
|
11
|
+
log('(dry run) would provision MinGW-w64 GCC');
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (!exists(gcc)) {
|
|
15
|
+
const pattern = arch === 'arm64' ? /^winlibs-aarch64-.*\.zip$/ : /^winlibs-x86_64-posix-seh-gcc-.*\.zip$/;
|
|
16
|
+
await installFromGithub('brechtsanders/winlibs_mingw', pattern, { dest: tool('mingw'), what: 'the C/C++ compiler, MinGW-w64 GCC with CMake and Ninja (~270 MB once)', searchOlder: true });
|
|
17
|
+
}
|
|
18
|
+
if (!exists(gcc))
|
|
19
|
+
throw new Error('gcc.exe missing after installing MinGW');
|
|
20
|
+
// Appended (not prepended) so MinGW's tools never shadow Windows or toolchain commands of the same name.
|
|
21
|
+
env.append.unshift(bin);
|
|
22
|
+
env.vars.CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER = gcc;
|
|
23
|
+
const cmake = versionOf(path.join(bin, 'cmake.exe'));
|
|
24
|
+
if (cmakeTooOld(cmake))
|
|
25
|
+
warn('The bundled CMake ' + cmake + ' is older than ' + CMAKE_FLOOR + ', which many projects need; set toolchains.cpp.enabled to refresh it, or install a newer CMake into C:\\AgentData\\tools\\bin.');
|
|
26
|
+
env.summary.push('gcc: ' + versionOf(gcc) + ' (MinGW-w64, with cmake ' + cmake + ', ninja, premake5)');
|
|
27
|
+
if (config.flexBison)
|
|
28
|
+
await setupFlexBison(env);
|
|
29
|
+
};
|
|
30
|
+
/** win_flex / win_bison (CMake's FindFLEX/FindBISON look for these names). Kept with the C/C++ tools on PATH. */
|
|
31
|
+
const setupFlexBison = async (env) => {
|
|
32
|
+
const dir = tool('winflexbison');
|
|
33
|
+
if (!exists(path.join(dir, 'win_bison.exe'))) {
|
|
34
|
+
await installFromGithub('lexxmark/winflexbison', /^win_flex_bison-[\d.]+\.zip$/, { dest: dir, what: 'win_flex / win_bison (~1 MB once)', locate: 'win_bison.exe' });
|
|
35
|
+
}
|
|
36
|
+
env.append.push(dir);
|
|
37
|
+
env.summary.push('win_flex/win_bison: ' + versionOf(path.join(dir, 'win_bison.exe')));
|
|
38
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Toolchains } from '../../../core/types.js';
|
|
2
|
+
import { type ToolEnv } from '../common.js';
|
|
3
|
+
/** .NET SDK channels from config plus whatever the workspace's global.json pins. Installs are remembered in a marker. */
|
|
4
|
+
export declare const setupDotnet: (env: ToolEnv, config: Toolchains["dotnet"], refresh: boolean) => Promise<void>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// .NET: the SDK (dotnet-install.ps1), .NET Framework reference assemblies for old-style projects, and .NET Framework 3.5.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { fetchJson, installArchive } from '../../../shared/download.js';
|
|
6
|
+
import { readJsonFile, writeJsonAtomic } from '../../../shared/rundir.js';
|
|
7
|
+
import { safeRemove } from '../../../shared/safe-remove.js';
|
|
8
|
+
import { SYSTEM } from '../../../shared/system.js';
|
|
9
|
+
import { dataDir, dryRun, errMsg, execAsync, execOutput, exists, log, mkdirp, warn } from '../../env.js';
|
|
10
|
+
import { projectWorkspace } from '../../project-context.js';
|
|
11
|
+
import { downloadFile, installOnce, tool } from '../common.js';
|
|
12
|
+
/** tools\dotnet\.agent-sandbox-installed.json: which install keys (channel:8.0, global.json:...) already ran. */
|
|
13
|
+
const DotnetMarker = z.record(z.string(), z.boolean());
|
|
14
|
+
/** .NET SDK channels from config plus whatever the workspace's global.json pins. Installs are remembered in a marker. */
|
|
15
|
+
export const setupDotnet = async (env, config, refresh) => {
|
|
16
|
+
const root = tool('dotnet');
|
|
17
|
+
const marker = path.join(root, '.agent-sandbox-installed.json');
|
|
18
|
+
const script = tool('dotnet-install.ps1');
|
|
19
|
+
if (dryRun) {
|
|
20
|
+
log('(dry run) would provision .NET SDK channels ' + config.channels.join(', '));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
// A corrupt or hand-edited marker just means "nothing recorded yet" (the installs are idempotent).
|
|
24
|
+
const recorded = DotnetMarker.safeParse(exists(marker) && !refresh ? readJsonFile(marker) : {});
|
|
25
|
+
const done = recorded.success ? recorded.data : {};
|
|
26
|
+
const globalJson = path.join(projectWorkspace(), 'global.json');
|
|
27
|
+
const installs = [
|
|
28
|
+
...config.channels.map((c) => ['channel:' + c, ['-Channel', c]]),
|
|
29
|
+
...(exists(globalJson) ? [['global.json:' + fs.readFileSync(globalJson, 'utf8').replace(/\s+/g, ''), ['-JSonFile', globalJson]]] : []),
|
|
30
|
+
];
|
|
31
|
+
for (const [key, args] of installs.filter(([k]) => !done[k])) {
|
|
32
|
+
// Fetched every time something is installed (it is small): an interrupted earlier download must not be run.
|
|
33
|
+
await downloadFile('https://dot.net/v1/dotnet-install.ps1', script);
|
|
34
|
+
log('Installing the .NET SDK ' + (key.startsWith('channel:') ? key.slice(8) : 'pinned in global.json') + ' (~250 MB once) ...');
|
|
35
|
+
await execAsync(SYSTEM.powershell(), ['-NoLogo', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', script, ...args, '-InstallDir', root, '-NoPath']);
|
|
36
|
+
done[key] = true;
|
|
37
|
+
writeJsonAtomic(marker, done);
|
|
38
|
+
}
|
|
39
|
+
const home = mkdirp(tool('dotnet-home'));
|
|
40
|
+
Object.assign(env.vars, { DOTNET_ROOT: root, DOTNET_CLI_HOME: home, DOTNET_CLI_TELEMETRY_OPTOUT: '1', DOTNET_NOLOGO: '1', DOTNET_SKIP_FIRST_TIME_EXPERIENCE: '1', NUGET_PACKAGES: tool('nuget', 'packages') });
|
|
41
|
+
env.prepend.push(root, path.join(home, '.dotnet', 'tools'));
|
|
42
|
+
env.summary.push('dotnet: ' + execOutput(path.join(root, 'dotnet.exe'), ['--list-sdks']).split(/\r?\n/).map((l) => l.split(' ')[0]).filter(Boolean).join(', '));
|
|
43
|
+
const dotnet = path.join(root, 'dotnet.exe');
|
|
44
|
+
const dotnetEnv = { env: { ...process.env, ...env.vars, PATH: root + path.delimiter + (process.env.PATH ?? '') } };
|
|
45
|
+
await installOnce(env, 'dotnet workload', config.workloads, (w) => execAsync(dotnet, ['workload', 'install', w, '--skip-manifest-update'], dotnetEnv));
|
|
46
|
+
await installOnce(env, 'dotnet tool', config.tools, (t) => execAsync(dotnet, ['tool', 'install', '--global', t], dotnetEnv));
|
|
47
|
+
await setupFrameworkReferences(env, config.frameworkTargets);
|
|
48
|
+
if (config.netfx35)
|
|
49
|
+
await setupNetFx35(env);
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* .NET Framework reference assemblies (the "targeting packs" Visual Studio would install) from NuGet, so net4x projects,
|
|
53
|
+
* old-style .csproj included, build with the SDK's MSBuild. MSBuild reads TargetFrameworkRootPath from the environment.
|
|
54
|
+
* Running .NET Framework 4.8 apps needs nothing: Windows ships it.
|
|
55
|
+
*/
|
|
56
|
+
const setupFrameworkReferences = async (env, targets) => {
|
|
57
|
+
if (!targets.length)
|
|
58
|
+
return;
|
|
59
|
+
const root = tool('netfx-references');
|
|
60
|
+
for (const target of targets) {
|
|
61
|
+
const id = 'microsoft.netframework.referenceassemblies.' + target.toLowerCase();
|
|
62
|
+
const marker = path.join(root, '.installed-' + target.toLowerCase());
|
|
63
|
+
if (exists(marker))
|
|
64
|
+
continue;
|
|
65
|
+
const staging = tool('netfx-references-' + target.toLowerCase());
|
|
66
|
+
try {
|
|
67
|
+
const { versions } = await fetchJson('https://api.nuget.org/v3-flatcontainer/' + id + '/index.json');
|
|
68
|
+
const version = versions.filter((v) => /^\d+(\.\d+)*$/.test(v)).pop();
|
|
69
|
+
if (!version)
|
|
70
|
+
throw new Error('no stable version of ' + id);
|
|
71
|
+
log('Downloading the .NET Framework ' + target + ' reference assemblies (~30 MB once) ...');
|
|
72
|
+
await installArchive('https://api.nuget.org/v3-flatcontainer/' + id + '/' + version + '/' + id + '.' + version + '.nupkg', staging, { fileName: id + '.zip' });
|
|
73
|
+
fs.cpSync(path.join(staging, 'build', '.NETFramework'), path.join(root, '.NETFramework'), { recursive: true });
|
|
74
|
+
fs.writeFileSync(marker, version);
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
warn('.NET Framework ' + target + ' reference assemblies could not be installed (projects targeting it will not build): ' + errMsg(e));
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
safeRemove(staging, dataDir);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (!exists(path.join(root, '.NETFramework')))
|
|
84
|
+
return;
|
|
85
|
+
env.vars.TargetFrameworkRootPath = root + path.sep;
|
|
86
|
+
env.summary.push('.NET Framework targeting: ' + fs.readdirSync(path.join(root, '.NETFramework')).join(', '));
|
|
87
|
+
};
|
|
88
|
+
/** .NET Framework 3.5 is a Windows feature the sandbox image lacks; DISM fetches it from Windows Update on every boot. */
|
|
89
|
+
const setupNetFx35 = async (env) => {
|
|
90
|
+
if (execOutput(SYSTEM.reg(), ['query', 'HKLM\\SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v3.5', '/v', 'Install']).includes('0x1')) {
|
|
91
|
+
env.summary.push('.NET Framework 3.5: on');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
log('Enabling .NET Framework 3.5 (downloads from Windows Update, a few minutes) ...');
|
|
95
|
+
try {
|
|
96
|
+
await execAsync(SYSTEM.dism(), ['/Online', '/Enable-Feature', '/FeatureName:NetFx3', '/All', '/NoRestart', '/Quiet'], { stdio: 'ignore' });
|
|
97
|
+
env.summary.push('.NET Framework 3.5: on');
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
warn('.NET Framework 3.5 could not be enabled: ' + errMsg(e));
|
|
101
|
+
env.summary.push('.NET Framework 3.5: FAILED');
|
|
102
|
+
}
|
|
103
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Toolchains } from '../../../core/types.js';
|
|
2
|
+
import { type ToolEnv } from '../common.js';
|
|
3
|
+
/**
|
|
4
|
+
* Mesa 3D for Windows (llvmpipe): OpenGL 4.5 on the CPU when the sandbox has no GPU. Windows only offers OpenGL 1.1 in
|
|
5
|
+
* software, and opengl32.dll cannot be replaced system-wide, so programs pick Mesa up from their own folder.
|
|
6
|
+
*/
|
|
7
|
+
export declare const setupMesa: (env: ToolEnv, config: Toolchains["mesa"]) => Promise<void>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Mesa for running OpenGL and Vulkan programs without a GPU (llvmpipe, lavapipe).
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { installArchive } from '../../../shared/download.js';
|
|
4
|
+
import { SYSTEM } from '../../../shared/system.js';
|
|
5
|
+
import { dryRun, execAsync, exists, log } from '../../env.js';
|
|
6
|
+
import { arch, installFromGithub, tool } from '../common.js';
|
|
7
|
+
/**
|
|
8
|
+
* Mesa 3D for Windows (llvmpipe): OpenGL 4.5 on the CPU when the sandbox has no GPU. Windows only offers OpenGL 1.1 in
|
|
9
|
+
* software, and opengl32.dll cannot be replaced system-wide, so programs pick Mesa up from their own folder.
|
|
10
|
+
*/
|
|
11
|
+
export const setupMesa = async (env, config) => {
|
|
12
|
+
if (dryRun) {
|
|
13
|
+
log('(dry run) would provision Mesa 3D');
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const root = tool('mesa');
|
|
17
|
+
const dir = path.join(root, arch === 'arm64' ? 'arm64' : 'x64');
|
|
18
|
+
if (!exists(path.join(dir, 'opengl32.dll'))) {
|
|
19
|
+
await installFromGithub('pal1000/mesa-dist-win', /^mesa3d-[\d.]+-release-msvc\.7z$/, { dest: root, what: 'Mesa 3D for software OpenGL (~100 MB once)', locate: 'opengl32.dll', up: 1 });
|
|
20
|
+
}
|
|
21
|
+
Object.assign(env.vars, { MESA_DIR: dir, GALLIUM_DRIVER: 'llvmpipe' });
|
|
22
|
+
env.summary.push('mesa (software OpenGL 4.5): copy opengl32.dll + libgallium_wgl.dll from %MESA_DIR% next to a game\'s .exe');
|
|
23
|
+
if (config.vulkan)
|
|
24
|
+
await setupLavapipe(env, dir);
|
|
25
|
+
};
|
|
26
|
+
const VULKAN_RUNTIME = 'https://sdk.lunarg.com/sdk/download/latest/windows/vulkan-runtime-components.zip';
|
|
27
|
+
/**
|
|
28
|
+
* Software Vulkan: Mesa's lavapipe plus the Khronos loader (vulkan-1.dll, from LunarG's runtime components; Windows
|
|
29
|
+
* Sandbox has no Vulkan loader). The loader ignores VK_DRIVER_FILES in elevated processes, and everything in the
|
|
30
|
+
* sandbox runs elevated, so lavapipe is registered where the loader looks (HKLM; the registry is new every boot).
|
|
31
|
+
*/
|
|
32
|
+
const setupLavapipe = async (env, mesaDir) => {
|
|
33
|
+
const icd = path.join(mesaDir, arch === 'arm64' ? 'lvp_icd.aarch64.json' : 'lvp_icd.x86_64.json');
|
|
34
|
+
if (!exists(icd))
|
|
35
|
+
throw new Error('this Mesa build has no lavapipe (' + path.basename(icd) + ')');
|
|
36
|
+
const dir = tool('vulkan');
|
|
37
|
+
if (!exists(path.join(dir, 'vulkan-1.dll'))) {
|
|
38
|
+
log('Downloading the Vulkan loader (~10 MB once) ...');
|
|
39
|
+
// LunarG publishes no checksum for this zip; it comes over HTTPS from LunarG, the Khronos SDK's maintainer.
|
|
40
|
+
// The zip has x86 and x64 folders: only the 64-bit loader (and vulkaninfo) is extracted.
|
|
41
|
+
await installArchive(VULKAN_RUNTIME, dir, { locate: 'vulkan-1.dll', fileName: 'vulkan-runtime.zip', members: ['*/x64/*'] });
|
|
42
|
+
}
|
|
43
|
+
await execAsync(SYSTEM.reg(), ['add', 'HKLM\\SOFTWARE\\Khronos\\Vulkan\\Drivers', '/v', icd, '/t', 'REG_DWORD', '/d', '0', '/f'], { stdio: 'ignore' });
|
|
44
|
+
env.prepend.push(dir);
|
|
45
|
+
env.summary.push('vulkan: lavapipe (software Vulkan 1.4 on the CPU), vulkaninfo on PATH; WGPU_BACKEND=vulkan for wgpu');
|
|
46
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface VcEnv {
|
|
2
|
+
vars: Record<string, string>;
|
|
3
|
+
path: string[];
|
|
4
|
+
}
|
|
5
|
+
/** Variables vcvars64.bat would set for this tree (host x64, target x64). */
|
|
6
|
+
export declare const vcvars: (root: string, msvc: string, sdk: string) => VcEnv;
|
|
7
|
+
/** vcvars64.bat / vcvarsall.bat in the usual place, for scripts that `call` them. */
|
|
8
|
+
export declare const writeVcvars: (env: VcEnv) => void;
|
|
9
|
+
/** vswhere.exe where Visual Studio's installer would put it, answering with this tree. */
|
|
10
|
+
export declare const installVswhere: (msvc: string) => void;
|
|
11
|
+
/**
|
|
12
|
+
* cl.exe and link.exe start vctip.exe (Visual Studio's telemetry uploader), which lingers after the build and keeps the
|
|
13
|
+
* folder locked, so `sandbox repair` / `data clean` could not delete tools\msvc. Neither VSCMD_SKIP_SENDTELEMETRY nor
|
|
14
|
+
* the TurnOffSwitch registry value stops it; without the file the compiler simply skips it. Nothing else needs it.
|
|
15
|
+
*/
|
|
16
|
+
export declare const removeTelemetry: (msvc: string) => void;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// What makes the unpacked tree behave like a Build Tools install: the vcvars environment, vcvars64.bat, vswhere.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { errMsg, mkdirp, warn } from '../../../env.js';
|
|
5
|
+
import { ensureCompiled } from '../../../helpers.js';
|
|
6
|
+
import { hostBin, ROOT } from './tree.js';
|
|
7
|
+
/** Where build tools look for vswhere (local to the sandbox, so it is written each boot). */
|
|
8
|
+
const VSWHERE_DIR = path.join(process.env['ProgramFiles(x86)'] ?? 'C:\\Program Files (x86)', 'Microsoft Visual Studio', 'Installer');
|
|
9
|
+
/** Variables vcvars64.bat would set for this tree (host x64, target x64). */
|
|
10
|
+
export const vcvars = (root, msvc, sdk) => {
|
|
11
|
+
const vc = path.join(root, 'VC', 'Tools', 'MSVC', msvc);
|
|
12
|
+
const kits = path.join(root, 'Windows Kits', '10');
|
|
13
|
+
const inc = (...p) => path.join(kits, 'Include', sdk, ...p);
|
|
14
|
+
const lib = (...p) => path.join(kits, 'Lib', sdk, ...p);
|
|
15
|
+
return {
|
|
16
|
+
vars: {
|
|
17
|
+
VSINSTALLDIR: root + '\\', VCINSTALLDIR: path.join(root, 'VC') + '\\', VCToolsInstallDir: vc + '\\', VCToolsVersion: msvc, VisualStudioVersion: '17.0',
|
|
18
|
+
WindowsSdkDir: kits + '\\', WindowsSDKVersion: sdk + '\\', WindowsSDKLibVersion: sdk + '\\', UniversalCRTSdkDir: kits + '\\', UCRTVersion: sdk,
|
|
19
|
+
VSCMD_ARG_HOST_ARCH: 'x64', VSCMD_ARG_TGT_ARCH: 'x64', Platform: 'x64',
|
|
20
|
+
INCLUDE: [path.join(vc, 'include'), inc('ucrt'), inc('shared'), inc('um'), inc('winrt'), inc('cppwinrt')].join(';'),
|
|
21
|
+
LIB: [path.join(vc, 'lib', 'x64'), lib('ucrt', 'x64'), lib('um', 'x64')].join(';'),
|
|
22
|
+
LIBPATH: [path.join(vc, 'lib', 'x64'), path.join(vc, 'lib', 'x86', 'store', 'references')].join(';'),
|
|
23
|
+
},
|
|
24
|
+
path: [path.join(vc, 'bin', 'Hostx64', 'x64'), path.join(kits, 'bin', sdk, 'x64')],
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
/** vcvars64.bat / vcvarsall.bat in the usual place, for scripts that `call` them. */
|
|
28
|
+
export const writeVcvars = (env) => {
|
|
29
|
+
const dir = mkdirp(path.join(ROOT, 'VC', 'Auxiliary', 'Build'));
|
|
30
|
+
const lines = [
|
|
31
|
+
'@echo off', 'rem Written by Windows Development Sandbox for its portable MSVC (host x64, target x64).',
|
|
32
|
+
...Object.entries(env.vars).map(([k, v]) => 'set "' + k + '=' + v + '"'),
|
|
33
|
+
'set "PATH=' + env.path.join(';') + ';%PATH%"', 'exit /b 0',
|
|
34
|
+
];
|
|
35
|
+
fs.writeFileSync(path.join(dir, 'vcvars64.bat'), lines.join('\r\n') + '\r\n');
|
|
36
|
+
fs.writeFileSync(path.join(dir, 'vcvarsall.bat'), ['@echo off', 'rem Only x64 (amd64) is installed.', 'call "%~dp0vcvars64.bat"', 'exit /b %errorlevel%'].join('\r\n') + '\r\n');
|
|
37
|
+
};
|
|
38
|
+
/** vswhere.exe where Visual Studio's installer would put it, answering with this tree. */
|
|
39
|
+
export const installVswhere = (msvc) => {
|
|
40
|
+
mkdirp(VSWHERE_DIR);
|
|
41
|
+
fs.copyFileSync(ensureCompiled('vswhere'), path.join(VSWHERE_DIR, 'vswhere.exe'));
|
|
42
|
+
const [major, minor] = msvc.split('.').slice(2);
|
|
43
|
+
fs.writeFileSync(path.join(VSWHERE_DIR, 'vswhere.json'), JSON.stringify({ installationPath: ROOT, installationVersion: '17.' + (minor ?? '0') + '.0.' + (major ?? '0') }));
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* cl.exe and link.exe start vctip.exe (Visual Studio's telemetry uploader), which lingers after the build and keeps the
|
|
47
|
+
* folder locked, so `sandbox repair` / `data clean` could not delete tools\msvc. Neither VSCMD_SKIP_SENDTELEMETRY nor
|
|
48
|
+
* the TurnOffSwitch registry value stops it; without the file the compiler simply skips it. Nothing else needs it.
|
|
49
|
+
*/
|
|
50
|
+
export const removeTelemetry = (msvc) => {
|
|
51
|
+
try {
|
|
52
|
+
fs.rmSync(hostBin(msvc, 'vctip.exe'), { force: true });
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
warn('Could not remove MSVC\'s telemetry helper (vctip.exe); it may keep tools\\msvc locked: ' + errMsg(e));
|
|
56
|
+
}
|
|
57
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Toolchains } from '../../../../core/types.js';
|
|
2
|
+
import type { ToolEnv } from '../../common.js';
|
|
3
|
+
import { type VcEnv } from './environment.js';
|
|
4
|
+
/** The vcvars environment of an installed tree (for steps that run before the agent's environment exists). */
|
|
5
|
+
export declare const installedMsvcEnv: () => VcEnv | null;
|
|
6
|
+
export declare const setupMsvc: (env: ToolEnv, config: Toolchains["msvc"]) => Promise<void>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// MSVC (cl, link) and the Windows SDK without an installer (packages.ts), laid out like a Build Tools install with
|
|
2
|
+
// vcvars64.bat and a vswhere that points at it (environment.ts), so CMake, cargo (msvc host), node-gyp and vcvars-based
|
|
3
|
+
// scripts work. Use is governed by the Visual Studio licence terms: it only installs with toolchains.msvc.acceptLicense.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { writeJsonAtomic } from '../../../../shared/rundir.js';
|
|
7
|
+
import { safeRemove } from '../../../../shared/safe-remove.js';
|
|
8
|
+
import { dryRun, exists, log } from '../../../env.js';
|
|
9
|
+
import { installVswhere, removeTelemetry, vcvars, writeVcvars } from './environment.js';
|
|
10
|
+
import { installPackages } from './packages.js';
|
|
11
|
+
import { hasCompiler, MARKER, readInstalled, ROOT } from './tree.js';
|
|
12
|
+
/** The vcvars environment of an installed tree (for steps that run before the agent's environment exists). */
|
|
13
|
+
export const installedMsvcEnv = () => {
|
|
14
|
+
const done = readInstalled();
|
|
15
|
+
return done && hasCompiler(done.msvc) ? vcvars(ROOT, done.msvc, done.sdk) : null;
|
|
16
|
+
};
|
|
17
|
+
/** Empties tools\msvc for a fresh install, keeping the manifest and the verified downloads of an earlier attempt. */
|
|
18
|
+
const clearTree = () => {
|
|
19
|
+
if (!exists(ROOT))
|
|
20
|
+
return;
|
|
21
|
+
for (const name of fs.readdirSync(ROOT).filter((n) => n !== '.manifest.json' && n !== '.download'))
|
|
22
|
+
safeRemove(path.join(ROOT, name), ROOT);
|
|
23
|
+
};
|
|
24
|
+
export const setupMsvc = async (env, config) => {
|
|
25
|
+
if (!config.acceptLicense) {
|
|
26
|
+
throw new Error('MSVC is installed only with toolchains.msvc.acceptLicense = true: its use is governed by the Visual Studio licence terms (https://visualstudio.microsoft.com/license-terms/), which need a Visual Studio licence');
|
|
27
|
+
}
|
|
28
|
+
if (dryRun) {
|
|
29
|
+
log('(dry run) would provision MSVC ' + config.msvcVersion + ' and Windows SDK ' + config.sdkVersion);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const want = config.msvcVersion + '/' + config.sdkVersion;
|
|
33
|
+
let done = readInstalled();
|
|
34
|
+
if (!done || done.want !== want || !hasCompiler(done.msvc)) {
|
|
35
|
+
log('Installing MSVC and the Windows SDK (~1-2 GB, once; licence accepted in toolchains.msvc.acceptLicense) ...');
|
|
36
|
+
clearTree();
|
|
37
|
+
done = { ...(await installPackages(config)), want };
|
|
38
|
+
// Written last and atomically: without it (an interrupted install) the next boot starts over.
|
|
39
|
+
writeJsonAtomic(MARKER, done);
|
|
40
|
+
}
|
|
41
|
+
removeTelemetry(done.msvc);
|
|
42
|
+
const vc = vcvars(ROOT, done.msvc, done.sdk);
|
|
43
|
+
writeVcvars(vc);
|
|
44
|
+
installVswhere(done.msvc);
|
|
45
|
+
Object.assign(env.vars, vc.vars);
|
|
46
|
+
env.prepend.push(...vc.path);
|
|
47
|
+
env.summary.push('msvc: ' + done.msvc + ', Windows SDK ' + done.sdk + ' (cl, link; vcvars64.bat and vswhere work)');
|
|
48
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Toolchains } from '../../../../core/types.js';
|
|
2
|
+
/** Downloads and unpacks MSVC and the Windows SDK into tools\msvc; resolves with the toolset and SDK folder names. */
|
|
3
|
+
export declare const installPackages: (config: Toolchains["msvc"]) => Promise<{
|
|
4
|
+
msvc: string;
|
|
5
|
+
sdk: string;
|
|
6
|
+
}>;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// Downloads the MSVC compiler and the Windows SDK from Microsoft's Visual Studio channel manifest, the way mmozeiko's
|
|
2
|
+
// portable-msvc.py does: each payload is checked against the manifest's sha256, the compiler is unpacked from its
|
|
3
|
+
// .vsix files, the SDK from its .msi files with `msiexec /a`.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { download, extractArchive, fetchJson } from '../../../../shared/download.js';
|
|
7
|
+
import { safeRemove } from '../../../../shared/safe-remove.js';
|
|
8
|
+
import { system32 } from '../../../../shared/system.js';
|
|
9
|
+
import { execAsync, exists, log, mkdirp } from '../../../env.js';
|
|
10
|
+
import { msiCabs, pickMsvcVersion, pickWindowsSdk } from '../../versions.js';
|
|
11
|
+
import { hostBin, ROOT } from './tree.js';
|
|
12
|
+
const CHANNEL = 'https://aka.ms/vs/17/release/channel';
|
|
13
|
+
const MANIFEST_DAY_MS = 24 * 60 * 60 * 1000;
|
|
14
|
+
/** The compiler's packages (host x64, target x64): tools, resources, CRT headers/libs, ASAN/PGO headers. */
|
|
15
|
+
const MSVC_PARTS = ['tools.hostx64.targetx64.base', 'tools.hostx64.targetx64.res.base', 'crt.headers.base', 'crt.x64.desktop.base', 'crt.x64.store.base', 'crt.source.base', 'asan.headers.base', 'pgo.headers.base'];
|
|
16
|
+
/** Without these the compiler is unusable; the other parts are taken when the manifest has them. */
|
|
17
|
+
const REQUIRED_PARTS = ['tools', 'crt.headers', 'crt.x64.desktop'];
|
|
18
|
+
/** The SDK's .msi files (Desktop + UCRT + Store headers/libs/tools for x64), as in portable-msvc.py. */
|
|
19
|
+
const SDK_MSIS = [
|
|
20
|
+
'Windows SDK for Windows Store Apps Tools-x86_en-us.msi', 'Windows SDK for Windows Store Apps Headers-x86_en-us.msi',
|
|
21
|
+
'Windows SDK for Windows Store Apps Headers OnecoreUap-x86_en-us.msi', 'Windows SDK for Windows Store Apps Libs-x86_en-us.msi',
|
|
22
|
+
'Universal CRT Headers Libraries and Sources-x86_en-us.msi', 'Windows SDK Desktop Headers x86-x86_en-us.msi',
|
|
23
|
+
'Windows SDK Desktop Headers x64-x86_en-us.msi', 'Windows SDK Desktop Libs x64-x86_en-us.msi', 'Windows SDK Desktop Tools x64-x86_en-us.msi',
|
|
24
|
+
];
|
|
25
|
+
/** The VS manifest (~18 MB), cached for a day. */
|
|
26
|
+
const manifest = async () => {
|
|
27
|
+
const cached = path.join(ROOT, '.manifest.json');
|
|
28
|
+
if (exists(cached) && Date.now() - fs.statSync(cached).mtimeMs < MANIFEST_DAY_MS) {
|
|
29
|
+
// A download cut short leaves unreadable JSON: fetch it again instead of failing until the day is over.
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(fs.readFileSync(cached, 'utf8'));
|
|
32
|
+
}
|
|
33
|
+
catch { /* downloaded again below */ }
|
|
34
|
+
}
|
|
35
|
+
const channel = await fetchJson(CHANNEL);
|
|
36
|
+
const item = channel.channelItems.find((i) => i.id === 'Microsoft.VisualStudio.Manifests.VisualStudio')?.payloads?.[0];
|
|
37
|
+
if (!item)
|
|
38
|
+
throw new Error('the Visual Studio channel has no package manifest');
|
|
39
|
+
mkdirp(ROOT);
|
|
40
|
+
// The channel's sha256 for the manifest is not the file's hash (portable-msvc.py does not check it either); the
|
|
41
|
+
// manifest comes over HTTPS from Microsoft, and every package payload it lists IS checked against its own sha256.
|
|
42
|
+
await download(item.url, cached);
|
|
43
|
+
return JSON.parse(fs.readFileSync(cached, 'utf8'));
|
|
44
|
+
};
|
|
45
|
+
/** .vsix contents are percent-encoded file names under Contents/; unpack them into the root, names decoded. */
|
|
46
|
+
const unpackVsix = async (vsix, work) => {
|
|
47
|
+
const out = mkdirp(path.join(work, 'vsix-' + path.basename(vsix)));
|
|
48
|
+
await extractArchive(vsix, out, ['Contents']);
|
|
49
|
+
const decode = (dir) => {
|
|
50
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
51
|
+
const from = path.join(dir, entry.name);
|
|
52
|
+
let name = entry.name;
|
|
53
|
+
try {
|
|
54
|
+
if (name.includes('%'))
|
|
55
|
+
name = decodeURIComponent(name);
|
|
56
|
+
}
|
|
57
|
+
catch { /* not percent-encoded after all */ }
|
|
58
|
+
const to = path.join(dir, name);
|
|
59
|
+
if (to !== from)
|
|
60
|
+
fs.renameSync(from, to);
|
|
61
|
+
if (entry.isDirectory())
|
|
62
|
+
decode(to);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
decode(out);
|
|
66
|
+
fs.cpSync(path.join(out, 'Contents'), ROOT, { recursive: true, force: true });
|
|
67
|
+
safeRemove(out, work);
|
|
68
|
+
};
|
|
69
|
+
/** An administrative install only unpacks files; its verbose log explains a failure (e.g. a path over 260 characters). */
|
|
70
|
+
const unpackMsi = async (msi) => {
|
|
71
|
+
const logFile = msi + '.log';
|
|
72
|
+
try {
|
|
73
|
+
await execAsync(system32('msiexec.exe'), ['/a', msi, '/quiet', '/qn', 'TARGETDIR=' + ROOT, '/l*v', logFile], { stdio: 'ignore' });
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
const lines = exists(logFile) ? fs.readFileSync(logFile, 'utf16le').split(/\r?\n/).filter((l) => /error|return value 3|failed/i.test(l)).slice(-4).join(' | ') : '';
|
|
77
|
+
throw new Error(path.basename(msi) + ' did not unpack: ' + (e instanceof Error ? e.message : String(e)) + (lines ? ' (' + lines.slice(0, 600) + ')' : ''));
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
/** The manifest's packages by id, with the lookups the installer needs. */
|
|
81
|
+
const catalogue = (m) => {
|
|
82
|
+
const byId = new Map();
|
|
83
|
+
for (const p of m.packages)
|
|
84
|
+
byId.set(p.id.toLowerCase(), [...(byId.get(p.id.toLowerCase()) ?? []), p]);
|
|
85
|
+
return {
|
|
86
|
+
ids: [...byId.keys()],
|
|
87
|
+
pick: (id) => byId.get(id.toLowerCase())?.find((p) => !p.language || p.language === 'en-US'),
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
/** Downloads (once, verified) into `work`; a verified file from an earlier attempt is reused. */
|
|
91
|
+
const payloadFetcher = (work) => async (p) => {
|
|
92
|
+
const file = path.join(work, path.basename(p.fileName.replace(/\\/g, '/')));
|
|
93
|
+
if (!exists(file))
|
|
94
|
+
await download(p.url, file, p.sha256);
|
|
95
|
+
return file;
|
|
96
|
+
};
|
|
97
|
+
/** Newest folder under VC\Tools\MSVC that holds a compiler (named after the toolset, 14.44.35207, not the package version). */
|
|
98
|
+
const unpackedToolset = () => {
|
|
99
|
+
const toolsRoot = path.join(ROOT, 'VC', 'Tools', 'MSVC');
|
|
100
|
+
return fs.readdirSync(toolsRoot).filter((d) => exists(hostBin(d, 'cl.exe'))).sort((a, b) => a.localeCompare(b, undefined, { numeric: true })).pop();
|
|
101
|
+
};
|
|
102
|
+
/** Downloads and unpacks MSVC and the Windows SDK into tools\msvc; resolves with the toolset and SDK folder names. */
|
|
103
|
+
export const installPackages = async (config) => {
|
|
104
|
+
const { ids, pick } = catalogue(await manifest());
|
|
105
|
+
const version = pickMsvcVersion(ids, config.msvcVersion);
|
|
106
|
+
if (!version)
|
|
107
|
+
throw new Error('no MSVC ' + config.msvcVersion + ' in the Visual Studio manifest');
|
|
108
|
+
const sdk = pickWindowsSdk(ids, config.sdkVersion);
|
|
109
|
+
if (!sdk)
|
|
110
|
+
throw new Error('no Windows SDK ' + config.sdkVersion + ' in the Visual Studio manifest');
|
|
111
|
+
const work = mkdirp(path.join(ROOT, '.download'));
|
|
112
|
+
const fetchPayload = payloadFetcher(work);
|
|
113
|
+
log('Downloading the MSVC ' + version + ' compiler and C/C++ libraries ...');
|
|
114
|
+
for (const part of MSVC_PARTS) {
|
|
115
|
+
const p = pick('microsoft.vc.' + version + '.' + part);
|
|
116
|
+
if (!p && REQUIRED_PARTS.some((r) => part.startsWith(r)))
|
|
117
|
+
throw new Error('MSVC ' + version + ' has no ' + part);
|
|
118
|
+
for (const payload of p?.payloads ?? [])
|
|
119
|
+
await unpackVsix(await fetchPayload(payload), work);
|
|
120
|
+
}
|
|
121
|
+
for (const payload of pick('microsoft.visualcpp.dia.sdk')?.payloads ?? [])
|
|
122
|
+
await unpackVsix(await fetchPayload(payload), work);
|
|
123
|
+
// The SDK component depends on the package that holds the .msi/.cab payloads (e.g. Win11SDK_10.0.26100).
|
|
124
|
+
const sdkPackage = pick(Object.keys(pick(sdk.componentId)?.dependencies ?? {})[0] ?? '');
|
|
125
|
+
if (!sdkPackage?.payloads)
|
|
126
|
+
throw new Error('the Windows SDK ' + sdk.build + ' package has no payloads');
|
|
127
|
+
log('Downloading the Windows SDK ' + sdk.build + ' (headers, libraries; the biggest part) ...');
|
|
128
|
+
const payload = (name) => sdkPackage.payloads.find((p) => p.fileName.toLowerCase() === ('installers\\' + name).toLowerCase());
|
|
129
|
+
const msis = [];
|
|
130
|
+
for (const name of SDK_MSIS) {
|
|
131
|
+
const p = payload(name);
|
|
132
|
+
if (!p)
|
|
133
|
+
throw new Error('the Windows SDK package has no ' + name);
|
|
134
|
+
const file = await fetchPayload(p);
|
|
135
|
+
msis.push(file);
|
|
136
|
+
for (const cab of msiCabs(fs.readFileSync(file))) {
|
|
137
|
+
const c = payload(cab);
|
|
138
|
+
if (c)
|
|
139
|
+
await fetchPayload(c);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
log('Unpacking the Windows SDK (a few minutes) ...');
|
|
143
|
+
for (const msi of msis)
|
|
144
|
+
await unpackMsi(msi);
|
|
145
|
+
// msiexec /a also copies every .msi into the target; they are not needed.
|
|
146
|
+
for (const name of fs.readdirSync(ROOT).filter((n) => n.toLowerCase().endsWith('.msi')))
|
|
147
|
+
fs.rmSync(path.join(ROOT, name), { force: true });
|
|
148
|
+
safeRemove(work, ROOT);
|
|
149
|
+
const sdkVersion = fs.readdirSync(path.join(ROOT, 'Windows Kits', '10', 'Include')).filter((d) => d.startsWith('10.0.' + sdk.build)).sort().pop();
|
|
150
|
+
if (!sdkVersion)
|
|
151
|
+
throw new Error('the Windows SDK did not unpack (no Include\\10.0.' + sdk.build + '.*)');
|
|
152
|
+
const toolset = unpackedToolset();
|
|
153
|
+
if (!toolset)
|
|
154
|
+
throw new Error('MSVC ' + version + ' did not unpack (no cl.exe under VC\\Tools\\MSVC)');
|
|
155
|
+
return { msvc: toolset, sdk: sdkVersion };
|
|
156
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const ROOT: string;
|
|
3
|
+
export declare const MARKER: string;
|
|
4
|
+
export declare const msvcTools: (msvc: string, ...parts: string[]) => string;
|
|
5
|
+
export declare const hostBin: (msvc: string, ...parts: string[]) => string;
|
|
6
|
+
export declare const hasCompiler: (msvc: string) => boolean;
|
|
7
|
+
declare const Installed: z.ZodObject<{
|
|
8
|
+
msvc: z.ZodString;
|
|
9
|
+
sdk: z.ZodString;
|
|
10
|
+
want: z.ZodString;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
export type Installed = z.infer<typeof Installed>;
|
|
13
|
+
/** What the last finished install recorded; null for a missing, unreadable or half-written marker. */
|
|
14
|
+
export declare const readInstalled: () => Installed | null;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Where the portable MSVC lives (tools\msvc) and what the last finished install recorded about it.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { exists } from '../../../env.js';
|
|
6
|
+
import { tool } from '../../common.js';
|
|
7
|
+
export const ROOT = tool('msvc');
|
|
8
|
+
export const MARKER = path.join(ROOT, '.installed.json');
|
|
9
|
+
export const msvcTools = (msvc, ...parts) => path.join(ROOT, 'VC', 'Tools', 'MSVC', msvc, ...parts);
|
|
10
|
+
export const hostBin = (msvc, ...parts) => msvcTools(msvc, 'bin', 'Hostx64', 'x64', ...parts);
|
|
11
|
+
export const hasCompiler = (msvc) => exists(hostBin(msvc, 'cl.exe'));
|
|
12
|
+
const Installed = z.object({ msvc: z.string().min(1), sdk: z.string().min(1), want: z.string() });
|
|
13
|
+
/** What the last finished install recorded; null for a missing, unreadable or half-written marker. */
|
|
14
|
+
export const readInstalled = () => {
|
|
15
|
+
try {
|
|
16
|
+
return Installed.safeParse(JSON.parse(fs.readFileSync(MARKER, 'utf8'))).data ?? null;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
};
|