windows-development-sandbox 1.23.2 → 1.25.1
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/config.example.json +5 -3
- package/dist/app/features/data/data.def.d.ts +4 -4
- package/dist/app/features/data/data.run.d.ts +1 -1
- package/dist/app/features/defs.d.ts +161 -6
- package/dist/app/features/defs.js +7 -0
- package/dist/app/features/projects/projects.def.d.ts +1 -1
- package/dist/app/features/projects/projects.run.d.ts +1 -1
- package/dist/app/features/unreal/index.d.ts +2 -0
- package/dist/app/features/unreal/index.js +3 -0
- package/dist/app/features/unreal/unreal.def.d.ts +236 -0
- package/dist/app/features/unreal/unreal.def.js +379 -0
- package/dist/app/features/unreal/unreal.run.d.ts +96 -0
- package/dist/app/features/unreal/unreal.run.js +217 -0
- package/dist/app/registry.js +5 -0
- package/dist/bin/agent-sandbox.js +2 -2
- package/dist/bin/{chunk-7YPEDAJD.js → chunk-3GAXLATK.js} +58 -5
- package/dist/bin/{chunk-USTRSHIE.js → chunk-F4PVBN23.js} +2615 -1515
- package/dist/bin/{chunk-QFSJX2KW.js → chunk-G3TG4ZT7.js} +1 -1
- package/dist/bin/claude-host.js +2 -2
- package/dist/bin/claude-sbx.js +2 -2
- package/dist/bin/codex-sbx.js +2 -2
- package/dist/bin/gemini-sbx.js +2 -2
- package/dist/bin/sandbox.js +2 -2
- package/dist/bin/sbx-hide.js +2 -2
- package/dist/bin/sbx-show.js +2 -2
- package/dist/cli/commands/launch/launch.js +5 -2
- package/dist/cli/commands/registry.js +3 -0
- package/dist/cli/manifest.d.ts +18 -0
- package/dist/cli/manifest.js +26 -0
- package/dist/cli/menu/coverage-exempt.js +1 -0
- package/dist/cli/menu/pages/sandbox-page.d.ts +3 -0
- package/dist/cli/menu/pages/sandbox-page.js +34 -4
- package/dist/cli/menu/views.js +2 -0
- package/dist/core/config/defaults.js +18 -5
- package/dist/core/detect/toolchains.js +0 -6
- package/dist/core/schema/config.d.ts +10 -9
- package/dist/core/schema/pc-installs.d.ts +3 -1
- package/dist/core/schema/pc-installs.js +10 -1
- package/dist/core/schema/toolchains.d.ts +1 -2
- package/dist/core/schema/toolchains.js +4 -7
- package/dist/guest/accounts/kinds/epic.js +18 -13
- package/dist/guest/agents/agent-files.d.ts +11 -7
- package/dist/guest/agents/agent-files.js +37 -11
- package/dist/guest/agents/local-state.d.ts +2 -1
- package/dist/guest/agents/local-state.js +2 -1
- package/dist/guest/apps/after-install.d.ts +1 -1
- package/dist/guest/apps/after-install.js +6 -1
- package/dist/guest/apps/app-installer.d.ts +1 -0
- package/dist/guest/apps/app-installer.js +13 -3
- package/dist/guest/apps/comet-unpack.js +3 -2
- package/dist/guest/apps/resolvers-gamedev.js +55 -1
- package/dist/guest/boot/boot-console.d.ts +28 -0
- package/dist/guest/boot/boot-console.js +98 -0
- package/dist/guest/boot/boot.d.ts +8 -1
- package/dist/guest/boot/boot.js +80 -43
- package/dist/guest/boot/graph.d.ts +9 -5
- package/dist/guest/boot/graph.js +8 -2
- package/dist/guest/boot/guide.js +24 -0
- package/dist/guest/boot/progress-view.d.ts +116 -0
- package/dist/guest/boot/progress-view.js +456 -0
- package/dist/guest/boot/runner.d.ts +20 -10
- package/dist/guest/boot/runner.js +39 -47
- package/dist/guest/daemon/daemon.js +3 -0
- package/dist/guest/runtime/log-line.d.ts +7 -0
- package/dist/guest/runtime/log-line.js +18 -0
- package/dist/guest/runtime/log.d.ts +17 -4
- package/dist/guest/runtime/log.js +40 -17
- package/dist/guest/runtime/process.d.ts +9 -0
- package/dist/guest/runtime/process.js +69 -8
- package/dist/guest/setup/addon-mcp.d.ts +7 -0
- package/dist/guest/setup/addon-mcp.js +19 -4
- package/dist/guest/setup/browser-gpu.js +14 -11
- package/dist/guest/setup/environment.js +9 -4
- package/dist/guest/setup/headroom.js +4 -0
- package/dist/guest/setup/hooks.d.ts +2 -0
- package/dist/guest/setup/hooks.js +4 -0
- package/dist/guest/setup/path-plan.d.ts +12 -0
- package/dist/guest/setup/path-plan.js +18 -0
- package/dist/guest/toolchains/engines/epic-launcher.d.ts +26 -0
- package/dist/guest/toolchains/engines/epic-launcher.js +127 -0
- package/dist/guest/toolchains/engines/unreal-engines.d.ts +21 -1
- package/dist/guest/toolchains/engines/unreal-engines.js +163 -3
- package/dist/guest/toolchains/engines/unreal-status.d.ts +13 -0
- package/dist/guest/toolchains/engines/unreal-status.js +41 -0
- package/dist/guest/toolchains/native/debuggers.d.ts +6 -0
- package/dist/guest/toolchains/native/debuggers.js +57 -0
- package/dist/guest/toolchains/native/mesa.js +15 -8
- package/dist/guest/toolchains/native/rust-extras.d.ts +7 -0
- package/dist/guest/toolchains/native/rust-extras.js +23 -1
- package/dist/guest/toolchains/native/unreal.d.ts +2 -2
- package/dist/guest/toolchains/native/unreal.js +96 -20
- package/dist/guest/toolchains/native/visual-studio.d.ts +14 -14
- package/dist/guest/toolchains/native/visual-studio.js +145 -174
- package/dist/guest/toolchains/native/vs-persist.d.ts +189 -0
- package/dist/guest/toolchains/native/vs-persist.js +256 -0
- package/dist/guest/toolchains/native/vs-request.d.ts +6 -26
- package/dist/guest/toolchains/native/vs-request.js +16 -50
- package/dist/guest/toolchains/plan.js +23 -15
- package/dist/guest/toolchains/tools/vscode-profile.js +2 -0
- package/dist/host/accounts/kinds/epic.js +11 -5
- package/dist/host/accounts/source.d.ts +2 -0
- package/dist/host/accounts/source.js +1 -0
- package/dist/host/migrate/migrations/drop-vs-install-files.d.ts +5 -0
- package/dist/host/migrate/migrations/drop-vs-install-files.js +34 -0
- package/dist/host/migrate/registry.js +7 -1
- package/dist/host/pc-installs/discover.d.ts +2 -0
- package/dist/host/pc-installs/discover.js +25 -5
- package/dist/host/pc-installs/epic.d.ts +3 -0
- package/dist/host/pc-installs/epic.js +33 -2
- package/dist/host/pc-installs/source.d.ts +1 -1
- package/dist/host/pc-installs/unreal-vault.d.ts +11 -0
- package/dist/host/pc-installs/unreal-vault.js +78 -0
- package/dist/host/pc-installs/unreal.d.ts +36 -0
- package/dist/host/pc-installs/unreal.js +145 -2
- package/dist/host/profile/claude-profile.d.ts +8 -0
- package/dist/host/profile/claude-profile.js +43 -4
- package/dist/host/profile/index.d.ts +1 -1
- package/dist/host/profile/index.js +1 -1
- package/dist/runtime/agent.js +148 -33
- package/dist/runtime/daemon.js +3146 -1546
- package/dist/runtime/db.js +148 -33
- package/dist/runtime/main.js +3982 -2049
- package/dist/runtime/netproxy.js +84 -13
- package/dist/runtime/tool.js +125 -31
- package/dist/runtime/view.js +85 -14
- package/dist/selftest/agent-paths.test.js +18 -1
- package/dist/selftest/agent-settings.test.js +36 -5
- package/dist/selftest/agents-web.test.js +12 -0
- package/dist/selftest/boot-console.test.d.ts +2 -0
- package/dist/selftest/boot-console.test.js +291 -0
- package/dist/selftest/boot-runner.test.js +10 -12
- package/dist/selftest/engines.test.js +602 -3
- package/dist/selftest/guest-boot.test.js +7 -1
- package/dist/selftest/heavy-load.test.js +3 -1
- package/dist/selftest/hostile-input.test.js +11 -1
- package/dist/selftest/index.js +4 -0
- package/dist/selftest/layout.test.js +4 -0
- package/dist/selftest/menu-screens.js +3 -1
- package/dist/selftest/migrate.test.js +21 -0
- package/dist/selftest/mixamo.test.d.ts +2 -0
- package/dist/selftest/mixamo.test.js +60 -0
- package/dist/selftest/persisted-installs.test.js +169 -33
- package/dist/selftest/secrets.test.js +36 -1
- package/dist/selftest/support/engine-golden.js +5 -0
- package/dist/selftest/support/fixtures.d.ts +4 -3
- package/dist/selftest/support/unreal-sample.d.ts +2 -0
- package/dist/selftest/support/unreal-sample.js +83 -0
- package/dist/selftest/toolchains.test.js +32 -2
- package/dist/shared/accounts/index.d.ts +1 -1
- package/dist/shared/accounts/index.js +1 -1
- package/dist/shared/accounts/kinds/epic.d.ts +6 -1
- package/dist/shared/accounts/kinds/epic.js +11 -1
- package/dist/shared/apps/associations.d.ts +7 -0
- package/dist/shared/apps/associations.js +26 -0
- package/dist/shared/apps/catalogue-agents-web.d.ts +4 -1
- package/dist/shared/apps/catalogue-agents-web.js +5 -2
- package/dist/shared/apps/catalogue-gamedev.js +71 -1
- package/dist/shared/apps/index.d.ts +2 -0
- package/dist/shared/apps/index.js +2 -0
- package/dist/shared/apps/preview.d.ts +2 -0
- package/dist/shared/apps/preview.js +10 -0
- package/dist/shared/browser-policy.d.ts +14 -0
- package/dist/shared/browser-policy.js +22 -0
- package/dist/shared/channel/schema.d.ts +3 -6
- package/dist/shared/io/index.d.ts +1 -0
- package/dist/shared/io/index.js +1 -0
- package/dist/shared/io/rotate-log.d.ts +2 -0
- package/dist/shared/io/rotate-log.js +16 -0
- package/dist/shared/memory/policy.d.ts +5 -0
- package/dist/shared/memory/policy.js +7 -0
- package/dist/shared/paths/pc-installs.d.ts +13 -2
- package/dist/shared/paths/pc-installs.js +1 -0
- package/dist/shared/pty/terminal.js +2 -1
- package/dist/shared/readiness.d.ts +0 -1
- package/dist/shared/readiness.js +0 -1
- package/dist/shared/signer.d.ts +4 -0
- package/dist/shared/signer.js +2 -0
- package/dist/shared/unreal/engines.d.ts +161 -0
- package/dist/shared/unreal/engines.js +308 -0
- package/dist/shared/unreal/guide.d.ts +19 -0
- package/dist/shared/unreal/guide.js +105 -0
- package/dist/shared/unreal/index.d.ts +7 -0
- package/dist/shared/unreal/index.js +8 -0
- package/dist/shared/unreal/launcher-files.d.ts +88 -0
- package/dist/shared/unreal/launcher-files.js +292 -0
- package/dist/shared/unreal/mixamo.d.ts +5 -0
- package/dist/shared/unreal/mixamo.js +16 -0
- package/dist/shared/unreal/room.d.ts +17 -0
- package/dist/shared/unreal/room.js +34 -0
- package/dist/shared/unreal/status.d.ts +54 -0
- package/dist/shared/unreal/status.js +57 -0
- package/dist/shared/unreal/vault.d.ts +21 -0
- package/dist/shared/unreal/vault.js +67 -0
- package/dist/shared/unreal/vs-check.d.ts +39 -0
- package/dist/shared/unreal/vs-check.js +79 -0
- package/dist/sync/watcher.js +5 -1
- package/dist/web/index.html +1 -1
- package/dist/web/{main-CCILLX2M.js → main-FNE6XEHA.js} +84 -84
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
- package/package.json +1 -1
- package/sandbox/csharp/VsSetup.cs +210 -0
- package/sandbox/scripts/get-vs-setup.ps1 +93 -0
- package/sandbox/scripts/restore-visual-studio.ps1 +78 -0
- package/sandbox/scripts/save-visual-studio.ps1 +106 -0
- package/sandbox/scripts/set-registry-values.ps1 +35 -0
- package/schemas/config.schema.json +38 -9
- package/schemas/sandbox-config.schema.json +11 -8
- package/dist/guest/toolchains/native/autosdk.d.ts +0 -3
- package/dist/guest/toolchains/native/autosdk.js +0 -50
|
@@ -8,7 +8,7 @@ import { DEFAULTS } from '../core/config/index.js';
|
|
|
8
8
|
import { isWin } from '../core/util/index.js';
|
|
9
9
|
import { SYSTEM } from '../shared/system.js';
|
|
10
10
|
import { GuestRequestSchema, PressureStatusSchema } from '../shared/channel/index.js';
|
|
11
|
-
import { compilersAllowed, DIAL_CALM_SAMPLES, DIAL_STEPS, HOST_THRESHOLDS, buildJobs, cargoJobs, linkSlotsFor, usableCpus, effectiveCpuLimit, gentleCpuLimit, maxCompilers, chooseTargetDir, } from '../shared/memory/index.js';
|
|
11
|
+
import { compilersAllowed, DIAL_CALM_SAMPLES, DIAL_STEPS, HOST_THRESHOLDS, buildJobs, cargoJobs, linkSlotsFor, usableCpus, effectiveCpuLimit, gentleCpuLimit, maxCompilers, chooseTargetDir, cargoLinkerVar, } from '../shared/memory/index.js';
|
|
12
12
|
import { HeartbeatSchema } from '../shared/channel/index.js';
|
|
13
13
|
import { heartbeatLine, lastEndOf, vanishedCause, stopWarning } from '../host/life/index.js';
|
|
14
14
|
import { cappedMemoryMb, decideMemory, memoryNote, residentCapMb, residentNote, resolveMemoryMb, } from '../host/launch/index.js';
|
|
@@ -240,6 +240,8 @@ export const heavyLoadSuite = {
|
|
|
240
240
|
chooseTargetDir('auto', 10 * 1024 ** 3).targetDir === 'separate' &&
|
|
241
241
|
Boolean(chooseTargetDir('auto', 10 * 1024 ** 3).warning) &&
|
|
242
242
|
chooseTargetDir('shared', 1).targetDir === 'shared', 'targetDir "auto": local with 25 GB free on C:, else separate with a warning; other values stay');
|
|
243
|
+
t.check(cargoLinkerVar('x86_64-pc-windows-msvc') === 'CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER' &&
|
|
244
|
+
cargoLinkerVar('aarch64-pc-windows-msvc') === 'CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER', 'cargo linker variable names the host triple (the msvc host links with lld, not link.exe)');
|
|
243
245
|
const beat = {
|
|
244
246
|
at: '2026-11-02T21:31:00.000Z',
|
|
245
247
|
uptimeS: 2800,
|
|
@@ -8,7 +8,7 @@ import { assertSafeMapping } from '../core/safe-paths.js';
|
|
|
8
8
|
import { safeText } from '../core/util/index.js';
|
|
9
9
|
import { isSafeFileName } from '../shared/paths/index.js';
|
|
10
10
|
import { removeRefusal, safeRemove } from '../shared/io/index.js';
|
|
11
|
-
import { writeJsonAtomic } from '../shared/io/index.js';
|
|
11
|
+
import { rotateLog, writeJsonAtomic } from '../shared/io/index.js';
|
|
12
12
|
export const hostileInputSuite = {
|
|
13
13
|
name: 'hostile-input',
|
|
14
14
|
title: 'file names, deletes, printed text, mappings and state files from an untrusted sandbox',
|
|
@@ -63,5 +63,15 @@ export const hostileInputSuite = {
|
|
|
63
63
|
fs.writeFileSync(path.join(repoDir, '.sandbox-config.json'), '{"readOnly": true}' + ' '.repeat(1100 * 1024));
|
|
64
64
|
await t.throws(() => f.run({ cwd: repoDir, trustConfig: true }), 'over 1 MB', 'a .sandbox-config.json over 1 MB is refused before it is parsed');
|
|
65
65
|
fs.rmSync(path.join(repoDir, '.sandbox-config.json'));
|
|
66
|
+
t.section('a log that grows for ever (Headroom appends to one on the data disk)');
|
|
67
|
+
const logFile = t.at('rotate', 'proxy.log');
|
|
68
|
+
fs.mkdirSync(path.dirname(logFile), { recursive: true });
|
|
69
|
+
t.check(!rotateLog(logFile, 10), 'no log yet: nothing to rotate');
|
|
70
|
+
fs.writeFileSync(logFile, 'x'.repeat(10));
|
|
71
|
+
t.check(!rotateLog(logFile, 10) && fs.existsSync(logFile), 'a log at the limit stays');
|
|
72
|
+
fs.writeFileSync(logFile + '.1', 'old');
|
|
73
|
+
fs.writeFileSync(logFile, 'y'.repeat(11));
|
|
74
|
+
t.check(rotateLog(logFile, 10), 'a log over the limit is rotated');
|
|
75
|
+
t.check(!fs.existsSync(logFile) && fs.readFileSync(logFile + '.1', 'utf8') === 'y'.repeat(11), 'it replaces the older copy');
|
|
66
76
|
},
|
|
67
77
|
};
|
package/dist/selftest/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { persistedInstallsSuite } from './persisted-installs.test.js';
|
|
|
16
16
|
import { pcInstallsSuite } from './pc-installs.test.js';
|
|
17
17
|
import { steamSuite } from './steam.test.js';
|
|
18
18
|
import { enginesSuite } from './engines.test.js';
|
|
19
|
+
import { mixamoSuite } from './mixamo.test.js';
|
|
19
20
|
import { unitySuite } from './unity.test.js';
|
|
20
21
|
import { fivemSuite } from './fivem.test.js';
|
|
21
22
|
import { androidSuite } from './android.test.js';
|
|
@@ -80,6 +81,7 @@ import { uiTextSuite } from './ui-text.test.js';
|
|
|
80
81
|
import { menuCoverageSuite } from './menu-coverage.test.js';
|
|
81
82
|
import { auditSuite } from './audit.test.js';
|
|
82
83
|
import { bootRunnerSuite } from './boot-runner.test.js';
|
|
84
|
+
import { bootConsoleSuite } from './boot-console.test.js';
|
|
83
85
|
import { layoutSuite } from './layout.test.js';
|
|
84
86
|
import { menuSuite } from './menu.test.js';
|
|
85
87
|
import { menuPerfSuite } from './menu-perf.test.js';
|
|
@@ -133,10 +135,12 @@ export const SUITES = [
|
|
|
133
135
|
// pure helpers and the command line
|
|
134
136
|
layoutSuite,
|
|
135
137
|
bootRunnerSuite,
|
|
138
|
+
bootConsoleSuite,
|
|
136
139
|
toolchainsSuite,
|
|
137
140
|
persistedInstallsSuite,
|
|
138
141
|
steamSuite,
|
|
139
142
|
enginesSuite,
|
|
143
|
+
mixamoSuite,
|
|
140
144
|
unitySuite,
|
|
141
145
|
fivemSuite,
|
|
142
146
|
androidSuite,
|
|
@@ -19,6 +19,8 @@ const PURE_GUEST = [
|
|
|
19
19
|
'guest/boot/project-env.js',
|
|
20
20
|
'guest/boot/runner.js',
|
|
21
21
|
'guest/boot/graph.js',
|
|
22
|
+
'guest/boot/progress-view.js',
|
|
23
|
+
'guest/runtime/log-line.js',
|
|
22
24
|
'guest/toolchains/split.js',
|
|
23
25
|
'guest/toolchains/step-scheduler.js',
|
|
24
26
|
'guest/audit/parse.js',
|
|
@@ -30,6 +32,8 @@ const PURE_GUEST = [
|
|
|
30
32
|
'guest/governor/decide.js',
|
|
31
33
|
'guest/governor/core.js',
|
|
32
34
|
'guest/daemon/live-folders.js',
|
|
35
|
+
'guest/toolchains/native/vs-request.js',
|
|
36
|
+
'guest/toolchains/native/vs-persist.js',
|
|
33
37
|
];
|
|
34
38
|
const SIDE_EFFECTS = ['guest/runtime/session.js', 'guest/runtime/log.js'];
|
|
35
39
|
const readModules = (dist) => {
|
|
@@ -10,6 +10,7 @@ import { appsOptions, stageTicks } from '../cli/menu/pages/apps-page.js';
|
|
|
10
10
|
import { maintenanceOptions } from '../cli/menu/pages/maintenance-page.js';
|
|
11
11
|
import { sandboxOptions } from '../cli/menu/pages/sandbox-page.js';
|
|
12
12
|
import { sessionsOptions } from '../cli/menu/pages/sessions-page.js';
|
|
13
|
+
import { UNREAL_SAMPLE } from './support/unreal-sample.js';
|
|
13
14
|
import { settingsOf, settingsOptions } from '../cli/menu/pages/settings-page.js';
|
|
14
15
|
import { CATEGORIES } from '../app/features/settings/index.js';
|
|
15
16
|
import { agentsOptions } from '../cli/menu/pages/agents-page.js';
|
|
@@ -199,6 +200,7 @@ export function renderScreens(dir) {
|
|
|
199
200
|
{ name: 'redis', running: false, port: 6379, title: 'Redis' },
|
|
200
201
|
],
|
|
201
202
|
defaultAgent: 'claude',
|
|
203
|
+
unreal: UNREAL_SAMPLE,
|
|
202
204
|
};
|
|
203
205
|
const stopped = {
|
|
204
206
|
...sandbox,
|
|
@@ -399,7 +401,7 @@ export function renderScreens(dir) {
|
|
|
399
401
|
out.push(block('1 Agents · Start', draw(0, { ...agentsOptions(agentsModel, API_FOLDER), startCategory: 'start' }, size, status), size), block('1 Agents · Options (navigation focused)', draw(0, { ...agentsOptions(agentsModel, API_FOLDER), startCategory: 'options' }, size, status, 0, true), size), block('1 Agents · On this PC', draw(0, { ...agentsOptions(agentsModel, API_FOLDER), startCategory: 'host' }, size, status), size), block('2 Sessions', draw(1, sessionsOptions({ sandboxRunning: true, sessions: SESSIONS, defaultAgent: 'claude' }), size, status), size), block('2 Sessions (no sandbox)', draw(1, sessionsOptions({ sandboxRunning: false, sessions: [], defaultAgent: 'claude' }), size), size));
|
|
400
402
|
for (const category of ['status', 'databases'])
|
|
401
403
|
out.push(block('3 Sandbox (stopped) · ' + category, draw(2, { ...sandboxOptions(stopped), startCategory: category }, size), size));
|
|
402
|
-
for (const category of ['status', 'projects', 'databases'])
|
|
404
|
+
for (const category of ['status', 'projects', 'databases', 'unreal'])
|
|
403
405
|
out.push(block('3 Sandbox · ' + category, draw(2, { ...sandboxOptions(sandbox), startCategory: category }, size, status), size));
|
|
404
406
|
out.push(block('4 Apps (1 unsaved)', draw(3, appsOptions(user, appsData, appsDraft, 'user'), size, status, appsDraft.count()), size));
|
|
405
407
|
for (const category of CATEGORIES)
|
|
@@ -159,6 +159,27 @@ export const migrateSuite = {
|
|
|
159
159
|
finally {
|
|
160
160
|
fs.rmSync(machineFile);
|
|
161
161
|
}
|
|
162
|
+
t.section('drop-vs-install-files');
|
|
163
|
+
const dropVs = MIGRATIONS.find((m) => m.id === '2026-11-drop-vs-install-files');
|
|
164
|
+
if (!dropVs)
|
|
165
|
+
return t.check(false, 'the drop-vs-install-files migration exists');
|
|
166
|
+
const oldVs = t.write(t.at('vs', 'old.json'), '// mine\n' +
|
|
167
|
+
JSON.stringify({
|
|
168
|
+
toolchains: { unreal: { enabled: true, keepInstallFiles: true, visualStudio: 'autosdk', components: ['X'] } },
|
|
169
|
+
}));
|
|
170
|
+
const fullVs = t.write(t.at('vs', 'full.json'), JSON.stringify({ toolchains: { unreal: { visualStudio: 'full' } } }));
|
|
171
|
+
const vsNotes = [];
|
|
172
|
+
const vsWanted = dropVs.applies(ctx(t.at('vs'), oldVs)) && !dropVs.applies(ctx(t.at('vs'), fullVs));
|
|
173
|
+
await dropVs.run(ctx(t.at('vs'), oldVs), (line) => vsNotes.push(line));
|
|
174
|
+
const vsAfter = parse(readText(oldVs));
|
|
175
|
+
t.check(vsWanted &&
|
|
176
|
+
vsAfter.toolchains.unreal.keepInstallFiles === undefined &&
|
|
177
|
+
vsAfter.toolchains.unreal.visualStudio === undefined &&
|
|
178
|
+
vsAfter.toolchains.unreal.enabled === true &&
|
|
179
|
+
readText(oldVs).includes('// mine') &&
|
|
180
|
+
vsNotes.join().includes('toolchains.unreal.keepInstallFiles') &&
|
|
181
|
+
loadConfig(oldVs).config.toolchains.unreal.visualStudio === 'full' &&
|
|
182
|
+
!dropVs.applies(ctx(t.at('vs'), oldVs)), 'the retired Visual Studio settings are removed (a chosen "full" stays), the file loads again, and it is idempotent');
|
|
162
183
|
t.section('sandbox migrate');
|
|
163
184
|
const cmdUser = t.dir('cmd-user');
|
|
164
185
|
const cmdConfig = t.write(t.at('cmd-user', 'config.json'), JSON.stringify({ ...JSON.parse(JSON.stringify(old.defaults)), sandbox: { memoryMb: 4096 } }, null, 2));
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Mixamo: the browser policy table (downloads folder, no GPU) and the guide's Mixamo lines and editor names.
|
|
2
|
+
import { browserPolicies } from '../shared/browser-policy.js';
|
|
3
|
+
import { unrealGuideLines } from '../shared/unreal/guide.js';
|
|
4
|
+
const DOWNLOADS = 'C:\\AgentData\\downloads';
|
|
5
|
+
const PROJECT = { workspace: 'C:\\P\\Game', uproject: 'C:\\P\\Game\\Game.uproject' };
|
|
6
|
+
function statusWith(version) {
|
|
7
|
+
return {
|
|
8
|
+
updatedAt: 'now',
|
|
9
|
+
engines: [
|
|
10
|
+
{
|
|
11
|
+
id: 'UE_' + version,
|
|
12
|
+
dir: 'C:\\E',
|
|
13
|
+
version,
|
|
14
|
+
build: 'installed',
|
|
15
|
+
origin: 'pc',
|
|
16
|
+
writable: true,
|
|
17
|
+
sizeBytes: null,
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
projects: [{ workspace: PROJECT.workspace, engine: 'C:\\E', reason: 'test' }],
|
|
21
|
+
launcher: { installed: false, installLocation: null, problem: null, freeBytes: null, needBytes: 1 },
|
|
22
|
+
visualStudio: { version: '17.14', path: 'C:/VS', how: 'kept', check: 'ok', ok: true },
|
|
23
|
+
debugger: null,
|
|
24
|
+
ddc: { mode: 'persisted', dir: null },
|
|
25
|
+
vaults: [],
|
|
26
|
+
mcpUrl: null,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export const mixamoSuite = {
|
|
30
|
+
name: 'mixamo',
|
|
31
|
+
title: 'Mixamo: browser downloads folder and the guide lines for bringing characters into Unreal',
|
|
32
|
+
run: async (t) => {
|
|
33
|
+
t.section('browser policies');
|
|
34
|
+
const four = browserPolicies({ downloads: DOWNLOADS });
|
|
35
|
+
t.equal(four.length, 4, 'two browsers, two policies each');
|
|
36
|
+
t.equal(four.filter((p) => p.name === 'DefaultDownloadDirectory').map((p) => p.data), [DOWNLOADS, DOWNLOADS], 'both browsers download to the data disk');
|
|
37
|
+
t.equal(browserPolicies({ downloads: null }).length, 2, 'no folder: only the GPU policy');
|
|
38
|
+
await t.throws(() => browserPolicies({ downloads: 'C:\\a"b' }), 'not a folder', 'a quote in the folder is refused');
|
|
39
|
+
await t.throws(() => browserPolicies({ downloads: 'downloads' }), 'not a folder', 'a relative folder is refused');
|
|
40
|
+
t.section('the guide');
|
|
41
|
+
const on = { mixamo: { downloads: DOWNLOADS, script: null } };
|
|
42
|
+
const guide = unrealGuideLines(statusWith('5.4.4'), [PROJECT], on).join('\n');
|
|
43
|
+
t.includes(guide, 'https://www.mixamo.com', 'the Mixamo line is there');
|
|
44
|
+
t.includes(guide, DOWNLOADS, 'it names the downloads folder');
|
|
45
|
+
t.includes(guide, 'Mixamo into Unreal', 'and the import line');
|
|
46
|
+
t.excludes(guide, 'Mixamo import script', 'no script line without a script');
|
|
47
|
+
const withScript = unrealGuideLines(statusWith('5.4.4'), [PROJECT], {
|
|
48
|
+
mixamo: { downloads: DOWNLOADS, script: 'C:\\x\\import_mixamo.py' },
|
|
49
|
+
}).join('\n');
|
|
50
|
+
t.includes(withScript, 'Mixamo import script', 'a script adds its line');
|
|
51
|
+
t.excludes(unrealGuideLines(statusWith('5.4.4'), [PROJECT]).join('\n'), 'Mixamo', 'off by default');
|
|
52
|
+
const noEngine = { ...statusWith('5.4.4'), engines: [] };
|
|
53
|
+
t.excludes(unrealGuideLines(noEngine, [PROJECT], on).join('\n'), 'Mixamo', 'no engine: no Mixamo lines');
|
|
54
|
+
t.section('UE4 names');
|
|
55
|
+
const ue4 = unrealGuideLines(statusWith('4.27.2'), [PROJECT]).join('\n');
|
|
56
|
+
t.includes(ue4, 'UE4Editor-Cmd.exe', 'a UE4 engine uses UE4Editor-Cmd.exe');
|
|
57
|
+
t.excludes(ue4, 'UnrealEditor', 'and not the UE5 names');
|
|
58
|
+
t.includes(unrealGuideLines(statusWith('5.4.4'), [PROJECT]).join('\n'), 'UnrealEditor-Cmd.exe', 'UE5 keeps its names');
|
|
59
|
+
},
|
|
60
|
+
};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// Large installs kept between sandboxes (guest/toolchains/persisted.ts): the staging folder and its TEMP, the marker,
|
|
2
2
|
// the refusal when the disk is short (an injected statfs), keeping only keepVersions, the records `sandbox data`
|
|
3
|
-
// reads,
|
|
3
|
+
// reads, which Visual Studio the toolchains ask for (vs-request.ts), and how it is saved on the data disk and put
|
|
4
|
+
// back (vs-persist.ts: folders, marker, instance state, registry). Temp folders only.
|
|
4
5
|
import fs from 'node:fs';
|
|
5
6
|
import path from 'node:path';
|
|
6
7
|
import { DEFAULTS } from '../core/config/index.js';
|
|
7
8
|
import { assertLocalRoom, ensurePersisted, installRecords, InstallRecordsSchema, LOCAL_RESERVE_BYTES, recordUse, } from '../guest/toolchains/persisted.js';
|
|
8
|
-
import { BUILD_TOOLS_COMPONENTS, UNREAL_COMPONENTS, visualStudioWanted, } from '../guest/toolchains/native/vs-request.js';
|
|
9
|
-
import {
|
|
9
|
+
import { BUILD_TOOLS_COMPONENTS, UNREAL_BUILD_TOOLS_COMPONENTS, UNREAL_COMPONENTS, visualStudioWanted, vsExitText, } from '../guest/toolchains/native/vs-request.js';
|
|
10
|
+
import { markerFrom, newestInstance, persistCovers, rebase, registryPlan, restoreInput, rewriteInstanceState, SaveResultSchema, saveInput, SETUP_CLSID, VsPersistMarkerSchema, } from '../guest/toolchains/native/vs-persist.js';
|
|
10
11
|
import { freeBytesAt } from '../shared/io/disk.js';
|
|
11
12
|
import { SIGNERS, signerMatches } from '../shared/signer.js';
|
|
12
13
|
import { decideToolchains } from '../core/detect/toolchains.js';
|
|
@@ -17,7 +18,7 @@ import { collectDataEntries } from '../cli/services/data-collect.js';
|
|
|
17
18
|
const GB = 1024 ** 3;
|
|
18
19
|
export const persistedInstallsSuite = {
|
|
19
20
|
name: 'persisted-installs',
|
|
20
|
-
title: 'large persisted installs: staging, marker, room checks, pruning, records;
|
|
21
|
+
title: 'large persisted installs: staging, marker, room checks, pruning, records; Visual Studio: request, save, restore',
|
|
21
22
|
run: async (t) => {
|
|
22
23
|
t.section('installing a version');
|
|
23
24
|
const tools = t.dir('persisted-tools');
|
|
@@ -110,28 +111,172 @@ export const persistedInstallsSuite = {
|
|
|
110
111
|
all.for.join() === 'unreal,flutter,unity' &&
|
|
111
112
|
UNREAL_COMPONENTS.every((c) => all.components.includes(c)) &&
|
|
112
113
|
all.components.includes('Microsoft.VisualStudio.Component.VC.Llvm.Clang'), 'with Unreal: one Community install serves all of them');
|
|
113
|
-
t.section('Unreal on the
|
|
114
|
-
tc.unreal.visualStudio = '
|
|
115
|
-
const
|
|
116
|
-
t.check(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
t.
|
|
123
|
-
pairs[0].to === 'T:\\autosdk\\HostWin64\\Win64\\VS2022\\14.44.35207' &&
|
|
124
|
-
pairs[0].from === 'T:\\msvc\\VC\\Tools\\MSVC\\14.44.35207' &&
|
|
125
|
-
pairs[1].from === 'T:\\msvc\\Windows Kits\\10\\Include\\10.0.26100.0' &&
|
|
126
|
-
pairs[1].to === 'T:\\autosdk\\HostWin64\\Win64\\Windows Kits\\10\\Include\\10.0.26100.0', 'the AutoSDK tree is MSVC under VS2022\\<version> and the SDK under Windows Kits\\10 (copies)');
|
|
114
|
+
t.section('Unreal on the Build Tools');
|
|
115
|
+
tc.unreal.visualStudio = 'buildtools';
|
|
116
|
+
const bt = visualStudioWanted(tc);
|
|
117
|
+
t.check(bt?.product === 'buildtools' &&
|
|
118
|
+
bt.for.join() === 'unreal,flutter,unity' &&
|
|
119
|
+
bt.licence === 'toolchains.unreal.acceptLicense' &&
|
|
120
|
+
UNREAL_BUILD_TOOLS_COMPONENTS.every((c) => bt.components.includes(c)) &&
|
|
121
|
+
BUILD_TOOLS_COMPONENTS.every((c) => bt.components.includes(c)) &&
|
|
122
|
+
bt.components.includes('Microsoft.VisualStudio.Component.VC.Llvm.Clang'), '"buildtools": the Build Tools with what UnrealBuildTool needs, shared with Flutter and Unity');
|
|
123
|
+
t.equal(DEFAULTS.toolchains.unreal.visualStudio, 'full', 'Community is the default (the IDE, its debugger and Unreal integration)');
|
|
127
124
|
const on = DEFAULTS.toolchains;
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
125
|
+
const decided = decideToolchains({ ...structuredClone(on), unreal: { ...on.unreal, acceptLicense: true } }, ['Game.uproject'], []);
|
|
126
|
+
t.check(decided.unreal.on && !decided.msvc.on, 'an Unreal project does not turn the portable MSVC on (Visual Studio brings MSVC)');
|
|
127
|
+
t.section('Visual Studio kept on the data disk');
|
|
128
|
+
const places = {
|
|
129
|
+
programFiles: 'C:\\Program Files',
|
|
130
|
+
programFilesX86: 'C:\\Program Files (x86)',
|
|
131
|
+
programData: 'C:\\ProgramData',
|
|
131
132
|
};
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
const root = 'C:\\AgentData\\tools\\visualstudio\\persist';
|
|
134
|
+
const community = 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Community';
|
|
135
|
+
const instances = 'C:\\ProgramData\\Microsoft\\VisualStudio\\Packages\\_Instances';
|
|
136
|
+
const vsAt = { product: 'community', installationPath: community, instanceId: 'a190d188' };
|
|
137
|
+
const save = saveInput(vsAt, root, places, 'T:\\result.json');
|
|
138
|
+
const saved = (name) => save.copies.find((c) => c.name === name);
|
|
139
|
+
t.check(saved('Community')?.from === community &&
|
|
140
|
+
saved('Community')?.to === root + '\\Community' &&
|
|
141
|
+
saved('WindowsKits10')?.from === 'C:\\Program Files (x86)\\Windows Kits\\10' &&
|
|
142
|
+
saved('instance')?.from === instances + '\\a190d188' &&
|
|
143
|
+
saved('instance')?.to === root + '\\instance' &&
|
|
144
|
+
saved('Setup')?.from === 'C:\\ProgramData\\Microsoft\\VisualStudio\\Setup' &&
|
|
145
|
+
saved('Installer')?.optional === false &&
|
|
146
|
+
saved('ReferenceAssemblies')?.optional === true &&
|
|
147
|
+
save.resultFile === 'T:\\result.json', 'the capture mirrors the install, the Windows SDK, the installer, the Setup API and the instance onto the data disk');
|
|
148
|
+
t.check(save.registry.length === 8 &&
|
|
149
|
+
save.registry.filter((r) => r.view === '32').length === 4 &&
|
|
150
|
+
save.registry.some((r) => r.key.endsWith('NETFXSDK') && r.recurse) &&
|
|
151
|
+
save.registry.some((r) => r.key.endsWith('Installed Roots') && r.names?.join() === 'KitsRoot10'), 'it reads the SDK registry values in both views (NETFXSDK with its subkeys, only KitsRoot10 of the kits roots)');
|
|
152
|
+
await t.throws(() => Promise.resolve(saveInput({ ...vsAt, instanceId: '..\\x' }, root, places, 'r')), 'not a Visual Studio instance ID', 'an instance ID is never a path');
|
|
153
|
+
const captured = [
|
|
154
|
+
{
|
|
155
|
+
view: '64',
|
|
156
|
+
key: 'SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots',
|
|
157
|
+
name: 'KitsRoot10',
|
|
158
|
+
kind: 'string',
|
|
159
|
+
value: 'C:\\Program Files (x86)\\Windows Kits\\10\\',
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
view: '32',
|
|
163
|
+
key: 'SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v10.0',
|
|
164
|
+
name: 'ProductVersion',
|
|
165
|
+
kind: 'string',
|
|
166
|
+
value: '10.0.22621',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
view: '32',
|
|
170
|
+
key: 'SOFTWARE\\Microsoft\\Microsoft SDKs\\NETFXSDK\\4.8',
|
|
171
|
+
name: 'KitsInstallationFolder',
|
|
172
|
+
kind: 'string',
|
|
173
|
+
value: 'C:\\Program Files (x86)\\Windows Kits\\NETFXSDK\\4.8\\',
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
view: '32',
|
|
177
|
+
key: 'SOFTWARE\\Microsoft\\Microsoft SDKs\\NETFXSDK\\4.8\\WinSDK-NetFx40Tools-x64',
|
|
178
|
+
name: 'InstallationFolder',
|
|
179
|
+
kind: 'string',
|
|
180
|
+
value: 'c:\\program files (x86)\\Microsoft SDKs\\Windows\\v10.0A\\bin\\NETFX 4.8 Tools\\x64\\',
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
view: '64',
|
|
184
|
+
key: 'SOFTWARE\\Microsoft\\VisualStudio\\Setup',
|
|
185
|
+
name: 'KeepDownloadedPayloads',
|
|
186
|
+
kind: 'dword',
|
|
187
|
+
value: 0,
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
view: '64',
|
|
191
|
+
key: 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run',
|
|
192
|
+
name: 'planted',
|
|
193
|
+
kind: 'string',
|
|
194
|
+
value: 'C:\\planted.exe',
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
const result = SaveResultSchema.parse({
|
|
198
|
+
copied: [
|
|
199
|
+
'Community',
|
|
200
|
+
'WindowsKits10',
|
|
201
|
+
'NETFXSDK',
|
|
202
|
+
'MicrosoftSDKs-v10.0A',
|
|
203
|
+
'ReferenceAssemblies',
|
|
204
|
+
'Installer',
|
|
205
|
+
].concat(['Setup', 'instance']),
|
|
206
|
+
registry: captured,
|
|
207
|
+
});
|
|
208
|
+
const marker = VsPersistMarkerSchema.parse(markerFrom({ product: 'community', components: [...UNREAL_COMPONENTS] }, { installationPath: community, instanceId: 'a190d188', version: '17.14.37827.9' }, result, '2026-11-04T00:00:00.000Z'));
|
|
209
|
+
t.check(marker.sdkVersion === '10.0.22621' && marker.instanceId === 'a190d188' && marker.folders.length === 8, 'the marker keeps the instance ID, what was saved and the Windows SDK version from the registry');
|
|
210
|
+
t.check(!VsPersistMarkerSchema.safeParse({ ...marker, instanceId: '..\\..\\Windows' }).success &&
|
|
211
|
+
!VsPersistMarkerSchema.safeParse({ ...marker, folders: ['..'] }).success, 'a marker with a path for its instance ID, or a folder it does not know, is refused');
|
|
212
|
+
const plan = registryPlan(marker, root, places);
|
|
213
|
+
const value = (view, key, name) => plan.find((e) => e.view === view && e.key.toLowerCase() === key.toLowerCase() && e.name === name)?.value;
|
|
214
|
+
const kits = root + '\\WindowsKits10\\';
|
|
215
|
+
const sdk = 'SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\v10.0';
|
|
216
|
+
const netfx = 'SOFTWARE\\Microsoft\\Microsoft SDKs\\NETFXSDK\\4.8';
|
|
217
|
+
t.check(['64', '32'].every((v) => value(v, 'SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots', 'KitsRoot10') === kits &&
|
|
218
|
+
value(v, sdk, 'InstallationFolder') === kits &&
|
|
219
|
+
value(v, sdk, 'ProductVersion') === '10.0.22621' &&
|
|
220
|
+
value(v, netfx, 'KitsInstallationFolder') === root + '\\NETFXSDK\\4.8\\' &&
|
|
221
|
+
value(v, netfx + '\\WinSDK-NetFx40Tools-x64', 'InstallationFolder') ===
|
|
222
|
+
root + '\\MicrosoftSDKs-v10.0A\\bin\\NETFX 4.8 Tools\\x64\\' &&
|
|
223
|
+
value(v, 'SOFTWARE\\Microsoft\\VisualStudio\\Setup', 'KeepDownloadedPayloads') === 0), 'both registry views point the Windows SDK and the .NET Framework SDK at the data disk (any case); the rest stays');
|
|
224
|
+
const clsid = 'SOFTWARE\\Classes\\CLSID\\' + SETUP_CLSID + '\\InprocServer32';
|
|
225
|
+
const setupDll = 'Microsoft.VisualStudio.Setup.Configuration.Native.dll';
|
|
226
|
+
t.check(value('64', clsid, '') === 'C:\\ProgramData\\Microsoft\\VisualStudio\\Setup\\x64\\' + setupDll &&
|
|
227
|
+
value('32', clsid, '') === 'C:\\ProgramData\\Microsoft\\VisualStudio\\Setup\\x86\\' + setupDll &&
|
|
228
|
+
value('64', clsid, 'ThreadingModel') === 'Both' &&
|
|
229
|
+
value('32', clsid, 'ThreadingModel') === 'Both', 'the Setup Configuration COM class is registered (x64 and x86), so vswhere and UnrealBuildTool find the instance');
|
|
230
|
+
t.check(!plan.some((e) => /CurrentVersion\\Run/i.test(e.key)), 'a value the capture never reads is never written back (a tampered marker)');
|
|
231
|
+
const back = restoreInput(marker, root, places);
|
|
232
|
+
const copied = (name) => back.copies.find((c) => c.name === name);
|
|
233
|
+
t.check(!copied('Community') &&
|
|
234
|
+
!copied('WindowsKits10') &&
|
|
235
|
+
!copied('NETFXSDK') &&
|
|
236
|
+
!copied('Shared') &&
|
|
237
|
+
copied('Installer')?.to === 'C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer' &&
|
|
238
|
+
copied('Installer')?.mode === 'all' &&
|
|
239
|
+
copied('ReferenceAssemblies')?.mode === 'missing' &&
|
|
240
|
+
copied('instance')?.from === root + '\\instance' &&
|
|
241
|
+
copied('instance')?.to === instances + '\\a190d188' &&
|
|
242
|
+
back.registry.length === plan.length, 'a boot copies back only the small fixed-path folders that were saved (the instance under its ID); the big ones stay');
|
|
243
|
+
t.check(persistCovers(marker, { product: 'community', components: [...UNREAL_COMPONENTS] }) &&
|
|
244
|
+
persistCovers(marker, { product: 'community', components: ['component.unreal'] }) &&
|
|
245
|
+
!persistCovers(marker, {
|
|
246
|
+
product: 'community',
|
|
247
|
+
components: [...UNREAL_COMPONENTS, 'Microsoft.VisualStudio.Component.VC.Llvm.Clang'],
|
|
248
|
+
}) &&
|
|
249
|
+
persistCovers(marker, { product: 'buildtools', components: [...BUILD_TOOLS_COMPONENTS] }) &&
|
|
250
|
+
!persistCovers({ ...marker, product: 'buildtools' }, { product: 'community', components: [] }) &&
|
|
251
|
+
!persistCovers({ ...marker, folders: marker.folders.filter((f) => f !== 'Setup') }, {
|
|
252
|
+
product: 'community',
|
|
253
|
+
components: [],
|
|
254
|
+
}), 'a saved copy serves requests with no more components (Community serves the Build Tools too); one without the Setup API none');
|
|
255
|
+
const state = JSON.stringify({
|
|
256
|
+
installationPath: community,
|
|
257
|
+
resolvedInstallationPath: community.toLowerCase(),
|
|
258
|
+
nested: community + '\\Common7\\IDE',
|
|
259
|
+
longer: community + '2',
|
|
260
|
+
launchParams: { fileName: 'Common7\\IDE\\devenv.exe' },
|
|
261
|
+
enginePath: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\resources\\app',
|
|
262
|
+
});
|
|
263
|
+
const moved = JSON.parse(rewriteInstanceState(state, community, root + '\\Community'));
|
|
264
|
+
t.check(moved.installationPath === root + '\\Community' &&
|
|
265
|
+
moved.resolvedInstallationPath === root + '\\Community' &&
|
|
266
|
+
moved.nested === root + '\\Community\\Common7\\IDE' &&
|
|
267
|
+
moved.longer === community + '2' &&
|
|
268
|
+
moved.enginePath === 'C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\resources\\app', "the instance's state names the saved folder (JSON-escaped, any case); a longer name that only starts the same stays");
|
|
269
|
+
const buildTools = 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\BuildTools';
|
|
270
|
+
const movedBt = JSON.parse(rewriteInstanceState(JSON.stringify({ installationPath: buildTools }), buildTools + '\\', root + '\\BuildTools'));
|
|
271
|
+
t.check(movedBt.installationPath === root + '\\BuildTools' &&
|
|
272
|
+
rebase('D:\\Other\\x', [{ from: 'C:\\Program Files', to: 'D:\\y' }]) === 'D:\\Other\\x', 'the Build Tools too (a trailing backslash does not matter); a value outside the saved folders is kept');
|
|
273
|
+
t.equal(newestInstance([
|
|
274
|
+
{ installationVersion: '17.9.1', id: 'a' },
|
|
275
|
+
{ installationVersion: '17.14.37827.9', id: 'b' },
|
|
276
|
+
{ installationVersion: '17.14.3', id: 'c' },
|
|
277
|
+
])?.id, 'b', 'the newest instance is chosen by version (vswhere -latest fails on two instances installed at the same time)');
|
|
278
|
+
t.equal(newestInstance([]), null, 'no instance: none');
|
|
279
|
+
t.section('the Visual Studio installer');
|
|
135
280
|
t.check(vsExitText(3762504530).includes('crashed') &&
|
|
136
281
|
vsExitText(-1).includes('exit 4294967295') &&
|
|
137
282
|
vsExitText(0) === 'installed' &&
|
|
@@ -139,15 +284,6 @@ export const persistedInstallsSuite = {
|
|
|
139
284
|
vsExitText(8006).includes('running') &&
|
|
140
285
|
vsExitText(3010).includes('restart') &&
|
|
141
286
|
vsExitText(5007).includes('refused'), 'installer exit codes are told in words (0xE0434352 is a crash)');
|
|
142
|
-
const reserve = LOCAL_RESERVE_BYTES;
|
|
143
|
-
const room = (free) => freeBytesAt(t.dir('layout-room'), () => ({ bavail: free, bsize: 1 })) ?? 0;
|
|
144
|
-
t.check(layoutNextStep(0, 0, reserve) === 'done' &&
|
|
145
|
-
layoutNextStep(3762504530, room(40 * GB), reserve) === 'stage' &&
|
|
146
|
-
layoutNextStep(3762504530, room(20 * GB), reserve) === 'skip' &&
|
|
147
|
-
layoutNextStep(null, room(40 * GB), reserve) === 'stage', 'a failed layout is built on the sandbox disk only with room for it (8 GB plus the reserve)');
|
|
148
|
-
t.check(layoutNextStep(null, room(14 * GB), STAGING_RESERVE_BYTES) === 'stage' &&
|
|
149
|
-
layoutNextStep(null, room(9.5 * GB), STAGING_RESERVE_BYTES) === 'stage' &&
|
|
150
|
-
layoutNextStep(null, room(9 * GB), STAGING_RESERVE_BYTES) === 'skip', 'before Visual Studio is installed, the ~14 GB free at boot is enough to build the install files (9.5 GB needed)');
|
|
151
287
|
t.equal(freeBytesAt(path.join(t.dir('layout-room'), 'not', 'there'), () => ({ bavail: 3, bsize: 2 })), 6, 'free room of a folder that does not exist asks its parent');
|
|
152
288
|
t.equal(freeBytesAt(t.dir('layout-room'), () => {
|
|
153
289
|
throw new Error('no');
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// What reaches the sandbox from this PC's Claude files, and what is stripped first: API keys, cloud credentials, tokens
|
|
2
2
|
// in MCP server definitions. Also the secret-looking files in a workspace that the launch warns about.
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { DEFAULTS } from '../core/config/index.js';
|
|
6
|
+
import { findSecretFiles, pluginFilter, pluginTrimLevel, sandboxGlobalState, stageClaudeProfile, stripMcpServers, stripSettings, } from '../host/profile/index.js';
|
|
5
7
|
const REMOVED = '[removed by agent-sandbox]';
|
|
6
8
|
export const secretsSuite = {
|
|
7
9
|
name: 'secrets',
|
|
@@ -111,5 +113,38 @@ export const secretsSuite = {
|
|
|
111
113
|
])
|
|
112
114
|
t.write(path.join(workspace, file), 'x');
|
|
113
115
|
t.equal(findSecretFiles(workspace).sort(), ['.env', 'api\\credentials.json', 'server.pem'], 'secret-looking workspace files are found for the launch warning (templates and dependencies ignored)');
|
|
116
|
+
t.section('plugins staged from this PC');
|
|
117
|
+
const hostClaude = t.at('plugins-host');
|
|
118
|
+
const plugins = path.join(hostClaude, 'plugins');
|
|
119
|
+
t.write(path.join(plugins, 'installed_plugins.json'), '{"version":2,"plugins":{}}');
|
|
120
|
+
t.write(path.join(plugins, 'known_marketplaces.json'), '{}');
|
|
121
|
+
t.write(path.join(plugins, 'cache', 'm', 'p', '1.0', 'plugin.json'), '{}');
|
|
122
|
+
t.write(path.join(plugins, 'cache', 'm', 'p', '1.0', 'node_modules', 'dep', 'index.js'), 'x');
|
|
123
|
+
t.write(path.join(plugins, 'cache', 'temp_git_1_abc', 'junk.txt'), 'x');
|
|
124
|
+
t.write(path.join(plugins, 'marketplaces', 'm', 'marketplace.json'), '{}');
|
|
125
|
+
t.write(path.join(plugins, 'marketplaces', 'm', '.git', 'HEAD'), 'ref');
|
|
126
|
+
t.write(path.join(plugins, 'marketplaces', 'm.backup', 'old.txt'), 'x');
|
|
127
|
+
t.write(path.join(plugins, 'marketplaces', 'temp_subdir_2.clone', 'junk.txt'), 'x');
|
|
128
|
+
t.write(path.join(hostClaude, 'settings.json'), '{"enabledPlugins":{"p@m":true}}');
|
|
129
|
+
const launch = t.at('plugins-launch');
|
|
130
|
+
const profile = { ...DEFAULTS.claudeProfile, hostDir: hostClaude, globalStateFile: '' };
|
|
131
|
+
const staged = stageClaudeProfile(profile, launch, 'D:/Code/app', DEFAULTS.secrets);
|
|
132
|
+
const out = path.join(launch, 'claude-profile', 'plugins');
|
|
133
|
+
t.check(staged.staged.includes('plugins'), 'plugins are staged');
|
|
134
|
+
t.check(fs.existsSync(path.join(out, 'cache', 'm', 'p', '1.0', 'plugin.json')) &&
|
|
135
|
+
fs.existsSync(path.join(out, 'marketplaces', 'm', 'marketplace.json')) &&
|
|
136
|
+
fs.existsSync(path.join(out, 'marketplaces', 'm', '.git', 'HEAD')) &&
|
|
137
|
+
fs.existsSync(path.join(out, 'cache', 'm', 'p', '1.0', 'node_modules', 'dep', 'index.js')), 'the plugin cache, marketplaces and (when they fit) their git folders and dependencies are copied');
|
|
138
|
+
t.check(!fs.existsSync(path.join(out, 'cache', 'temp_git_1_abc')) &&
|
|
139
|
+
!fs.existsSync(path.join(out, 'marketplaces', 'm.backup')) &&
|
|
140
|
+
!fs.existsSync(path.join(out, 'marketplaces', 'temp_subdir_2.clone')), 'interrupted clones, backups and staging folders of plugins are left out');
|
|
141
|
+
t.check(pluginTrimLevel(plugins) === 0, 'a plugins folder under the cap is not trimmed');
|
|
142
|
+
const git = path.join(plugins, 'marketplaces', 'm', '.git');
|
|
143
|
+
const deps = path.join(plugins, 'cache', 'm', 'p', '1.0', 'node_modules');
|
|
144
|
+
t.check(pluginFilter(plugins, 0)(git) &&
|
|
145
|
+
!pluginFilter(plugins, 1)(git) &&
|
|
146
|
+
pluginFilter(plugins, 1)(deps) &&
|
|
147
|
+
!pluginFilter(plugins, 2)(deps), 'a plugins folder over the cap loses git history first, then node_modules');
|
|
148
|
+
t.check(fs.readFileSync(path.join(launch, 'claude-profile', 'settings.json'), 'utf8').includes('p@m'), 'enabledPlugins reach the sandbox');
|
|
114
149
|
},
|
|
115
150
|
};
|
|
@@ -69,6 +69,7 @@ export const LAUNCH_FILES_GOLDEN = [
|
|
|
69
69
|
'sandbox/csharp/SandboxTools/MemoryLog.cs',
|
|
70
70
|
'sandbox/csharp/SandboxTools/PageFile.cs',
|
|
71
71
|
'sandbox/csharp/SandboxTools/Program.cs',
|
|
72
|
+
'sandbox/csharp/VsSetup.cs',
|
|
72
73
|
'sandbox/csharp/VsWhere.cs',
|
|
73
74
|
'sandbox/csharp/VtInput.cs',
|
|
74
75
|
'sandbox/scripts/add-firewall-port.ps1',
|
|
@@ -76,13 +77,17 @@ export const LAUNCH_FILES_GOLDEN = [
|
|
|
76
77
|
'sandbox/scripts/expand-archive.ps1',
|
|
77
78
|
'sandbox/scripts/get-gpu-names.ps1',
|
|
78
79
|
'sandbox/scripts/get-signature.ps1',
|
|
80
|
+
'sandbox/scripts/get-vs-setup.ps1',
|
|
79
81
|
'sandbox/scripts/install-shell-path.ps1',
|
|
80
82
|
'sandbox/scripts/link-profile-dirs.ps1',
|
|
81
83
|
'sandbox/scripts/new-shortcuts.ps1',
|
|
82
84
|
'sandbox/scripts/repair-wmi-services.ps1',
|
|
85
|
+
'sandbox/scripts/restore-visual-studio.ps1',
|
|
86
|
+
'sandbox/scripts/save-visual-studio.ps1',
|
|
83
87
|
'sandbox/scripts/set-desktop.ps1',
|
|
84
88
|
'sandbox/scripts/set-dns-servers.ps1',
|
|
85
89
|
'sandbox/scripts/set-machine-environment.ps1',
|
|
90
|
+
'sandbox/scripts/set-registry-values.ps1',
|
|
86
91
|
'sandbox/templates/fivem/README.txt',
|
|
87
92
|
'sandbox/templates/fivem/cs/Client/Client.csproj',
|
|
88
93
|
'sandbox/templates/fivem/cs/Client/ClientMain.cs',
|
|
@@ -142,7 +142,9 @@ export declare function createFixtures(t: Harness): {
|
|
|
142
142
|
pcInstalls: {
|
|
143
143
|
steamGames: boolean | number[];
|
|
144
144
|
epicGames: boolean | string[];
|
|
145
|
-
unrealEngines: boolean | string[];
|
|
145
|
+
unrealEngines: boolean | string[] | "auto";
|
|
146
|
+
unrealEngineFolders: string[];
|
|
147
|
+
unrealAssets: boolean | "auto";
|
|
146
148
|
unityEditors: boolean | string[];
|
|
147
149
|
};
|
|
148
150
|
provisioning: {
|
|
@@ -199,8 +201,7 @@ export declare function createFixtures(t: Harness): {
|
|
|
199
201
|
enabled: boolean | "auto";
|
|
200
202
|
acceptLicense: boolean;
|
|
201
203
|
components: string[];
|
|
202
|
-
|
|
203
|
-
visualStudio: "full" | "autosdk";
|
|
204
|
+
visualStudio: "full" | "buildtools";
|
|
204
205
|
persistEngines: boolean;
|
|
205
206
|
derivedDataCache: "persisted" | "local";
|
|
206
207
|
};
|