windows-development-sandbox 1.0.5 → 1.4.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/LICENSE +21 -0
- package/README.md +25 -8
- package/config.example.json +55 -5
- package/dist/api.js +2 -1
- package/dist/cli/args.d.ts +23 -0
- package/dist/cli/args.js +38 -4
- package/dist/cli/colors.d.ts +24 -0
- package/dist/cli/colors.js +42 -0
- package/dist/cli/commands/accounts.js +2 -2
- 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 +2 -0
- package/dist/cli/commands/context.js +5 -1
- package/dist/cli/commands/data/clean.d.ts +15 -0
- package/dist/cli/commands/data/clean.js +106 -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 +6 -4
- package/dist/cli/commands/index.js +6 -4
- package/dist/cli/commands/launch.d.ts +26 -2
- package/dist/cli/commands/launch.js +256 -42
- package/dist/cli/commands/maintenance.d.ts +2 -0
- package/dist/cli/commands/maintenance.js +67 -5
- 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.js → repair/checks.js} +19 -181
- package/dist/cli/commands/{repair.d.ts → repair/finders.d.ts} +0 -29
- 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 +2 -1
- package/dist/cli/commands/sessions.js +189 -45
- package/dist/cli/commands/setup/checks.d.ts +13 -0
- package/dist/cli/commands/setup/checks.js +71 -0
- package/dist/cli/commands/{setup.d.ts → setup/facts.d.ts} +1 -15
- 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 +7 -1
- package/dist/cli/index.js +42 -15
- package/dist/cli/manifest.d.ts +8 -0
- package/dist/cli/manifest.js +40 -15
- package/dist/cli/menu/apps.d.ts +21 -0
- package/dist/cli/menu/apps.js +122 -0
- package/dist/cli/menu/command-form.d.ts +28 -0
- package/dist/cli/menu/command-form.js +219 -0
- package/dist/cli/menu/index.d.ts +26 -0
- package/dist/cli/menu/index.js +180 -0
- package/dist/cli/menu/settings/categories.d.ts +9 -0
- package/dist/cli/menu/settings/categories.js +28 -0
- package/dist/cli/menu/settings/config-file.d.ts +40 -0
- package/dist/cli/menu/settings/config-file.js +71 -0
- package/dist/cli/menu/settings/editors.d.ts +30 -0
- package/dist/cli/menu/settings/editors.js +305 -0
- package/dist/cli/menu/settings/index.d.ts +32 -0
- package/dist/cli/menu/settings/index.js +136 -0
- package/dist/cli/menu/settings/schema-node.d.ts +62 -0
- package/dist/cli/menu/settings/schema-node.js +104 -0
- package/dist/cli/menu/several.d.ts +20 -0
- package/dist/cli/menu/several.js +102 -0
- package/dist/cli/progress.d.ts +9 -0
- package/dist/cli/progress.js +49 -0
- package/dist/cli/tui/app.d.ts +39 -0
- package/dist/cli/tui/app.js +52 -0
- package/dist/cli/tui/dialogs.d.ts +57 -0
- package/dist/cli/tui/dialogs.js +162 -0
- package/dist/cli/tui/frame.d.ts +18 -0
- package/dist/cli/tui/frame.js +27 -0
- package/dist/cli/tui/index.d.ts +7 -0
- package/dist/cli/tui/index.js +8 -0
- package/dist/cli/tui/list-screen.d.ts +101 -0
- package/dist/cli/tui/list-screen.js +299 -0
- package/dist/cli/tui/terminal.d.ts +37 -0
- package/dist/cli/tui/terminal.js +89 -0
- package/dist/cli/tui/text.d.ts +20 -0
- package/dist/cli/tui/text.js +105 -0
- package/dist/cli/tui/theme.d.ts +45 -0
- package/dist/cli/tui/theme.js +30 -0
- package/dist/cli/validate.d.ts +17 -0
- package/dist/cli/validate.js +89 -0
- package/dist/core/config.js +12 -6
- package/dist/core/disk.d.ts +16 -0
- package/dist/core/disk.js +55 -0
- package/dist/core/local-config.d.ts +23 -0
- package/dist/core/local-config.js +79 -7
- 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/apps.d.ts +40 -0
- package/dist/core/schema/apps.js +27 -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 +659 -0
- package/dist/core/schema/config.js +75 -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 +32 -0
- package/dist/core/schema/profile.js +31 -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 +78 -0
- package/dist/core/schema/sandbox.js +64 -0
- package/dist/core/schema/toolchains.d.ts +96 -0
- package/dist/core/schema/toolchains.js +88 -0
- package/dist/core/schema.d.ts +6 -604
- package/dist/core/schema.js +8 -264
- package/dist/core/types.d.ts +35 -1
- package/dist/core/util.d.ts +9 -0
- package/dist/core/util.js +14 -3
- package/dist/core/workspace.d.ts +0 -5
- package/dist/core/workspace.js +33 -50
- package/dist/guest/apps/index.d.ts +10 -0
- package/dist/guest/apps/index.js +206 -0
- package/dist/guest/apps/resolvers.d.ts +8 -0
- package/dist/guest/apps/resolvers.js +90 -0
- package/dist/guest/audit-parse.d.ts +7 -0
- package/dist/guest/audit-parse.js +24 -0
- package/dist/guest/audit.d.ts +4 -0
- package/dist/guest/audit.js +47 -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/relays.d.ts +1 -0
- package/dist/guest/daemon/relays.js +18 -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.js +12 -141
- package/dist/guest/db.js +18 -3
- package/dist/guest/env.d.ts +3 -0
- package/dist/guest/env.js +7 -3
- package/dist/guest/guide.d.ts +6 -2
- package/dist/guest/guide.js +5 -4
- package/dist/guest/helpers.d.ts +3 -0
- package/dist/guest/helpers.js +26 -0
- package/dist/guest/main.js +31 -8
- package/dist/guest/netproxy.d.ts +1 -0
- package/dist/guest/netproxy.js +100 -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 +0 -18
- package/dist/guest/ptyhost.js +17 -65
- package/dist/guest/setup/agents.d.ts +5 -2
- package/dist/guest/setup/agents.js +22 -8
- package/dist/guest/setup/claude.d.ts +0 -4
- package/dist/guest/setup/claude.js +59 -5
- package/dist/guest/setup/environment.d.ts +13 -3
- package/dist/guest/setup/environment.js +14 -10
- package/dist/guest/setup/hooks.d.ts +9 -1
- package/dist/guest/setup/hooks.js +28 -14
- package/dist/guest/setup/network.d.ts +9 -0
- package/dist/guest/setup/network.js +97 -4
- package/dist/guest/setup/user.js +4 -0
- package/dist/guest/toolchains/catalogue.d.ts +4 -0
- package/dist/guest/toolchains/catalogue.js +2 -2
- package/dist/guest/toolchains/cli.js +25 -17
- package/dist/guest/toolchains/common.d.ts +18 -1
- package/dist/guest/toolchains/common.js +34 -11
- package/dist/guest/toolchains/databases/dynamodb.d.ts +8 -0
- package/dist/guest/toolchains/databases/dynamodb.js +52 -0
- package/dist/guest/toolchains/{databases.d.ts → databases/index.d.ts} +2 -3
- 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.js +25 -16
- package/dist/guest/toolchains/go.js +1 -1
- package/dist/guest/toolchains/index.d.ts +13 -9
- package/dist/guest/toolchains/index.js +40 -82
- package/dist/guest/toolchains/java.js +5 -5
- package/dist/guest/toolchains/leftovers.d.ts +2 -0
- package/dist/guest/toolchains/leftovers.js +26 -0
- package/dist/guest/toolchains/managers.js +12 -10
- package/dist/guest/toolchains/{binstall.js → native/binstall.js} +6 -9
- 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 +56 -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.js +26 -6
- package/dist/guest/toolchains/plan.d.ts +24 -0
- package/dist/guest/toolchains/plan.js +140 -0
- package/dist/guest/toolchains/pwsh.js +1 -1
- package/dist/guest/toolchains/python.js +43 -12
- package/dist/guest/toolchains/runtimes.d.ts +2 -0
- package/dist/guest/toolchains/runtimes.js +6 -8
- 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/vscode-profile.d.ts +30 -0
- package/dist/guest/toolchains/vscode-profile.js +69 -0
- package/dist/guest/toolchains/vscode.d.ts +17 -0
- package/dist/guest/toolchains/vscode.js +87 -1
- package/dist/guest/view.js +21 -1
- package/dist/host/accounts.js +14 -5
- 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 +70 -0
- package/dist/host/audit/collect.d.ts +34 -0
- package/dist/host/audit/collect.js +126 -0
- package/dist/host/audit/log.d.ts +46 -0
- package/dist/host/audit/log.js +143 -0
- package/dist/host/channel.d.ts +23 -22
- package/dist/host/channel.js +66 -84
- package/dist/host/claude-profile.js +7 -3
- package/dist/host/cleanup.js +2 -11
- package/dist/host/database-plan.d.ts +12 -0
- package/dist/host/database-plan.js +52 -0
- package/dist/host/generate.d.ts +10 -12
- package/dist/host/generate.js +58 -135
- 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 +54 -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/motw.d.ts +17 -0
- package/dist/host/motw.js +74 -0
- package/dist/host/pc-scan.d.ts +6 -0
- package/dist/host/pc-scan.js +37 -0
- package/dist/host/platform.d.ts +2 -0
- package/dist/host/platform.js +16 -1
- package/dist/host/port-forward.d.ts +8 -0
- package/dist/host/port-forward.js +37 -0
- package/dist/host/project-scan.d.ts +1 -17
- package/dist/host/project-scan.js +2 -86
- package/dist/host/projects.d.ts +100 -0
- package/dist/host/projects.js +147 -0
- package/dist/host/repo-guard.d.ts +25 -5
- package/dist/host/repo-guard.js +242 -23
- package/dist/host/stops.d.ts +67 -0
- package/dist/host/stops.js +126 -0
- package/dist/host/terminal.d.ts +8 -0
- package/dist/host/terminal.js +48 -1
- package/dist/host/test-guard.d.ts +6 -0
- package/dist/host/test-guard.js +15 -0
- package/dist/host/window.js +7 -3
- package/dist/host/workspace-view/index.d.ts +20 -0
- package/dist/host/workspace-view/index.js +53 -0
- package/dist/host/workspace-view/rules.d.ts +26 -0
- package/dist/host/workspace-view/rules.js +93 -0
- package/dist/host/workspace-view/view.d.ts +50 -0
- package/dist/host/workspace-view/view.js +281 -0
- package/dist/host/wsb.d.ts +37 -0
- package/dist/host/wsb.js +101 -0
- package/dist/runtime/daemon.js +11791 -2821
- package/dist/runtime/db.js +6585 -466
- package/dist/runtime/main.js +10238 -1724
- package/dist/runtime/netproxy.js +25936 -0
- package/dist/runtime/view.js +6183 -59
- 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/audit.test.d.ts +2 -0
- package/dist/selftest/audit.test.js +57 -0
- package/dist/selftest/channel.test.d.ts +2 -0
- package/dist/selftest/channel.test.js +44 -0
- package/dist/selftest/cli.test.d.ts +2 -2
- package/dist/selftest/cli.test.js +145 -27
- 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 -3
- package/dist/selftest/config.test.js +36 -72
- package/dist/selftest/data.test.d.ts +2 -2
- package/dist/selftest/data.test.js +77 -70
- package/dist/selftest/detect.test.d.ts +2 -2
- package/dist/selftest/detect.test.js +54 -38
- package/dist/selftest/fixtures.d.ts +89 -4
- package/dist/selftest/fixtures.js +43 -24
- package/dist/selftest/generate.test.d.ts +2 -0
- package/dist/selftest/generate.test.js +68 -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 +44 -7
- package/dist/selftest/harness.js +62 -21
- 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 -1
- package/dist/selftest/index.js +137 -35
- 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-network.test.d.ts +2 -0
- package/dist/selftest/live-network.test.js +63 -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 +120 -0
- package/dist/selftest/mirror.test.d.ts +2 -3
- package/dist/selftest/mirror.test.js +92 -65
- package/dist/selftest/network.test.d.ts +2 -0
- package/dist/selftest/network.test.js +40 -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 +121 -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 -2
- package/dist/selftest/setup.test.js +45 -22
- package/dist/selftest/suite.d.ts +13 -0
- package/dist/selftest/suite.js +1 -0
- package/dist/selftest/sync.test.d.ts +2 -3
- package/dist/selftest/sync.test.js +130 -78
- 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 +133 -0
- package/dist/selftest/workspace-view.test.d.ts +2 -0
- package/dist/selftest/workspace-view.test.js +89 -0
- package/dist/selftest/workspace.test.d.ts +2 -0
- package/dist/selftest/workspace.test.js +53 -0
- package/dist/shared/apps.d.ts +49 -0
- package/dist/shared/apps.js +114 -0
- package/dist/shared/download.d.ts +3 -1
- package/dist/shared/download.js +19 -4
- package/dist/shared/netpolicy.d.ts +27 -0
- package/dist/shared/netpolicy.js +51 -0
- package/dist/shared/paths.d.ts +5 -0
- package/dist/shared/paths.js +6 -0
- package/dist/shared/projects.d.ts +53 -0
- package/dist/shared/projects.js +67 -0
- package/dist/shared/pty-client.d.ts +9 -4
- package/dist/shared/pty-client.js +139 -17
- package/dist/shared/rundir.d.ts +12 -1
- package/dist/shared/rundir.js +14 -4
- package/dist/shared/safe-remove.js +11 -0
- package/dist/shared/schema.d.ts +453 -0
- package/dist/shared/schema.js +43 -2
- package/dist/shared/system.d.ts +1 -0
- package/dist/shared/system.js +1 -0
- package/dist/shared/test-guard.d.ts +4 -0
- package/dist/shared/test-guard.js +37 -0
- package/dist/sync/claude-mem.js +6 -3
- package/dist/sync/history.d.ts +29 -12
- package/dist/sync/history.js +72 -25
- package/dist/sync/watcher.d.ts +8 -0
- package/dist/sync/watcher.js +99 -8
- package/package.json +7 -6
- package/sandbox/bootstrap.ps1 +258 -258
- package/sandbox/cargoshim.cs +56 -0
- package/sandbox/cpucap.cs +33 -0
- package/sandbox/cpucap.ps1 +20 -0
- package/sandbox/logon.cmd +24 -16
- package/sandbox/memlog.ps1 +34 -0
- package/sandbox/openssl.cs +42 -0
- package/sandbox/pagefile.cs +31 -0
- package/sandbox/pagefile.ps1 +21 -0
- package/sandbox/ptyhost.cs +120 -11
- package/sandbox/trust.cmd +35 -35
- package/schemas/config.schema.json +403 -3
- package/schemas/sandbox-config.schema.json +143 -13
- package/dist/cli/commands/data.d.ts +0 -46
- package/dist/cli/commands/data.js +0 -249
- package/dist/cli/commands/setup.js +0 -152
- package/dist/cli/menu.d.ts +0 -22
- package/dist/cli/menu.js +0 -187
- package/dist/cli/settings.d.ts +0 -11
- package/dist/cli/settings.js +0 -156
- package/dist/guest/toolchains/databases.js +0 -226
- package/dist/guest/toolchains/msvc.d.ts +0 -10
- package/dist/guest/toolchains/msvc.js +0 -216
- package/dist/guest/toolchains/native.d.ts +0 -12
- package/dist/guest/toolchains/native.js +0 -220
- package/dist/guest/toolchains/scripting.d.ts +0 -10
- package/dist/guest/toolchains/scripting.js +0 -137
- package/dist/selftest/guest.test.d.ts +0 -3
- package/dist/selftest/guest.test.js +0 -61
- package/dist/selftest/helpers.test.d.ts +0 -2
- package/dist/selftest/helpers.test.js +0 -125
- package/dist/selftest/host.test.d.ts +0 -2
- package/dist/selftest/host.test.js +0 -71
- package/dist/selftest/live.test.d.ts +0 -7
- package/dist/selftest/live.test.js +0 -127
- package/dist/selftest/security.test.d.ts +0 -3
- package/dist/selftest/security.test.js +0 -143
- /package/dist/guest/toolchains/{binstall.d.ts → native/binstall.d.ts} +0 -0
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
// Compiled languages: C/C++ (MinGW-w64 GCC, flex/bison), Rust (rustup + cargo), C# (.NET SDK and .NET Framework
|
|
2
|
-
// targeting), and Mesa for running OpenGL programs without a GPU.
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { z } from 'zod';
|
|
6
|
-
import { download, fetchJson, githubAsset, installArchive } from '../../shared/download.js';
|
|
7
|
-
import { readJsonFile, writeJsonAtomic } from '../../shared/rundir.js';
|
|
8
|
-
import { SYSTEM } from '../../shared/system.js';
|
|
9
|
-
import { dryRun, errMsg, execAsync, execOutput, exists, log, mkdirp, warn, workspace } from '../env.js';
|
|
10
|
-
import { arch, installOnce, tool, versionOf } from './common.js';
|
|
11
|
-
import { CMAKE_FLOOR, cmakeTooOld } from './versions.js';
|
|
12
|
-
import { crateInstaller } from './binstall.js';
|
|
13
|
-
import { installedMsvcEnv } from './msvc.js';
|
|
14
|
-
/** MinGW-w64 GCC from WinLibs: gcc/g++/mingw32-make for the agent, and the linker for Rust's GNU host. */
|
|
15
|
-
export const setupCpp = async (env, config) => {
|
|
16
|
-
const bin = tool('mingw', 'mingw64', 'bin');
|
|
17
|
-
const gcc = path.join(bin, 'gcc.exe');
|
|
18
|
-
if (dryRun) {
|
|
19
|
-
log('(dry run) would provision MinGW-w64 GCC');
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
if (!exists(gcc)) {
|
|
23
|
-
const asset = await githubAsset('brechtsanders/winlibs_mingw', arch === 'arm64' ? /^winlibs-aarch64-.*\.zip$/ : /^winlibs-x86_64-posix-seh-gcc-.*\.zip$/, true);
|
|
24
|
-
log('Downloading ' + asset.name + ' (C/C++ compiler, ~270 MB once) ...');
|
|
25
|
-
await installArchive(asset.url, tool('mingw'), { sha256: asset.sha256, fileName: asset.name });
|
|
26
|
-
}
|
|
27
|
-
if (!exists(gcc))
|
|
28
|
-
throw new Error('gcc.exe missing after installing MinGW');
|
|
29
|
-
// Appended (not prepended) so MinGW's tools never shadow Windows or toolchain commands of the same name.
|
|
30
|
-
env.append.unshift(bin);
|
|
31
|
-
env.vars.CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER = gcc;
|
|
32
|
-
const cmake = versionOf(path.join(bin, 'cmake.exe'));
|
|
33
|
-
if (cmakeTooOld(cmake))
|
|
34
|
-
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.');
|
|
35
|
-
env.summary.push('gcc: ' + versionOf(gcc) + ' (MinGW-w64, with cmake ' + cmake + ', ninja, premake5)');
|
|
36
|
-
if (config.flexBison)
|
|
37
|
-
await setupFlexBison(env);
|
|
38
|
-
};
|
|
39
|
-
/** win_flex / win_bison (CMake's FindFLEX/FindBISON look for these names). Kept with the C/C++ tools on PATH. */
|
|
40
|
-
const setupFlexBison = async (env) => {
|
|
41
|
-
const dir = tool('winflexbison');
|
|
42
|
-
if (!exists(path.join(dir, 'win_bison.exe'))) {
|
|
43
|
-
const asset = await githubAsset('lexxmark/winflexbison', /^win_flex_bison-[\d.]+\.zip$/);
|
|
44
|
-
log('Downloading ' + asset.name + ' ...');
|
|
45
|
-
await installArchive(asset.url, dir, { sha256: asset.sha256, locate: 'win_bison.exe', fileName: asset.name });
|
|
46
|
-
}
|
|
47
|
-
env.append.push(dir);
|
|
48
|
-
env.summary.push('win_flex/win_bison: ' + versionOf(path.join(dir, 'win_bison.exe')));
|
|
49
|
-
};
|
|
50
|
-
/**
|
|
51
|
-
* Mesa 3D for Windows (llvmpipe): OpenGL 4.5 on the CPU when the sandbox has no GPU. Windows only offers OpenGL 1.1 in
|
|
52
|
-
* software, and opengl32.dll cannot be replaced system-wide, so programs pick Mesa up from their own folder.
|
|
53
|
-
*/
|
|
54
|
-
export const setupMesa = async (env, config) => {
|
|
55
|
-
if (dryRun) {
|
|
56
|
-
log('(dry run) would provision Mesa 3D');
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
const root = tool('mesa');
|
|
60
|
-
const dir = path.join(root, arch === 'arm64' ? 'arm64' : 'x64');
|
|
61
|
-
if (!exists(path.join(dir, 'opengl32.dll'))) {
|
|
62
|
-
const asset = await githubAsset('pal1000/mesa-dist-win', /^mesa3d-[\d.]+-release-msvc\.7z$/);
|
|
63
|
-
log('Downloading ' + asset.name + ' (software OpenGL) ...');
|
|
64
|
-
await installArchive(asset.url, root, { sha256: asset.sha256, locate: 'opengl32.dll', up: 1, fileName: asset.name });
|
|
65
|
-
}
|
|
66
|
-
Object.assign(env.vars, { MESA_DIR: dir, GALLIUM_DRIVER: 'llvmpipe' });
|
|
67
|
-
env.summary.push('mesa (software OpenGL 4.5): copy opengl32.dll + libgallium_wgl.dll from %MESA_DIR% next to a game\'s .exe');
|
|
68
|
-
if (config.vulkan)
|
|
69
|
-
await setupLavapipe(env, dir);
|
|
70
|
-
};
|
|
71
|
-
const VULKAN_RUNTIME = 'https://sdk.lunarg.com/sdk/download/latest/windows/vulkan-runtime-components.zip';
|
|
72
|
-
/**
|
|
73
|
-
* Software Vulkan: Mesa's lavapipe plus the Khronos loader (vulkan-1.dll, from LunarG's runtime components; Windows
|
|
74
|
-
* Sandbox has no Vulkan loader). The loader ignores VK_DRIVER_FILES in elevated processes, and everything in the
|
|
75
|
-
* sandbox runs elevated, so lavapipe is registered where the loader looks (HKLM; the registry is new every boot).
|
|
76
|
-
*/
|
|
77
|
-
const setupLavapipe = async (env, mesaDir) => {
|
|
78
|
-
const icd = path.join(mesaDir, arch === 'arm64' ? 'lvp_icd.aarch64.json' : 'lvp_icd.x86_64.json');
|
|
79
|
-
if (!exists(icd))
|
|
80
|
-
throw new Error('this Mesa build has no lavapipe (' + path.basename(icd) + ')');
|
|
81
|
-
const dir = tool('vulkan');
|
|
82
|
-
if (!exists(path.join(dir, 'vulkan-1.dll'))) {
|
|
83
|
-
log('Downloading the Vulkan loader (LunarG runtime components) ...');
|
|
84
|
-
// LunarG publishes no checksum for this zip; it comes over HTTPS from LunarG, the Khronos SDK's maintainer.
|
|
85
|
-
// The zip has x86 and x64 folders: only the 64-bit loader (and vulkaninfo) is extracted.
|
|
86
|
-
await installArchive(VULKAN_RUNTIME, dir, { locate: 'vulkan-1.dll', fileName: 'vulkan-runtime.zip', members: ['*/x64/*'] });
|
|
87
|
-
}
|
|
88
|
-
await execAsync(SYSTEM.reg(), ['add', 'HKLM\\SOFTWARE\\Khronos\\Vulkan\\Drivers', '/v', icd, '/t', 'REG_DWORD', '/d', '0', '/f'], { stdio: 'ignore' });
|
|
89
|
-
env.prepend.push(dir);
|
|
90
|
-
env.summary.push('vulkan: lavapipe (software Vulkan 1.4 on the CPU), vulkaninfo on PATH; WGPU_BACKEND=vulkan for wgpu');
|
|
91
|
-
};
|
|
92
|
-
export const setupRust = async (env, config, refresh) => {
|
|
93
|
-
const RUSTUP_HOME = tool('rust', 'rustup');
|
|
94
|
-
const CARGO_HOME = tool('rust', 'cargo');
|
|
95
|
-
const rustEnv = { env: { ...process.env, RUSTUP_HOME, CARGO_HOME } };
|
|
96
|
-
const rustup = path.join(CARGO_HOME, 'bin', 'rustup.exe');
|
|
97
|
-
if (dryRun) {
|
|
98
|
-
log('(dry run) would provision Rust ' + config.toolchain + ' (' + config.host + ')');
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
if (!exists(rustup)) {
|
|
102
|
-
const init = tool('rust', 'rustup-init.exe');
|
|
103
|
-
log('Downloading rustup ...');
|
|
104
|
-
await download('https://static.rust-lang.org/rustup/dist/' + (arch === 'arm64' ? 'aarch64' : 'x86_64') + '-pc-windows-msvc/rustup-init.exe', init);
|
|
105
|
-
await execAsync(init, ['-y', '--no-modify-path', '--profile', 'default', '--default-host', config.host, '--default-toolchain', config.toolchain], rustEnv);
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
// The host can change (GNU <-> msvc with toolchains.msvc): install the toolchain for it; a no-op when it is there.
|
|
109
|
-
await execAsync(rustup, ['set', 'default-host', config.host], { ...rustEnv, stdio: 'ignore' });
|
|
110
|
-
await execAsync(rustup, ['toolchain', 'install', config.toolchain, '--profile', 'default', '--no-self-update'], { ...rustEnv, stdio: 'ignore' });
|
|
111
|
-
await execAsync(rustup, ['default', config.toolchain], { ...rustEnv, stdio: 'ignore' });
|
|
112
|
-
if (refresh)
|
|
113
|
-
await execAsync(rustup, ['update'], rustEnv);
|
|
114
|
-
}
|
|
115
|
-
Object.assign(env.vars, { RUSTUP_HOME, CARGO_HOME });
|
|
116
|
-
env.prepend.push(path.join(CARGO_HOME, 'bin'));
|
|
117
|
-
env.summary.push('rust: ' + versionOf(path.join(CARGO_HOME, 'bin', 'rustc.exe'), ['--version'], rustEnv) + ' [' + config.host + ']');
|
|
118
|
-
const toolchainKey = 'rustup@' + config.toolchain;
|
|
119
|
-
await installOnce(env, toolchainKey + ' components', config.components, (c) => execAsync(rustup, ['component', 'add', c], rustEnv));
|
|
120
|
-
await installOnce(env, toolchainKey + ' targets', config.targets, (t) => execAsync(rustup, ['target', 'add', t], rustEnv));
|
|
121
|
-
// cargo install links with MSVC's link.exe (msvc host) or MinGW's gcc (GNU host). Those steps ran first, but their
|
|
122
|
-
// PATH only reaches the agent at the end, so it is put together here.
|
|
123
|
-
const msvc = config.host.endsWith('-msvc') ? installedMsvcEnv() : null;
|
|
124
|
-
const mingw = tool('mingw', 'mingw64', 'bin');
|
|
125
|
-
const cargoEnv = msvc
|
|
126
|
-
? { env: { ...rustEnv.env, ...msvc.vars, PATH: [path.join(CARGO_HOME, 'bin'), ...msvc.path, process.env.PATH ?? ''].join(path.delimiter) } }
|
|
127
|
-
: { env: { ...rustEnv.env, CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER: path.join(mingw, 'gcc.exe'), PATH: [path.join(CARGO_HOME, 'bin'), mingw, process.env.PATH ?? ''].join(path.delimiter) } };
|
|
128
|
-
// Prebuilt through cargo-binstall where the crate publishes a Windows build, else compiled (same "cargo" keys as before).
|
|
129
|
-
await installOnce(env, 'cargo', config.crates, crateInstaller(CARGO_HOME, cargoEnv));
|
|
130
|
-
};
|
|
131
|
-
/** tools\dotnet\.agent-sandbox-installed.json: which install keys (channel:8.0, global.json:...) already ran. */
|
|
132
|
-
const DotnetMarker = z.record(z.string(), z.boolean());
|
|
133
|
-
/** .NET SDK channels from config plus whatever the workspace's global.json pins. Installs are remembered in a marker. */
|
|
134
|
-
export const setupDotnet = async (env, config, refresh) => {
|
|
135
|
-
const root = tool('dotnet');
|
|
136
|
-
const marker = path.join(root, '.agent-sandbox-installed.json');
|
|
137
|
-
const script = tool('dotnet-install.ps1');
|
|
138
|
-
if (dryRun) {
|
|
139
|
-
log('(dry run) would provision .NET SDK channels ' + config.channels.join(', '));
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
// A corrupt or hand-edited marker just means "nothing recorded yet" (the installs are idempotent).
|
|
143
|
-
const recorded = DotnetMarker.safeParse(exists(marker) && !refresh ? readJsonFile(marker) : {});
|
|
144
|
-
const done = recorded.success ? recorded.data : {};
|
|
145
|
-
const globalJson = path.join(workspace, 'global.json');
|
|
146
|
-
const installs = [
|
|
147
|
-
...config.channels.map((c) => ['channel:' + c, ['-Channel', c]]),
|
|
148
|
-
...(exists(globalJson) ? [['global.json:' + fs.readFileSync(globalJson, 'utf8').replace(/\s+/g, ''), ['-JSonFile', globalJson]]] : []),
|
|
149
|
-
];
|
|
150
|
-
for (const [key, args] of installs.filter(([k]) => !done[k])) {
|
|
151
|
-
if (!exists(script) || refresh)
|
|
152
|
-
await download('https://dot.net/v1/dotnet-install.ps1', script);
|
|
153
|
-
log('Installing .NET SDK (' + key + ') ...');
|
|
154
|
-
await execAsync(SYSTEM.powershell(), ['-NoLogo', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', script, ...args, '-InstallDir', root, '-NoPath']);
|
|
155
|
-
done[key] = true;
|
|
156
|
-
writeJsonAtomic(marker, done);
|
|
157
|
-
}
|
|
158
|
-
const home = mkdirp(tool('dotnet-home'));
|
|
159
|
-
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') });
|
|
160
|
-
env.prepend.push(root, path.join(home, '.dotnet', 'tools'));
|
|
161
|
-
env.summary.push('dotnet: ' + execOutput(path.join(root, 'dotnet.exe'), ['--list-sdks']).split(/\r?\n/).map((l) => l.split(' ')[0]).filter(Boolean).join(', '));
|
|
162
|
-
const dotnet = path.join(root, 'dotnet.exe');
|
|
163
|
-
const dotnetEnv = { env: { ...process.env, ...env.vars, PATH: root + path.delimiter + (process.env.PATH ?? '') } };
|
|
164
|
-
await installOnce(env, 'dotnet workload', config.workloads, (w) => execAsync(dotnet, ['workload', 'install', w, '--skip-manifest-update'], dotnetEnv));
|
|
165
|
-
await installOnce(env, 'dotnet tool', config.tools, (t) => execAsync(dotnet, ['tool', 'install', '--global', t], dotnetEnv));
|
|
166
|
-
await setupFrameworkReferences(env, config.frameworkTargets);
|
|
167
|
-
if (config.netfx35)
|
|
168
|
-
await setupNetFx35(env);
|
|
169
|
-
};
|
|
170
|
-
/**
|
|
171
|
-
* .NET Framework reference assemblies (the "targeting packs" Visual Studio would install) from NuGet, so net4x projects,
|
|
172
|
-
* old-style .csproj included, build with the SDK's MSBuild. MSBuild reads TargetFrameworkRootPath from the environment.
|
|
173
|
-
* Running .NET Framework 4.8 apps needs nothing: Windows ships it.
|
|
174
|
-
*/
|
|
175
|
-
const setupFrameworkReferences = async (env, targets) => {
|
|
176
|
-
if (!targets.length)
|
|
177
|
-
return;
|
|
178
|
-
const root = tool('netfx-references');
|
|
179
|
-
for (const target of targets) {
|
|
180
|
-
const id = 'microsoft.netframework.referenceassemblies.' + target.toLowerCase();
|
|
181
|
-
const marker = path.join(root, '.installed-' + target.toLowerCase());
|
|
182
|
-
if (exists(marker))
|
|
183
|
-
continue;
|
|
184
|
-
try {
|
|
185
|
-
const { versions } = await fetchJson('https://api.nuget.org/v3-flatcontainer/' + id + '/index.json');
|
|
186
|
-
const version = versions.filter((v) => /^\d+(\.\d+)*$/.test(v)).pop();
|
|
187
|
-
if (!version)
|
|
188
|
-
throw new Error('no stable version of ' + id);
|
|
189
|
-
log('Downloading .NET Framework reference assemblies (' + target + ') ...');
|
|
190
|
-
const staging = tool('netfx-references-' + target.toLowerCase());
|
|
191
|
-
await installArchive('https://api.nuget.org/v3-flatcontainer/' + id + '/' + version + '/' + id + '.' + version + '.nupkg', staging, { fileName: id + '.zip' });
|
|
192
|
-
fs.cpSync(path.join(staging, 'build', '.NETFramework'), path.join(root, '.NETFramework'), { recursive: true });
|
|
193
|
-
fs.rmSync(staging, { recursive: true, force: true });
|
|
194
|
-
fs.writeFileSync(marker, version);
|
|
195
|
-
}
|
|
196
|
-
catch (e) {
|
|
197
|
-
warn('.NET Framework reference assemblies ' + target + ' failed: ' + errMsg(e));
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
if (!exists(path.join(root, '.NETFramework')))
|
|
201
|
-
return;
|
|
202
|
-
env.vars.TargetFrameworkRootPath = root + path.sep;
|
|
203
|
-
env.summary.push('.NET Framework targeting: ' + fs.readdirSync(path.join(root, '.NETFramework')).join(', '));
|
|
204
|
-
};
|
|
205
|
-
/** .NET Framework 3.5 is a Windows feature the sandbox image lacks; DISM fetches it from Windows Update on every boot. */
|
|
206
|
-
const setupNetFx35 = async (env) => {
|
|
207
|
-
if (execOutput(SYSTEM.reg(), ['query', 'HKLM\\SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v3.5', '/v', 'Install']).includes('0x1')) {
|
|
208
|
-
env.summary.push('.NET Framework 3.5: on');
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
log('Enabling .NET Framework 3.5 (downloads from Windows Update, a few minutes) ...');
|
|
212
|
-
try {
|
|
213
|
-
await execAsync(SYSTEM.dism(), ['/Online', '/Enable-Feature', '/FeatureName:NetFx3', '/All', '/NoRestart', '/Quiet'], { stdio: 'ignore' });
|
|
214
|
-
env.summary.push('.NET Framework 3.5: on');
|
|
215
|
-
}
|
|
216
|
-
catch (e) {
|
|
217
|
-
warn('.NET Framework 3.5 could not be enabled: ' + errMsg(e));
|
|
218
|
-
env.summary.push('.NET Framework 3.5: FAILED');
|
|
219
|
-
}
|
|
220
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { Toolchains } from '../../core/types.js';
|
|
2
|
-
import { type ToolEnv } from './common.js';
|
|
3
|
-
/**
|
|
4
|
-
* vcruntime140.dll & co. A real installer (it writes to System32), so it runs on every boot; the installer itself is
|
|
5
|
-
* kept in persistent storage and only run when Microsoft's signature on it checks out. Resolves true when this call
|
|
6
|
-
* ran the installer. Only the "Visual C++ runtime" step reports it (`summary`); PHP calls it again just to be sure.
|
|
7
|
-
*/
|
|
8
|
-
export declare const setupVcRuntime: (env: ToolEnv, summary?: boolean) => Promise<boolean>;
|
|
9
|
-
export declare const setupRuby: (env: ToolEnv, config: Toolchains["ruby"]) => Promise<void>;
|
|
10
|
-
export declare const setupPhp: (env: ToolEnv, config: Toolchains["php"]) => Promise<void>;
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
// Ruby (RubyInstaller) and PHP (windows.php.net, with Composer), plus the Visual C++ runtime PHP and other native
|
|
2
|
-
// tools need. Gems and Composer's global packages live in persistent storage.
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { download, fetchJson, fetchText, installArchive, spawnAsync } from '../../shared/download.js';
|
|
6
|
-
import { SYSTEM } from '../../shared/system.js';
|
|
7
|
-
import { dryRun, execAsync, execOutput, exists, log, mkdirp } from '../env.js';
|
|
8
|
-
import { installOnce, resolveCached, tool, versionFromFiles, versionOf } from './common.js';
|
|
9
|
-
import { pickPackageVersion } from './versions.js';
|
|
10
|
-
/** Writes `<name>.cmd` into tools\shims (on PATH). */
|
|
11
|
-
const shim = (env, name, command) => {
|
|
12
|
-
fs.writeFileSync(path.join(mkdirp(tool('shims')), name + '.cmd'), '@echo off\r\n' + command + ' %*\r\n');
|
|
13
|
-
env.prepend.push(tool('shims'));
|
|
14
|
-
};
|
|
15
|
-
// ---- Visual C++ runtime ----------------------------------------------------------------------------------------------
|
|
16
|
-
const VC_REDIST = 'https://aka.ms/vs/17/release/vc_redist.x64.exe';
|
|
17
|
-
/**
|
|
18
|
-
* vcruntime140.dll & co. A real installer (it writes to System32), so it runs on every boot; the installer itself is
|
|
19
|
-
* kept in persistent storage and only run when Microsoft's signature on it checks out. Resolves true when this call
|
|
20
|
-
* ran the installer. Only the "Visual C++ runtime" step reports it (`summary`); PHP calls it again just to be sure.
|
|
21
|
-
*/
|
|
22
|
-
export const setupVcRuntime = async (env, summary = true) => {
|
|
23
|
-
if (dryRun) {
|
|
24
|
-
log('(dry run) would install the Visual C++ runtime');
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
const system32 = path.join(process.env.WINDIR ?? 'C:\\Windows', 'System32');
|
|
28
|
-
const missing = !exists(path.join(system32, 'vcruntime140.dll')) || !exists(path.join(system32, 'msvcp140.dll'));
|
|
29
|
-
if (missing) {
|
|
30
|
-
const installer = tool('vcredist', 'vc_redist.x64.exe');
|
|
31
|
-
if (!exists(installer)) {
|
|
32
|
-
log('Downloading the Visual C++ runtime ...');
|
|
33
|
-
await download(VC_REDIST, installer);
|
|
34
|
-
}
|
|
35
|
-
const signer = execOutput(SYSTEM.powershell(), ['-NoLogo', '-NoProfile', '-Command', "$s = Get-AuthenticodeSignature -LiteralPath '" + installer.replace(/'/g, "''") + "'; if ($s.Status -eq 'Valid') { $s.SignerCertificate.Subject }"]);
|
|
36
|
-
if (signer.trim() !== 'CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US') {
|
|
37
|
-
fs.rmSync(installer, { force: true });
|
|
38
|
-
throw new Error('vc_redist.x64.exe is not signed by Microsoft; deleted it (it is downloaded again next boot)');
|
|
39
|
-
}
|
|
40
|
-
log('Installing the Visual C++ runtime ...');
|
|
41
|
-
// 1638: a newer version is already installed; 3010: installed, a restart would finish it (not needed for the DLLs).
|
|
42
|
-
const code = await spawnAsync(installer, ['/install', '/quiet', '/norestart']);
|
|
43
|
-
if (![0, 1638, 3010].includes(code ?? -1) || !exists(path.join(system32, 'vcruntime140.dll')))
|
|
44
|
-
throw new Error('the Visual C++ runtime installer failed (exit ' + code + ')');
|
|
45
|
-
}
|
|
46
|
-
if (summary)
|
|
47
|
-
env.summary.push('vc++ runtime: installed');
|
|
48
|
-
return missing;
|
|
49
|
-
};
|
|
50
|
-
const RUBY_ASSET = /^rubyinstaller-(\d+\.\d+\.\d+)-(\d+)-x64\.7z$/;
|
|
51
|
-
/** RubyInstaller builds per Ruby version (newest build of each), from its GitHub releases. */
|
|
52
|
-
const rubyBuilds = async () => {
|
|
53
|
-
const releases = await fetchJson('https://api.github.com/repos/oneclick/rubyinstaller2/releases?per_page=100');
|
|
54
|
-
const builds = new Map();
|
|
55
|
-
for (const asset of releases.filter((r) => !r.draft && !r.prerelease).flatMap((r) => r.assets)) {
|
|
56
|
-
const match = RUBY_ASSET.exec(asset.name);
|
|
57
|
-
const known = match && builds.get(match[1]);
|
|
58
|
-
if (match && (!known || Number(RUBY_ASSET.exec(known.name)[2]) < Number(match[2])))
|
|
59
|
-
builds.set(match[1], asset);
|
|
60
|
-
}
|
|
61
|
-
return builds;
|
|
62
|
-
};
|
|
63
|
-
export const setupRuby = async (env, config) => {
|
|
64
|
-
const wantedSpec = versionFromFiles(['.ruby-version']) ?? { spec: config.version, source: 'config' };
|
|
65
|
-
if (dryRun) {
|
|
66
|
-
log('(dry run) would provision Ruby ' + wantedSpec.spec);
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
const version = await resolveCached('ruby:' + wantedSpec.spec, async () => pickPackageVersion([...(await rubyBuilds()).keys()], wantedSpec.spec) ?? Promise.reject(new Error('No RubyInstaller build matches "' + wantedSpec.spec + '"')));
|
|
70
|
-
const home = tool('ruby', version);
|
|
71
|
-
if (!exists(path.join(home, 'bin', 'ruby.exe'))) {
|
|
72
|
-
const asset = (await rubyBuilds()).get(version);
|
|
73
|
-
if (!asset)
|
|
74
|
-
throw new Error('RubyInstaller ' + version + ' is no longer published');
|
|
75
|
-
log('Downloading ' + asset.name + ' ...');
|
|
76
|
-
await installArchive(asset.browser_download_url, home, { sha256: asset.digest?.startsWith('sha256:') ? asset.digest.slice(7) : null, locate: 'ruby.exe', up: 1, fileName: asset.name });
|
|
77
|
-
}
|
|
78
|
-
const gems = tool('ruby-gems', version.split('.').slice(0, 2).join('.'));
|
|
79
|
-
Object.assign(env.vars, { GEM_HOME: gems, BUNDLE_USER_HOME: tool('bundle-home') });
|
|
80
|
-
env.prepend.push(path.join(home, 'bin'), path.join(gems, 'bin'));
|
|
81
|
-
env.summary.push('ruby: ' + versionOf(path.join(home, 'bin', 'ruby.exe')) + ' (from ' + wantedSpec.source + '; gems with C extensions need MSYS2, not included)');
|
|
82
|
-
const gem = path.join(home, 'bin', 'gem.cmd');
|
|
83
|
-
await installOnce(env, 'gem@' + version, config.gems, (pkg) => execAsync(SYSTEM.cmd(), ['/d', '/c', gem, 'install', pkg, '--no-document'], { env: { ...process.env, GEM_HOME: gems } }));
|
|
84
|
-
};
|
|
85
|
-
const PHP_EXTENSIONS = ['curl', 'fileinfo', 'gd', 'intl', 'mbstring', 'openssl', 'pdo_mysql', 'pdo_pgsql', 'pdo_sqlite', 'sodium', 'sqlite3', 'zip'];
|
|
86
|
-
/** The non-thread-safe x64 zip of the newest release matching `spec` ("8" -> newest 8.x, "8.3" -> newest 8.3.x). */
|
|
87
|
-
const phpBuild = async (spec) => {
|
|
88
|
-
const releases = await fetchJson('https://windows.php.net/downloads/releases/releases.json');
|
|
89
|
-
const pick = pickPackageVersion(Object.values(releases).map((r) => r.version), spec);
|
|
90
|
-
const release = Object.values(releases).find((r) => r.version === pick);
|
|
91
|
-
const build = release && Object.entries(release).find(([key, value]) => /^nts-vs\d+-x64$/.test(key) && typeof value === 'object' && value.zip)?.[1];
|
|
92
|
-
if (!release || typeof build !== 'object' || !build.zip)
|
|
93
|
-
throw new Error('No PHP build for Windows matches "' + spec + '"');
|
|
94
|
-
return { version: release.version, ...build.zip };
|
|
95
|
-
};
|
|
96
|
-
/** php.ini from the development template with the common extensions switched on. */
|
|
97
|
-
const writePhpIni = (home) => {
|
|
98
|
-
const ini = path.join(home, 'php.ini');
|
|
99
|
-
if (exists(ini) || !exists(path.join(home, 'php.ini-development')))
|
|
100
|
-
return;
|
|
101
|
-
let text = fs.readFileSync(path.join(home, 'php.ini-development'), 'utf8')
|
|
102
|
-
.replace(/^;\s*extension_dir\s*=\s*"ext"/m, 'extension_dir = "' + path.join(home, 'ext') + '"');
|
|
103
|
-
for (const ext of PHP_EXTENSIONS.filter((e) => exists(path.join(home, 'ext', 'php_' + e + '.dll'))))
|
|
104
|
-
text = text.replace(new RegExp('^;(extension=' + ext + ')\\s*$', 'm'), '$1');
|
|
105
|
-
fs.writeFileSync(ini, text);
|
|
106
|
-
};
|
|
107
|
-
export const setupPhp = async (env, config) => {
|
|
108
|
-
const wantedSpec = versionFromFiles(['.php-version']) ?? { spec: config.version, source: 'config' };
|
|
109
|
-
if (dryRun) {
|
|
110
|
-
log('(dry run) would provision PHP ' + wantedSpec.spec + (config.composer ? ' + Composer' : ''));
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
// No summary line of its own: the Visual C++ runtime step (which runs first) already reports it.
|
|
114
|
-
await setupVcRuntime(env, false);
|
|
115
|
-
const version = await resolveCached('php:' + wantedSpec.spec, async () => (await phpBuild(wantedSpec.spec)).version);
|
|
116
|
-
const home = tool('php', version);
|
|
117
|
-
if (!exists(path.join(home, 'php.exe'))) {
|
|
118
|
-
const build = await phpBuild(version);
|
|
119
|
-
log('Downloading ' + build.path + ' ...');
|
|
120
|
-
await installArchive('https://windows.php.net/downloads/releases/' + build.path, home, { sha256: build.sha256, locate: 'php.exe', fileName: build.path });
|
|
121
|
-
}
|
|
122
|
-
writePhpIni(home);
|
|
123
|
-
env.prepend.push(home);
|
|
124
|
-
env.summary.push('php: ' + version + ' (from ' + wantedSpec.source + ')');
|
|
125
|
-
if (!config.composer)
|
|
126
|
-
return;
|
|
127
|
-
const phar = tool('composer', 'composer.phar');
|
|
128
|
-
if (!exists(phar)) {
|
|
129
|
-
log('Downloading Composer ...');
|
|
130
|
-
await download('https://getcomposer.org/download/latest-stable/composer.phar', phar, (await fetchText('https://getcomposer.org/download/latest-stable/composer.phar.sha256sum')).trim().split(/\s+/)[0]);
|
|
131
|
-
}
|
|
132
|
-
const composerHome = tool('composer-home');
|
|
133
|
-
env.vars.COMPOSER_HOME = composerHome;
|
|
134
|
-
env.prepend.push(path.join(composerHome, 'vendor', 'bin'));
|
|
135
|
-
shim(env, 'composer', '"' + path.join(home, 'php.exe') + '" "' + phar + '"');
|
|
136
|
-
env.summary.push('composer: ' + (/\d+\.\d+\.\d+/.exec(execOutput(path.join(home, 'php.exe'), [phar, '--version', '--no-ansi']))?.[0] ?? 'installed'));
|
|
137
|
-
};
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
// Generated sandbox (.wsb + launch folder) and the guest boot in dry-run mode.
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { buildWsb, gitIdentity, resolveMemoryMb } from '../host/generate.js';
|
|
5
|
-
import { readJsonFile } from '../shared/rundir.js';
|
|
6
|
-
export const guestTests = async (t, f) => {
|
|
7
|
-
const { check, rejects, at } = { ...t, at: f.at };
|
|
8
|
-
t.section('generated sandbox');
|
|
9
|
-
const rA = await f.run();
|
|
10
|
-
const gen = f.generate(rA, ['--version'], { ...f.probe, gitDir: f.shared, nvmDir: f.workB });
|
|
11
|
-
const wsb = fs.readFileSync(gen.wsbPath, 'utf8');
|
|
12
|
-
check(wsb.includes('<MemoryInMB>8192</MemoryInMB>') && wsb.includes('<SandboxFolder>C:\\Workspace\\work-a</SandboxFolder>') && wsb.includes('C:\\HostTools\\git') && wsb.includes('C:\\HostTools\\nvm') && !wsb.includes('Missing') && wsb.includes('logon.cmd</Command>') && wsb.includes('<vGPU>Disable</vGPU>'), 'wsb: memory, workspace, host git/nvm, optional mapping skipped, logon command, vGPU off');
|
|
13
|
-
const files = ['package.json', 'runtime/main.js', 'runtime/daemon.js', 'runtime/view.js', 'runtime/gitguard.js', 'sandbox/bootstrap.ps1', 'sandbox/logon.cmd', 'sandbox/trust.cmd', 'sandbox/ptyhost.cs', 'sandbox/gitshim.cs'];
|
|
14
|
-
const missing = files.filter((file) => !fs.existsSync(path.join(gen.launchPath, file)));
|
|
15
|
-
check(missing.length === 0 && fs.readdirSync(path.join(gen.launchPath, 'runtime')).length === 5, 'launch folder has the bundled sandbox runtime (5 self-contained files), the C# sources and the boot scripts' + (missing.length ? ' (missing: ' + missing.join(', ') + ')' : ''));
|
|
16
|
-
const logon = fs.readFileSync(path.join(gen.launchPath, 'sandbox', 'logon.cmd'), 'utf8');
|
|
17
|
-
check(logon.indexOf('trust.cmd') < logon.indexOf('wt.exe') && logon.includes('tools\\terminal\\wt.exe') && logon.includes('-NoExit'), 'logon.cmd applies sandbox trust settings first, then opens Windows Terminal (PowerShell fallback)');
|
|
18
|
-
const session = f.readJson(gen.sessionPath);
|
|
19
|
-
check(session.launchId === gen.launchId && session.workspace === 'C:\\Workspace\\work-a' && session.workspaceHost === f.workA && session.harnessArguments[0] === '--version' && session.hostTools.git && session.hostTools.nvm && /^\d+\.\d+\.\d+$/.test(session.hostTools.nodeVersion ?? '') && session.versions.node.spec === '22.3.0' && session.provisioning.git.access === 'full', 'session.json carries ids, paths, versions, host tools and git access');
|
|
20
|
-
const staged = path.join(gen.launchPath, 'claude-profile');
|
|
21
|
-
check(['settings.json', 'CLAUDE.md', '.claude.json', 'plugins'].every((file) => fs.existsSync(path.join(staged, file))) && !fs.existsSync(path.join(staged, '.credentials.json')), 'Claude settings and plugins staged; the login is not');
|
|
22
|
-
check(gitIdentity('[user]\n\tname = Josh\n\temail = "j@x.io"\n[credential]\n\thelper = store\n[url "https://TOKEN@github.com/"]\n\tinsteadOf = https://github.com/\n') === '[user]\n\tname = Josh\n\temail = j@x.io\n' && gitIdentity('[core]\n\tname = x\n') === '', 'only name/email from ~/.gitconfig reach the sandbox (no credential helpers or token URLs)');
|
|
23
|
-
check(buildWsb(f.baseConfig, 4096, [{ host: 'C:\\a&b', guest: 'C:\\X', readOnly: true }]).includes('C:\\a&b'), 'wsb values are XML-escaped');
|
|
24
|
-
check(resolveMemoryMb({ ...f.baseConfig.sandbox, memoryMb: 'auto' }, 40000 * 1048576) === 31744 && await rejects(() => resolveMemoryMb({ ...f.baseConfig.sandbox, memoryMb: 'auto' }, 9000 * 1048576), 'Not enough free RAM'), "memory 'auto' follows free RAM minus reserve");
|
|
25
|
-
t.section('guest boot (dry run)');
|
|
26
|
-
const data1 = at('data1');
|
|
27
|
-
const r1 = f.runGuest(gen.launchPath, data1, f.workA, { AGENT_TEST_EXEMPT: '172.28.0.1' });
|
|
28
|
-
check(r1.status === 0 && r1.out.includes('would run:') && r1.out.includes('--version') && r1.out.includes('cwd=' + f.workA), 'guest starts the agent with its arguments in the workspace' + (r1.status === 0 ? '' : '\n' + r1.out));
|
|
29
|
-
check(['would provision nvm and Node 22.3.0 (from this PC)', 'would provision Python 3', 'would provision MinGW-w64 GCC', 'would provision Rust', '.NET SDK', 'would provision bun', 'would provision uv', 'would provision Git for Windows from this PC'].every((s) => r1.out.includes(s)), 'guest provisions nvm/Node, Python, MinGW, Rust, .NET, Bun, uv, Git');
|
|
30
|
-
const claudeState = path.join(data1, 'state', 'claude');
|
|
31
|
-
check(fs.readFileSync(path.join(claudeState, 'settings.json'), 'utf8').includes('dark') && fs.existsSync(path.join(claudeState, 'commands', 'review.md')) && !fs.existsSync(path.join(claudeState, '.credentials.json')), 'guest applied host Claude settings (no login)');
|
|
32
|
-
check(fs.readFileSync(path.join(claudeState, 'plugins', 'installed_plugins.json'), 'utf8').includes(JSON.stringify(claudeState).slice(1, -1)), 'plugin manifests are re-pointed at the sandbox Claude folder');
|
|
33
|
-
const guestState = f.readJson(path.join(claudeState, '.claude.json'));
|
|
34
|
-
check(guestState.mcpServers?.github?.command === 'npx', 'MCP servers from this PC are in the sandbox');
|
|
35
|
-
const manifest = fs.readFileSync(at('managed', 'CLAUDE.md'), 'utf8');
|
|
36
|
-
check(manifest.includes('DISPOSABLE Windows Sandbox') && manifest.includes(f.workA) && manifest.split('\n').length < 16 && fs.readFileSync(path.join(claudeState, 'CLAUDE.md'), 'utf8') === 'my rules' && fs.existsSync(path.join(data1, 'SANDBOX.md')), 'agent manifest goes to the managed CLAUDE.md (tiny); the user CLAUDE.md is untouched');
|
|
37
|
-
check(!fs.existsSync(at('managed', 'managed-settings.json')), 'git access full: no managed deny rules');
|
|
38
|
-
check(r1.out.includes('AgentSandbox-Block-Private') && r1.out.includes('remoteport=1-52,54-65535'), 'guest firewall plan blocks the LAN but keeps DNS to the gateway');
|
|
39
|
-
check(readJsonFile(path.join(data1, 'run', 'sandbox.json'))?.phase === 'ready', 'guest reports "ready" in run/sandbox.json');
|
|
40
|
-
// Second boot: host changed its MCP servers; the sandbox keeps its own .claude.json but gets the new servers.
|
|
41
|
-
fs.writeFileSync(path.join(claudeState, '.claude.json'), JSON.stringify({ ...guestState, oauthAccount: { emailAddress: 'sandbox@x' }, mcpServers: { local: { command: 'x' } } }));
|
|
42
|
-
fs.writeFileSync(path.join(gen.launchPath, 'claude-profile', '.claude.json'), JSON.stringify({ mcpServers: { github: { command: 'npx2' } }, projects: { [f.workA.replace(/\\/g, '/')]: { mcpServers: { db: { command: 'pg' } } } } }));
|
|
43
|
-
f.runGuest(gen.launchPath, data1, f.workA);
|
|
44
|
-
const merged = f.readJson(path.join(claudeState, '.claude.json'));
|
|
45
|
-
check(merged.oauthAccount.emailAddress === 'sandbox@x' && merged.mcpServers.github.command === 'npx2' && merged.mcpServers.local.command === 'x' && Object.values(merged.projects).some((p) => p.mcpServers?.db), 'MCP servers re-sync every boot (user + project scope) without touching the sandbox login');
|
|
46
|
-
const readonlyConfig = f.makeConfig();
|
|
47
|
-
readonlyConfig.provisioning.git.access = 'readonly';
|
|
48
|
-
const genRo = f.generate(await f.run({}, readonlyConfig));
|
|
49
|
-
f.runGuest(genRo.launchPath, at('data-ro'), f.workA, { AGENT_MANAGED_DIR: at('managed-ro') });
|
|
50
|
-
const policy = f.readJson(at('managed-ro', 'managed-settings.json'));
|
|
51
|
-
check(policy.permissions.deny.includes('Bash(git commit:*)') && policy.permissions.deny.includes('PowerShell(git push:*)') && policy.permissions.deny.includes('Edit(**/.git/**)') && !policy.permissions.deny.includes('Bash(git status:*)'), 'git readonly: Claude managed settings deny writing git commands and .git edits');
|
|
52
|
-
const genHook = f.generate(await f.run({ cwd: f.workB, harness: 'claude', trustConfig: true }));
|
|
53
|
-
const rHook = f.runGuest(genHook.launchPath, at('data-hook'), f.workB);
|
|
54
|
-
check(rHook.status === 0 && fs.readFileSync(at('data-hook', 'hook-ran.txt'), 'utf8') === f.workB, 'local "scripts" run inside the sandbox with the workspace set');
|
|
55
|
-
const genInstall = f.generate(await f.run({}, f.makeConfig(false)), [], { ...f.probe, nodeDir: null, harnessExe: null });
|
|
56
|
-
const r3 = f.runGuest(genInstall.launchPath, at('data3'), f.workA, { AGENT_HOST_TOOLS_DIR: at('empty') });
|
|
57
|
-
check(r3.status === 0 && r3.out.includes('npm-cli.js') && r3.out.includes('@anthropic-ai/claude-code') && !fs.readFileSync(genInstall.wsbPath, 'utf8').includes('HostTools'), 'install mode: nothing mapped from this PC, agent installed with npm');
|
|
58
|
-
const r5 = f.runGuest(f.generate(await f.run({ noAgent: true })).launchPath, at('data5'), f.workA);
|
|
59
|
-
check(r5.status === 0 && r5.out.includes('no agent requested'), '--no-agent provisions without an agent');
|
|
60
|
-
return gen.launchPath;
|
|
61
|
-
};
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
// Small pure helpers: version picking, .reg generation, firewall maths, storage cleanup.
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { spawnSync } from 'node:child_process';
|
|
5
|
-
import { pathToFileURL } from 'node:url';
|
|
6
|
-
import { pruneStorage } from '../host/cleanup.js';
|
|
7
|
-
import { toRegFile } from '../host/personalize.js';
|
|
8
|
-
import { javaMajorFrom, pickGoRelease, pickNodeVersion, pickPackageVersion, pickSpreadVersions, projectUses, specFromVersionFile } from '../guest/toolchains/versions.js';
|
|
9
|
-
import { parseReleaseTags } from '../shared/download.js';
|
|
10
|
-
import { buildFirewallCommands, cidrToRange, ipv4DnsServers, parseDefaultGateways, parseDnsServers, subtractIps } from '../shared/netrules.js';
|
|
11
|
-
import { ensureDir, isWin } from '../core/util.js';
|
|
12
|
-
import { decidePwsh, pcHasPwsh, PWSH_MAX_BYTES, readPwshScripts, usesPwsh } from '../core/detect-pwsh.js';
|
|
13
|
-
import { binstallAsset, CLI_TOOLS, decideCli, pwshAsset } from '../guest/toolchains/catalogue.js';
|
|
14
|
-
import { PWSH_PROFILE_GUID, syncPwshProfile, withPwshProfile } from '../host/terminal.js';
|
|
15
|
-
export const helperTests = (t) => {
|
|
16
|
-
const { check, write } = t;
|
|
17
|
-
t.section('versions, registry, firewall');
|
|
18
|
-
const releases = [{ version: 'v23.1.0', lts: false }, { version: 'v22.11.0', lts: 'Jod' }, { version: 'v20.18.0', lts: 'Iron' }, { version: 'v20.9.0', lts: 'Iron' }];
|
|
19
|
-
check(pickNodeVersion(releases, 'lts/*') === '22.11.0' && pickNodeVersion(releases, 'lts/iron') === '20.18.0' && pickNodeVersion(releases, '20') === '20.18.0' && pickNodeVersion(releases, 'v20.9.0') === '20.9.0' && pickNodeVersion(releases, 'node') === '23.1.0' && pickNodeVersion(releases, '99') === null, '.nvmrc specs resolve (lts/*, lts/<name>, major, exact, node)');
|
|
20
|
-
const spread = pickSpreadVersions(['16.20.2', '18.19.0', '18.20.4', '20.18.0', '22.3.0', '22.11.0', '23.1.0', 'notes'], '22.11.0', 3);
|
|
21
|
-
check(JSON.stringify(spread) === '["22.11.0","16.20.2","23.1.0"]' && pickSpreadVersions(['20.18.0'], '22.3.0', 3).join() === '22.3.0,20.18.0' && pickSpreadVersions(['18.20.4', '20.18.0'], '22.3.0', 1).join() === '22.3.0', 'host nvm versions: capped, current first, rest spread across other majors');
|
|
22
|
-
const go = [{ version: 'go1.24rc1', stable: false, files: [] }, { version: 'go1.23.2', stable: true, files: [] }, { version: 'go1.23.1', stable: true, files: [] }, { version: 'go1.22.8', stable: true, files: [] }];
|
|
23
|
-
check(pickGoRelease(go, 'stable')?.version === 'go1.23.2' && pickGoRelease(go, '1.22')?.version === 'go1.22.8' && pickGoRelease(go, 'go1.23.1')?.version === 'go1.23.1' && pickGoRelease(go, '1.30') === null, 'Go versions resolve (stable, minor prefix, exact; release candidates skipped)');
|
|
24
|
-
check(javaMajorFrom('21\n') === '21' && javaMajorFrom('17.0.9') === '17' && javaMajorFrom('1.8') === '8' && javaMajorFrom('temurin-21.0.2') === '21' && javaMajorFrom('# sdkman\njava=21.0.2-tem\n', true) === '21' && specFromVersionFile('ruby-3.3.1\n') === '3.3.1', 'version files: .java-version, .sdkmanrc, .ruby-version');
|
|
25
|
-
check(projectUses(['README.md', 'server/go.mod'], ['go.mod']) && projectUses(['App.csproj'], ['*.csproj']) && !projectUses(['notes.txt', 'docs/readme.md'], ['go.mod', '*.csproj']), 'enabled "auto": a language is used when its marker file is in the workspace or one folder down');
|
|
26
|
-
check(pickPackageVersion(['3.9.13', '3.12.7', '3.12.10', '3.13.0-rc1', '3.13.1'], '3') === '3.13.1' && pickPackageVersion(['3.12.7', '3.12.10'], '3.12') === '3.12.10' && pickPackageVersion(['2.7.18'], '2.7') === '2.7.18', 'Python versions resolve to the newest stable match');
|
|
27
|
-
const reg = toRegFile([{ key: 'Control Panel\\Desktop', name: 'WallpaperStyle', kind: 'String', value: '10' }, { key: 'X', name: 'D', kind: 'DWord', value: 4294967295 }, { key: 'X', name: 'B', kind: 'Binary', value: Buffer.from([1, 255]).toString('base64') }]);
|
|
28
|
-
check(reg.includes('[HKEY_CURRENT_USER\\Control Panel\\Desktop]') && reg.includes('"WallpaperStyle"="10"') && reg.includes('"D"=dword:ffffffff') && reg.includes('"B"=hex:01,ff'), 'personalisation: registry values become a valid .reg file (HKCU only)');
|
|
29
|
-
const [s172, e172] = cidrToRange('172.16.0.0/12');
|
|
30
|
-
const gw = 172 * 16777216 + 28 * 65536 + 1;
|
|
31
|
-
check(subtractIps([[s172, e172]], ['172.28.0.1']).every(([s, e]) => gw < s || gw > e) && !buildFirewallCommands(['8.8.8.8']).join().includes('8.8.8.8'), 'firewall maths: private block minus the gateway; public addresses ignored');
|
|
32
|
-
check(parseDefaultGateways(' 0.0.0.0 0.0.0.0 172.28.80.1 172.28.85.200 5\n').join() === '172.28.80.1' && parseDnsServers(' DNS Servers . . . : 172.28.80.1\n 8.8.8.8\n NetBIOS : x\n').join() === '172.28.80.1,8.8.8.8', 'parses gateway and DNS servers');
|
|
33
|
-
check(ipv4DnsServers(['172.28.80.1', '8.8.8.8:1053', '[::1]:53', 'fe80::1']).join() === '172.28.80.1,8.8.8.8', 'DNS servers from the resolver (any Windows language): IPv4 only, ports dropped');
|
|
34
|
-
check(parseReleaseTags('<a href="/coreybutler/nvm-windows/releases/tag/1.2.2">x</a><a href="/coreybutler/nvm-windows/releases/tag/1.2.2">y</a><a href="/other/repo/releases/tag/9">z</a><a href="/coreybutler/nvm-windows/releases/tag/1.1.12">w</a>', 'coreybutler/nvm-windows').join() === '1.2.2,1.1.12', 'GitHub release list page: this repo\'s tags in order, once each');
|
|
35
|
-
t.section('cmd quoting (npm agents)');
|
|
36
|
-
// ptyhost.ts loads env.ts, which reads session.json: import it in a child with a throwaway launch/data folder.
|
|
37
|
-
const quoteDir = path.join(t.temp, 'quote');
|
|
38
|
-
write(path.join(quoteDir, 'launch', 'session.json'), '{}');
|
|
39
|
-
const agentCmd = write(path.join(quoteDir, 'agent.cmd'), '@"' + process.execPath + '" -e "console.log(JSON.stringify(process.argv.slice(1)))" %*\r\n');
|
|
40
|
-
// Odd quote counts flip cmd's quote state for every later argument: those come after them on purpose.
|
|
41
|
-
const tricky = ['https://x?a=1&b=2', '50% off', 'a|b<c>d', 'x^y', 'say "hi" & go', 'plain', '%PATH%', 'a"b&c', 'q"x|y^z"w', 'e"5%', 'k&v', '100%'];
|
|
42
|
-
const probe = spawnSync(process.execPath, ['--input-type=module', '-e', [
|
|
43
|
-
'const m = await import(process.argv[1]); const [cmd, tricky] = JSON.parse(process.argv[2]);',
|
|
44
|
-
'console.log(JSON.stringify({ normal: m.commandLine("C:\\\\W\\\\cmd.exe", ["/d", "/c", "C:\\\\a\\\\agent.cmd", "--model", "big one", "plain", ""]), direct: m.commandLine("C:\\\\a\\\\agent.exe", ["a&b", "50%"]),',
|
|
45
|
-
' url: m.cmdArgs(["https://x?a=1&b=2"]), pct: m.cmdArgs(["50%"]), live: m.commandLine("cmd.exe", ["/d", "/c", cmd, ...tricky]).slice(8) }));',
|
|
46
|
-
].join('\n'), pathToFileURL(path.join(t.packageRoot, 'dist', 'guest', 'ptyhost.js')).href, JSON.stringify([agentCmd, tricky])], {
|
|
47
|
-
encoding: 'utf8', env: { ...process.env, AGENT_LAUNCH_DIR: path.join(quoteDir, 'launch'), AGENT_DATA_DIR: path.join(quoteDir, 'data') },
|
|
48
|
-
});
|
|
49
|
-
const quoting = (() => { try {
|
|
50
|
-
return JSON.parse(probe.stdout);
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
return {};
|
|
54
|
-
} })();
|
|
55
|
-
check(quoting.normal === 'C:\\W\\cmd.exe /d /c C:\\a\\agent.cmd --model "big one" plain ""' && quoting.direct === 'C:\\a\\agent.exe a&b 50%', 'command lines: ordinary arguments quoted as before; a program started directly gets & and % unchanged' + (quoting.normal ? '' : ' (' + (probe.stderr || probe.stdout).trim().slice(0, 300) + ')'));
|
|
56
|
-
check(quoting.url === '"https://x?a=1&b=2"' && quoting.pct === '"50"%""', 'cmd /c arguments: & | < > ^ quoted, % split out so no %name% expands');
|
|
57
|
-
// (Agents live under C:\AgentData\tools; a temp folder with spaces would also need cmd's quote rules, so skip there.)
|
|
58
|
-
if (isWin && quoting.live && !/\s/.test(agentCmd)) {
|
|
59
|
-
const echoed = spawnSync(process.env.ComSpec ?? 'cmd.exe', [quoting.live], { encoding: 'utf8', windowsVerbatimArguments: true, windowsHide: true });
|
|
60
|
-
check(echoed.stdout.trim() === JSON.stringify(tricky), 'cmd /d /c <agent>.cmd passes &, |, <, >, ^, % and quotes through to the agent unchanged (got ' + echoed.stdout.trim() + ')');
|
|
61
|
-
}
|
|
62
|
-
t.section('cleanup');
|
|
63
|
-
const store = path.join(t.temp, 'prune');
|
|
64
|
-
const aged = (p, days) => { const d = new Date(Date.now() - days * 86400000); fs.utimesSync(p, d, d); };
|
|
65
|
-
for (const [name, days] of [['old', 9], ['kept', 30], ['new', 1]]) {
|
|
66
|
-
aged(write(path.join(store, 'launches', name, 'agent.wsb'), 'x'), days);
|
|
67
|
-
aged(path.join(store, 'launches', name), days);
|
|
68
|
-
}
|
|
69
|
-
aged(write(path.join(store, 'data', 'd', 'logs', 'guest-old.log'), 'x'), 20);
|
|
70
|
-
write(path.join(store, 'data', 'd', 'logs', 'guest-new.log'), 'x');
|
|
71
|
-
ensureDir(path.join(store, 'data', 'd', 'tools', 'git.staging-ab12'));
|
|
72
|
-
aged(path.join(store, 'data', 'd', 'tools', 'git.staging-ab12'), 2);
|
|
73
|
-
ensureDir(path.join(store, 'data', 'd', 'tools', 'node'));
|
|
74
|
-
const pruned = pruneStorage(store, { keep: ['kept'] });
|
|
75
|
-
const left = (p) => fs.existsSync(path.join(store, p));
|
|
76
|
-
check(pruned.launches === 1 && !left('launches/old') && left('launches/kept') && left('launches/new') && !left('data/d/logs/guest-old.log') && left('data/d/logs/guest-new.log') && !left('data/d/tools/git.staging-ab12') && left('data/d/tools/node'), 'cleanup: launches > 7 days, logs > 14 days and interrupted downloads go; the running launch and real tools stay');
|
|
77
|
-
t.section('PowerShell 7, CLI catalogue, cargo-binstall');
|
|
78
|
-
const pwshNames = ['PowerShell-7.6.6-win-x64.zip', 'PowerShell-7.6.6-win-arm64.zip', 'PowerShell-7.6.6-win-x64.msi', 'PowerShell-7.6.6-win-fxdependent.zip', 'PowerShell-7.6.6-win-x86.zip', 'PowerShell-7.6.6-win-x64.zip.sha256'];
|
|
79
|
-
check(pwshNames.filter((n) => pwshAsset('x64').test(n)).join() === 'PowerShell-7.6.6-win-x64.zip' && pwshNames.filter((n) => pwshAsset('arm64').test(n)).join() === 'PowerShell-7.6.6-win-arm64.zip', 'PowerShell 7: the portable zip for the architecture (no MSI, fxdependent or x86 build)');
|
|
80
|
-
const binstallNames = ['cargo-binstall-x86_64-pc-windows-msvc.zip', 'cargo-binstall-x86_64-pc-windows-msvc.full.zip', 'cargo-binstall-x86_64-pc-windows-msvc.zip.sig', 'cargo-binstall-aarch64-pc-windows-msvc.zip', 'cargo-binstall-x86_64-unknown-linux-musl.tgz'];
|
|
81
|
-
check(binstallNames.filter((n) => binstallAsset('x64').test(n)).join() === 'cargo-binstall-x86_64-pc-windows-msvc.zip' && binstallNames.filter((n) => binstallAsset('arm64').test(n)).join() === 'cargo-binstall-aarch64-pc-windows-msvc.zip', 'cargo-binstall: the plain Windows zip for the architecture (not .full, not the signature)');
|
|
82
|
-
const cliAsset = (name, arch, names) => names.filter((n) => CLI_TOOLS.find((c) => c.name === name).asset(arch).test(n)).join();
|
|
83
|
-
const ghNames = ['gh_2.102.0_windows_amd64.zip', 'gh_2.102.0_windows_amd64.msi', 'gh_2.102.0_windows_386.zip', 'gh_2.102.0_windows_arm64.zip', 'gh_2.102.0_linux_amd64.tar.gz'];
|
|
84
|
-
const zstdNames = ['zstd-v1.5.7-win64.zip', 'zstd-v1.5.7-win32.zip', 'zstd-v1.5.7.tar.gz'];
|
|
85
|
-
check(cliAsset('gh', 'x64', ghNames) === 'gh_2.102.0_windows_amd64.zip' && cliAsset('gh', 'arm64', ghNames) === 'gh_2.102.0_windows_arm64.zip' && cliAsset('zstd', 'x64', zstdNames) === 'zstd-v1.5.7-win64.zip' && cliAsset('zstd', 'arm64', zstdNames) === 'zstd-v1.5.7-win64.zip' && CLI_TOOLS.every((c) => c.exe.endsWith('.exe')), 'CLI catalogue: gh and zstd release zips per architecture (zstd: win64 also on Arm)');
|
|
86
|
-
const cli = (settings, gitAccess, hasGithubDir = false) => Object.entries(decideCli(settings, { gitAccess, hasGithubDir })).filter(([, d]) => d.on).map(([n]) => n).join();
|
|
87
|
-
check(cli({ gh: true, zstd: true, openssl: true }, 'full') === 'gh,zstd,openssl' && cli({ gh: true, zstd: true }, 'readonly') === 'zstd' && cli({ gh: 'auto' }, 'none', true) === '' && decideCli({ gh: true }, { gitAccess: 'readonly', hasGithubDir: false }).gh.reason.includes('"full"'), 'CLI catalogue: gh only with git access "full" (it pushes through the API), whatever the config says');
|
|
88
|
-
check(cli({ gh: 'auto', zstd: 'auto', openssl: 'auto' }, 'full', true) === 'gh,openssl' && cli({ gh: 'auto', zstd: 'auto' }, 'full', false) === '' && cli({}, 'full', true) === '', 'CLI catalogue: "auto" = gh with a .github folder, openssl always (a shim), zstd only when listed; unlisted tools stay off');
|
|
89
|
-
const ps = (text, file = 'moh.ps1') => usesPwsh([{ path: file, text }]);
|
|
90
|
-
check(ps('#Requires -Version 7.2\nparam()') === 'moh.ps1: #Requires -Version 7' && ps(' #requires -PSEdition Core') !== null && ps('#Requires -Modules Pester -Version 7') !== null && ps('<#\n.SYNOPSIS\n Usage: pwsh scripts/moh/build-openmohaa.ps1 -Release\n#>') !== null && ps('& pwsh.exe -NoProfile -File x.ps1') !== null && ps('C:\\Program Files\\PowerShell\\7\\pwsh.exe -c 1') !== null, 'pwsh markers: #Requires -Version 7+ / -PSEdition Core, and pwsh usage lines');
|
|
91
|
-
check(ps('#Requires -Version 5.1') === null && ps('#Requires -PSEdition Desktop') === null && ps('# Works in Windows PowerShell and pwsh too.') === null && ps('$pwsh -x; Write-Host mypwsh -y') === null && ps('#Requires -Version 7', 'notes.txt') === null && ps('Write-Host hi') === null, 'pwsh markers: Windows PowerShell scripts, prose, variables and non-scripts do not count');
|
|
92
|
-
const evilStart = Date.now();
|
|
93
|
-
const evilText = (' #requires' + ' -version'.repeat(5000) + '\n' + 'xpwsh pwsh\t\t'.repeat(20000) + '\n' + ' '.repeat(100000) + 'pwsh\n' + 'pwsh\n'.repeat(50000)).repeat(3);
|
|
94
|
-
const evil = usesPwsh(Array.from({ length: 100 }, (_, i) => ({ path: 'a' + i + '.ps1', text: evilText })));
|
|
95
|
-
check(evil === null && Date.now() - evilStart < 2000, 'pwsh markers: hostile scripts (huge lines, endless repeats) are capped and scanned in linear time (' + (Date.now() - evilStart) + ' ms)');
|
|
96
|
-
check(decidePwsh('auto', [], true, true).on && !decidePwsh('auto', [], false, true).on && !decidePwsh('auto', [], true, false).on && decidePwsh('auto', [{ path: 'x/run.ps1', text: '#Requires -PSEdition Core' }], false, false).reason === 'x/run.ps1: #Requires -PSEdition Core' && !decidePwsh(false, [{ path: 'a.ps1', text: '#Requires -Version 7' }], true, true).on && decidePwsh(true, [], false, false).on, 'pwsh "auto": a script that needs it, else this PC has it (new folder only); true/false win');
|
|
97
|
-
const pf = 'C:\\PF';
|
|
98
|
-
check(pcHasPwsh({ ProgramFiles: pf }, (p) => p === 'C:\\PF\\PowerShell\\7\\pwsh.exe') && pcHasPwsh({ PATH: 'C:\\x;C:\\tools\\pwsh' }, (p) => p === 'C:\\tools\\pwsh\\pwsh.exe') && !pcHasPwsh({ ProgramFiles: pf, PATH: 'C:\\Users\\u\\AppData\\Local\\Microsoft\\WindowsApps' }, (p) => /WindowsApps/.test(p)), 'pwsh on this PC: Program Files\\PowerShell\\7 or PATH, not a Store alias');
|
|
99
|
-
const psRoot = path.join(t.temp, 'pwsh-scan');
|
|
100
|
-
write(path.join(psRoot, 'moh.ps1'), '#Requires -Version 7\n');
|
|
101
|
-
write(path.join(psRoot, 'scripts', 'build.ps1'), 'x'.repeat(200000) + '\n#Requires -Version 7');
|
|
102
|
-
write(path.join(psRoot, 'node_modules', 'pkg.ps1'), '#Requires -Version 7');
|
|
103
|
-
write(path.join(psRoot, 'scripts', 'deep', 'far.ps1'), '#Requires -Version 7');
|
|
104
|
-
write(path.join(psRoot, 'readme.md'), 'pwsh -File x.ps1');
|
|
105
|
-
const scanned = readPwshScripts(psRoot);
|
|
106
|
-
check(scanned.map((f) => f.path).join() === 'moh.ps1,scripts/build.ps1' && scanned[1].text.length === PWSH_MAX_BYTES, 'pwsh scan: *.ps1 at the root and one folder down only (node_modules skipped), each read up to its cap');
|
|
107
|
-
const shown = withPwshProfile({ theme: 'dark', profiles: { defaults: { font: {} } } }, true);
|
|
108
|
-
const shownList = shown?.profiles;
|
|
109
|
-
const tweaked = { profiles: { list: [{ guid: 'other' }, { guid: PWSH_PROFILE_GUID.toUpperCase(), name: 'My pwsh', fontSize: 14 }] } };
|
|
110
|
-
const hidden = withPwshProfile(tweaked, false)?.profiles;
|
|
111
|
-
check(shownList?.list.length === 1 && shownList.list[0].guid === PWSH_PROFILE_GUID && String(shownList.list[0].commandline).includes('pwsh.exe') && Boolean(shownList.defaults) && shown?.theme === 'dark' && withPwshProfile(shown, true) === null && withPwshProfile({ profiles: { defaults: {} } }, false) === null, 'Windows Terminal: a PowerShell 7 profile is added once pwsh is installed, nothing rewritten when already there or never wanted');
|
|
112
|
-
check(hidden?.list[1].hidden === true && hidden.list[1].name === 'My pwsh' && hidden.list[1].fontSize === 14 && hidden.list.length === 2 && (withPwshProfile({ profiles: [{ guid: 'a' }] }, true)?.profiles).list.length === 2, 'Windows Terminal: turning pwsh off hides the profile, keeping the user\'s edits; the old array form of "profiles" works');
|
|
113
|
-
const wtData = path.join(t.temp, 'wt-data');
|
|
114
|
-
const wtSettings = write(path.join(wtData, 'tools', 'terminal', 'settings', 'settings.json'), '// edited in the sandbox\n{ "theme": "light", "profiles": { "list": [{ "guid": "{x}", }] } }');
|
|
115
|
-
syncPwshProfile(wtData, true);
|
|
116
|
-
const before = fs.readFileSync(wtSettings, 'utf8');
|
|
117
|
-
write(path.join(wtData, 'tools', 'pwsh', 'pwsh.exe'), 'MZ');
|
|
118
|
-
syncPwshProfile(wtData, true);
|
|
119
|
-
const after = JSON.parse(fs.readFileSync(wtSettings, 'utf8'));
|
|
120
|
-
syncPwshProfile(wtData, false);
|
|
121
|
-
const off = JSON.parse(fs.readFileSync(wtSettings, 'utf8'));
|
|
122
|
-
write(path.join(wtData, 'tools', 'terminal', 'settings', 'settings.json'), '{ not json');
|
|
123
|
-
syncPwshProfile(wtData, true);
|
|
124
|
-
check(before.startsWith('// edited') && after.theme === 'light' && after.profiles.list.map((p) => p.guid).join() === '{x},' + PWSH_PROFILE_GUID && off.profiles.list[1].hidden === true && fs.readFileSync(wtSettings, 'utf8') === '{ not json', 'Windows Terminal settings.json (JSONC): untouched until pwsh is installed, then the profile is added, hidden when off; an unreadable file is left alone');
|
|
125
|
-
};
|