windows-development-sandbox 1.28.0 → 1.29.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/README.md +5 -4
- package/dist/app/features/sandbox/sandbox.run.js +1 -1
- package/dist/bin/{chunk-UZ5S3EMB.js → chunk-O4EL32TM.js} +1 -1
- package/dist/bin/{chunk-CB44HTTN.js → chunk-QYRRWGFY.js} +1442 -1280
- package/dist/bin/sandbox.js +10772 -3
- package/dist/cli/commands/launch/launch.js +1 -1
- package/dist/cli/commands/launch/running.js +2 -2
- package/dist/cli/index.js +1 -1
- package/dist/cli/menu/pages/agents-page.js +1 -1
- package/dist/cli/services/sandbox-service.js +1 -1
- package/dist/core/schema/sandbox.js +1 -1
- package/dist/guest/apps/app-installer.js +1 -1
- package/dist/guest/apps/store-check.d.ts +2 -0
- package/dist/guest/apps/store-check.js +3 -1
- package/dist/guest/boot/guide-lines.d.ts +2 -0
- package/dist/guest/boot/guide-lines.js +7 -0
- package/dist/guest/boot/guide.js +2 -1
- package/dist/guest/toolchains/native/unreal.js +3 -22
- package/dist/host/accounts/deliver.js +16 -2
- package/dist/host/apps/store-package.d.ts +2 -0
- package/dist/host/apps/store-package.js +16 -15
- package/dist/host/channel/attach.js +1 -1
- package/dist/host/channel/channel.js +3 -3
- package/dist/host/guard/commands.js +1 -0
- package/dist/host/health/path-copies.d.ts +1 -1
- package/dist/host/health/path-copies.js +2 -2
- package/dist/host/install/index.d.ts +1 -1
- package/dist/host/install/index.js +1 -1
- package/dist/host/install/shims.d.ts +8 -1
- package/dist/host/install/shims.js +39 -1
- package/dist/host/launch/generate.js +4 -0
- package/dist/host/launch/index.d.ts +1 -0
- package/dist/host/launch/index.js +1 -0
- package/dist/host/launch/pc-logs.d.ts +6 -0
- package/dist/host/launch/pc-logs.js +129 -0
- package/dist/host/sandbox/platform.js +1 -1
- package/dist/host/sandbox/window.js +1 -1
- package/dist/runtime/agent.js +1 -1
- package/dist/runtime/daemon.js +72 -90
- package/dist/runtime/db.js +1 -1
- package/dist/runtime/main.js +35 -57
- package/dist/runtime/netproxy.js +1 -1
- package/dist/runtime/tool.js +1 -1
- package/dist/runtime/view.js +1 -1
- package/dist/selftest/cli.test.js +5 -5
- package/dist/selftest/config.test.js +1 -1
- package/dist/selftest/credentials.test.js +1 -1
- package/dist/selftest/engine.test.js +1 -1
- package/dist/selftest/generate.test.js +66 -1
- package/dist/selftest/hcs.test.js +1 -1
- package/dist/selftest/health.test.js +21 -10
- package/dist/selftest/heavy-load.test.js +1 -5
- package/dist/selftest/host-sim.test.js +13 -14
- package/dist/selftest/install.test.js +6 -2
- package/dist/selftest/store-app.test.js +62 -4
- package/dist/selftest/support/web-preview.js +2 -2
- package/dist/shared/accounts/kinds.d.ts +5 -0
- package/dist/shared/accounts/kinds.js +8 -0
- package/dist/shared/apps/index.d.ts +1 -1
- package/dist/shared/apps/index.js +1 -1
- package/dist/shared/apps/selection.d.ts +1 -0
- package/dist/shared/apps/selection.js +4 -3
- package/dist/shared/apps/store-package.d.ts +13 -0
- package/dist/shared/apps/store-package.js +24 -0
- package/dist/shared/memory/index.d.ts +0 -1
- package/dist/shared/memory/index.js +0 -1
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/Window.cs +1 -1
- package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
- package/native/scripts/get-store-package.ps1 +24 -13
- package/package.json +4 -14
- package/sandbox/scripts/install-store-package.ps1 +11 -4
- package/schemas/config.schema.json +1 -1
- package/schemas/sandbox-config.schema.json +1 -1
- package/dist/bin/agent-sandbox.d.ts +0 -2
- package/dist/bin/agent-sandbox.js +0 -9
- package/dist/bin/chunk-ZMZI3625.js +0 -10778
- package/dist/bin/claude-host.d.ts +0 -2
- package/dist/bin/claude-host.js +0 -11
- package/dist/bin/claude-sbx.d.ts +0 -2
- package/dist/bin/claude-sbx.js +0 -9
- package/dist/bin/codex-sbx.d.ts +0 -2
- package/dist/bin/codex-sbx.js +0 -9
- package/dist/bin/gemini-sbx.d.ts +0 -2
- package/dist/bin/gemini-sbx.js +0 -9
- package/dist/bin/sbx-hide.d.ts +0 -2
- package/dist/bin/sbx-hide.js +0 -9
- package/dist/bin/sbx-show.d.ts +0 -2
- package/dist/bin/sbx-show.js +0 -9
- package/dist/shared/memory/unreal-build.d.ts +0 -2
- package/dist/shared/memory/unreal-build.js +0 -21
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { LocalOverridesSchema } from '../core/schema/index.js';
|
|
6
|
-
import { buildWsb, gitIdentity, logonEnv, resolveMemoryMb } from '../host/launch/index.js';
|
|
6
|
+
import { buildWsb, gitIdentity, isSandboxSourceRepo, launchHasSandboxSource, logonEnv, resolveMemoryMb, stagePcLogs, } from '../host/launch/index.js';
|
|
7
|
+
import { pcLogsGuideLine } from '../guest/boot/guide-lines.js';
|
|
7
8
|
export const generateSuite = {
|
|
8
9
|
name: 'generate',
|
|
9
10
|
title: 'generated .wsb, launch folder and session.json',
|
|
@@ -90,5 +91,69 @@ export const generateSuite = {
|
|
|
90
91
|
resolveMemoryMb({ ...auto, maxMemoryPercent: 50 }, 60000 * MB, pcRam),
|
|
91
92
|
], [31744, 21504, 32768], "memory 'auto' follows free RAM minus the reserve and never takes more than maxMemoryPercent of the PC's RAM");
|
|
92
93
|
await t.throws(() => resolveMemoryMb(auto, 9000 * MB, pcRam), 'Not enough free RAM', "memory 'auto' refuses when too little RAM is free");
|
|
94
|
+
t.section('PC logs for developing the sandbox');
|
|
95
|
+
const root = path.join(t.temp, 'pc-logs');
|
|
96
|
+
const URL = 'git+https://github.com/JoshDiDuca/windows-development-sandbox.git';
|
|
97
|
+
const project = (name, pkg) => {
|
|
98
|
+
t.write(path.join(root, name, 'package.json'), pkg);
|
|
99
|
+
return path.join(root, name);
|
|
100
|
+
};
|
|
101
|
+
const own = project('own', JSON.stringify({ name: 'windows-development-sandbox', repository: { type: 'git', url: URL } }));
|
|
102
|
+
const pkgOf = (url, name = 'windows-development-sandbox') => JSON.stringify({ name, repository: { url } });
|
|
103
|
+
const variants = [
|
|
104
|
+
project('v-https', pkgOf('https://github.com/joshdiduca/windows-development-sandbox')),
|
|
105
|
+
project('v-dotgit', pkgOf('https://github.com/JoshDiDuca/windows-development-sandbox.git')),
|
|
106
|
+
project('v-string', JSON.stringify({ name: 'windows-development-sandbox', repository: URL })),
|
|
107
|
+
];
|
|
108
|
+
t.equal([isSandboxSourceRepo(own, own), ...variants.map((v) => isSandboxSourceRepo(v, own))], [true, true, true, true], 'a folder with the sandbox name and repository url is its source (git+, .git and case variants too)');
|
|
109
|
+
const big = project('big', pkgOf(URL) + ' '.repeat(300 * 1024));
|
|
110
|
+
const link = path.join(root, 'linked');
|
|
111
|
+
let linked = false;
|
|
112
|
+
try {
|
|
113
|
+
fs.symlinkSync(path.join(root, 'own'), link, 'junction');
|
|
114
|
+
linked = true;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// No permission for links here: that case is skipped.
|
|
118
|
+
}
|
|
119
|
+
t.equal([
|
|
120
|
+
isSandboxSourceRepo(project('wrong-url', pkgOf('https://github.com/someone/windows-development-sandbox')), own),
|
|
121
|
+
isSandboxSourceRepo(project('wrong-name', pkgOf(URL, 'other')), own),
|
|
122
|
+
isSandboxSourceRepo(project('invalid', '{ nope'), own),
|
|
123
|
+
isSandboxSourceRepo(path.join(root, 'missing'), own),
|
|
124
|
+
isSandboxSourceRepo(big, own),
|
|
125
|
+
linked ? isSandboxSourceRepo(link, own) : false,
|
|
126
|
+
], [false, false, false, false, false, false], 'a wrong url or name, a missing, oversized or invalid package.json and a link are not the sandbox source');
|
|
127
|
+
t.check(launchHasSandboxSource([f.workA, own], own) && !launchHasSandboxSource([f.workA, f.workB], own), 'any project of the launch can be the source; a launch without it has none');
|
|
128
|
+
t.check(!fs.existsSync(path.join(gen.launchPath, 'pc-logs')), 'a launch of another project gets no pc-logs');
|
|
129
|
+
const storage = path.join(root, 'storage');
|
|
130
|
+
const dataPath = path.join(root, 'data-x');
|
|
131
|
+
for (let i = 0; i < 12; i++)
|
|
132
|
+
t.write(path.join(storage, 'logs', 'crashes', 'crash-2026-01-' + String(10 + i) + '.txt'), 'report ' + i);
|
|
133
|
+
t.write(path.join(storage, 'logs', 'host-memory-data-x.log'), 'start\n' + 'm'.repeat(600 * 1024) + 'END');
|
|
134
|
+
t.write(path.join(storage, 'logs', 'host-sync-data-x.log'), 'small');
|
|
135
|
+
t.write(path.join(storage, 'state', 'live-folders.log'), 'live');
|
|
136
|
+
t.write(path.join(storage, 'state', 'logins.json'), 'SECRET');
|
|
137
|
+
t.write(path.join(storage, 'secrets', 'x.json'), 'SECRET');
|
|
138
|
+
const launch = path.join(root, 'launch');
|
|
139
|
+
const out = stagePcLogs(storage, dataPath, launch);
|
|
140
|
+
const listing = (dir) => fs
|
|
141
|
+
.readdirSync(dir, { recursive: true, withFileTypes: true })
|
|
142
|
+
.flatMap((e) => e.isFile() ? [path.relative(out, path.join(e.parentPath, e.name)).replaceAll('\\', '/')] : []);
|
|
143
|
+
const copied = listing(out).sort();
|
|
144
|
+
const reports = copied.filter((n) => n.startsWith('crashes/'));
|
|
145
|
+
const memory = fs.readFileSync(path.join(out, 'host-memory.log'), 'utf8');
|
|
146
|
+
t.check(reports.length === 10 &&
|
|
147
|
+
reports.includes('crashes/crash-2026-01-21.txt') &&
|
|
148
|
+
!reports.includes('crashes/crash-2026-01-11.txt') &&
|
|
149
|
+
fs.statSync(path.join(out, 'host-memory.log')).size <= 256 * 1024 &&
|
|
150
|
+
memory.endsWith('END') &&
|
|
151
|
+
fs.readFileSync(path.join(out, 'README.txt'), 'utf8').includes('never commit'), 'the newest 10 crash reports, the last 256 KB of the memory log and a README are copied', copied.join(', '));
|
|
152
|
+
t.equal(copied.filter((n) => !n.startsWith('crashes/')), ['README.txt', 'host-memory.log', 'host-sync.log', 'live-folders.log'], 'nothing else from the storage folder is copied (no state, logins or secrets)');
|
|
153
|
+
t.section('the guide line for the PC logs');
|
|
154
|
+
t.equal([pcLogsGuideLine('C:\\AgentLaunch', () => false), pcLogsGuideLine('C:\\AgentLaunch', () => true)], [
|
|
155
|
+
[],
|
|
156
|
+
['- PC crash reports and logs (this project only, read-only, never commit them): `C:\\AgentLaunch\\pc-logs`'],
|
|
157
|
+
], 'the guide mentions the PC logs only when the folder exists');
|
|
93
158
|
},
|
|
94
159
|
};
|
|
@@ -514,7 +514,7 @@ export const hcsSuite = {
|
|
|
514
514
|
LEFTOVER_DEPS.current = null;
|
|
515
515
|
}
|
|
516
516
|
t.section('sandbox engine check hcs');
|
|
517
|
-
const out = await f.runBin('
|
|
517
|
+
const out = await f.runBin('sandbox', ['engine', 'check', 'hcs', '--config', t.writeJson(t.at('hcs-config.json'), {})], {
|
|
518
518
|
cwd: f.workA,
|
|
519
519
|
env: { ...process.env, NO_COLOR: '1', AGENT_SANDBOX_HOME: t.dir('hcs-home'), AGENT_SANDBOX_HOST_ONLY: '1' },
|
|
520
520
|
});
|
|
@@ -14,7 +14,7 @@ import { logText, writeRepairLog } from '../cli/commands/repair/log.js';
|
|
|
14
14
|
import { HealthPrinter, summaryText } from '../cli/commands/repair/printer.js';
|
|
15
15
|
import { runHealth } from '../cli/commands/repair/run.js';
|
|
16
16
|
import { configBackups, findCommandCopies, fileOnDisk, groupEntries, inspectPointers, lineDiff, orphanWatchers, parseGroupMembers, parseIcacls, parseRegPath, pathHas, privacyProblems, repairPointers, runStateProblems, shimProblems, staleActiveRecords, staleLaunchFolders, watcherRecords, } from '../host/health/index.js';
|
|
17
|
-
import { InstallLayout, writeShims } from '../host/install/index.js';
|
|
17
|
+
import { InstallLayout, REMOVED_COMMANDS, shimText, writeShims } from '../host/install/index.js';
|
|
18
18
|
import { ActiveRecords } from '../host/sharing/index.js';
|
|
19
19
|
const SID = 'S-1-5-21-1-2-3-1001';
|
|
20
20
|
function fakeSystem(over = {}) {
|
|
@@ -225,7 +225,7 @@ function installChecks(t, mode) {
|
|
|
225
225
|
t.writeJson(path.join(layout.packageDir(v), 'package.json'), { name: 'x', version: v, bin });
|
|
226
226
|
};
|
|
227
227
|
pkg('1.4.0', { sandbox: 'dist/bin/sandbox.js' });
|
|
228
|
-
pkg('1.5.0', { sandbox: 'dist/bin/sandbox.js', '
|
|
228
|
+
pkg('1.5.0', { sandbox: 'dist/bin/sandbox.js', 'other-tool': 'dist/bin/other-tool.js' });
|
|
229
229
|
t.dir('health', 'install-' + mode, 'WindowsDevelopmentSandbox', 'versions', '1.6.0', 'staging');
|
|
230
230
|
t.write(layout.nodeExe('v22.1.0'), 'x');
|
|
231
231
|
t.write(layout.nodeExe('v22.11.0'), 'x');
|
|
@@ -257,13 +257,13 @@ async function pointers(t, layout, env) {
|
|
|
257
257
|
async function shims(t, layout, env) {
|
|
258
258
|
t.section('install: shims');
|
|
259
259
|
layout.setActive('1.5.0', 'v22.11.0');
|
|
260
|
-
const bins = { sandbox: 'dist/bin/sandbox.js', '
|
|
260
|
+
const bins = { sandbox: 'dist/bin/sandbox.js', 'other-tool': 'dist/bin/other-tool.js' };
|
|
261
261
|
writeShims(layout, bins);
|
|
262
262
|
t.equal(shimProblems(layout, bins), { missing: [], different: [] }, 'freshly written shims are fine');
|
|
263
263
|
fs.rmSync(path.join(layout.binDir, 'sandbox.cmd'));
|
|
264
|
-
fs.writeFileSync(path.join(layout.binDir, '
|
|
264
|
+
fs.writeFileSync(path.join(layout.binDir, 'other-tool'), '#!/bin/sh\necho old\n');
|
|
265
265
|
const problems = shimProblems(layout, bins);
|
|
266
|
-
t.equal([problems.missing, problems.different], [['sandbox.cmd'], ['
|
|
266
|
+
t.equal([problems.missing, problems.different], [['sandbox.cmd'], ['other-tool']], 'a missing and an out-of-date shim are found');
|
|
267
267
|
const { run, result } = await lookAt(env, 'install-shims');
|
|
268
268
|
t.check(result.status === 'needs-you' && result.fixable, 'the shims check offers to rewrite them');
|
|
269
269
|
const fixed = await run.fix([result]);
|
|
@@ -271,6 +271,19 @@ async function shims(t, layout, env) {
|
|
|
271
271
|
t.equal(shimProblems(layout, bins), { missing: [], different: [] }, 'the shims read the active version again');
|
|
272
272
|
fs.writeFileSync(path.join(layout.binDir, 'extra.cmd'), 'echo mine');
|
|
273
273
|
t.check(fs.existsSync(path.join(layout.binDir, 'extra.cmd')), 'a file that is not one of ours is left alone');
|
|
274
|
+
t.section('install: shims of removed commands');
|
|
275
|
+
for (const [name, target] of Object.entries(REMOVED_COMMANDS)) {
|
|
276
|
+
fs.writeFileSync(path.join(layout.binDir, name + '.cmd'), shimText(name, 'cmd', target));
|
|
277
|
+
fs.writeFileSync(path.join(layout.binDir, name), shimText(name, 'sh', target));
|
|
278
|
+
}
|
|
279
|
+
fs.writeFileSync(path.join(layout.binDir, 'claude-sbx.cmd'), '@echo off rem mine');
|
|
280
|
+
writeShims(layout, bins);
|
|
281
|
+
const left = Object.keys(REMOVED_COMMANDS).flatMap((name) => [name + '.cmd', name].filter((file) => fs.existsSync(path.join(layout.binDir, file))));
|
|
282
|
+
t.equal(left, ['claude-sbx.cmd'], 'writing the shims removes the shims of old commands, but not a file that is not ours');
|
|
283
|
+
const kept = { ...bins, 'claude-sbx': 'dist/bin/claude-sbx.js' };
|
|
284
|
+
writeShims(layout, kept);
|
|
285
|
+
t.check(fs.readFileSync(path.join(layout.binDir, 'claude-sbx'), 'utf8') ===
|
|
286
|
+
shimText('claude-sbx', 'sh', kept['claude-sbx']), 'a package that still declares an old command keeps its shim (a rollback)');
|
|
274
287
|
}
|
|
275
288
|
async function copiesOnPath(t) {
|
|
276
289
|
t.section('install: copies of the commands on the PATH');
|
|
@@ -282,9 +295,7 @@ async function copiesOnPath(t) {
|
|
|
282
295
|
t.write(path.join(npmBin, 'sandbox'), '#!/bin/sh');
|
|
283
296
|
t.write(path.join(npmBin, 'node_modules', 'windows-development-sandbox', 'package.json'), '{}');
|
|
284
297
|
t.write(path.join(managedBin, 'sandbox.cmd'), '@echo off');
|
|
285
|
-
t.write(path.join(
|
|
286
|
-
t.write(path.join(managedBin, 'claude-sbx.exe'), '');
|
|
287
|
-
t.write(path.join(oldBin, 'claude-sbx.cmd'), '@echo off');
|
|
298
|
+
t.write(path.join(oldBin, 'sandbox.cmd'), '@echo off');
|
|
288
299
|
const machinePath = [npmBin, managedBin].join(';');
|
|
289
300
|
const userPath = [oldBin, npmBin, t.dir('copies', 'empty')].join(';');
|
|
290
301
|
const pathext = '.COM;.EXE;.BAT;.CMD';
|
|
@@ -298,8 +309,8 @@ async function copiesOnPath(t) {
|
|
|
298
309
|
});
|
|
299
310
|
t.equal(found.map((c) => [c.kind, c.files.join(',')]), [
|
|
300
311
|
['npm', 'sandbox.cmd'],
|
|
301
|
-
['managed-machine', 'sandbox.cmd
|
|
302
|
-
['old-scripts', '
|
|
312
|
+
['managed-machine', 'sandbox.cmd'],
|
|
313
|
+
['old-scripts', 'sandbox.cmd'],
|
|
303
314
|
], 'copies come in PATH order (machine first, a folder once), each command as its PATHEXT match, extensionless files ignored');
|
|
304
315
|
const asked = [];
|
|
305
316
|
const saved = process.env.ProgramFiles;
|
|
@@ -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 { BURST_GATE, compilersAllowed, DIAL_CALM_SAMPLES, DIAL_STEPS, HOST_THRESHOLDS, buildJobs, cargoJobs, linkSlotsFor, usableCpus, effectiveCpuLimit, gentleCpuLimit, maxCompilers, chooseTargetDir, cargoLinkerVar,
|
|
11
|
+
import { BURST_GATE, 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';
|
|
@@ -96,10 +96,6 @@ export const heavyLoadSuite = {
|
|
|
96
96
|
t.check(cargoJobs(16, 32, 3) === 2 && cargoJobs(16, 32, 8) === 1, 'fewer when several sessions build');
|
|
97
97
|
t.check(cargoJobs(32, 32, 1) === 8, '32 processors, 32 GB: 8 jobs for one session');
|
|
98
98
|
t.check(buildJobs(32, 32) === 8 && buildJobs(32, 64) === 19 && buildJobs(4, 64) === 4 && buildJobs(16, 4) === 2, 'the build budget: 3 GB per job after 6 GB, never more than the processors, at least 2');
|
|
99
|
-
const ubt = ubtBuildConfiguration(buildJobs(20, 36));
|
|
100
|
-
t.check(ubt.includes('<MaxParallelActions>10</MaxParallelActions>') &&
|
|
101
|
-
ubt.includes('xmlns="https://www.unrealengine.com/BuildConfiguration"') &&
|
|
102
|
-
ubtBuildConfiguration(0).includes('<MaxParallelActions>1</MaxParallelActions>'), "UnrealBuildTool's compiles at once follow the build budget (at least 1)", ubt);
|
|
103
99
|
t.check(linkSlotsFor(32) === 8 && linkSlotsFor(16) === 3 && linkSlotsFor(8) === 2 && linkSlotsFor(128) === 8, 'links at once ("auto"): one per 3 GB after 6 GB, 2 to 8');
|
|
104
100
|
t.check(usableCpus(32, 80) === 25 && usableCpus(32, null) === 32 && usableCpus(4, 10) === 2 && usableCpus(8, 100) === 8, 'the processors programs see follow the CPU cap (at least 2)');
|
|
105
101
|
t.section('the governor: which programs');
|
|
@@ -27,9 +27,9 @@ const SANDBOX_ONLY_VARS = [
|
|
|
27
27
|
'AGENT_SANDBOX_CONFIG',
|
|
28
28
|
];
|
|
29
29
|
const PARALLEL = 2;
|
|
30
|
-
const sandbox = (label, ...args) => ({ bin: '
|
|
30
|
+
const sandbox = (label, ...args) => ({ bin: 'sandbox', label, args, config: true });
|
|
31
31
|
const COMMANDS = [
|
|
32
|
-
{ bin: '
|
|
32
|
+
{ bin: 'sandbox', label: '--help', args: ['--help'] },
|
|
33
33
|
sandbox('status', 'status'),
|
|
34
34
|
sandbox('status --json', 'status', '--json'),
|
|
35
35
|
sandbox('sessions', 'sessions'),
|
|
@@ -38,26 +38,25 @@ const COMMANDS = [
|
|
|
38
38
|
sandbox('vpn status', 'vpn', 'status'),
|
|
39
39
|
sandbox('vpn status --json', 'vpn', 'status', '--json'),
|
|
40
40
|
sandbox('account use nobody', 'account', 'use', 'nobody'),
|
|
41
|
-
{ bin: '
|
|
41
|
+
{ bin: 'sandbox', label: 'config path', args: ['config', 'path'] },
|
|
42
42
|
sandbox('config show', 'config', 'show'),
|
|
43
|
-
{ bin: '
|
|
43
|
+
{ bin: 'sandbox', label: 'config schema', args: ['config', 'schema'] },
|
|
44
44
|
sandbox('clean', 'clean'),
|
|
45
45
|
sandbox('sync', 'sync'),
|
|
46
46
|
sandbox('logs', 'logs'),
|
|
47
47
|
sandbox('attach', 'attach'),
|
|
48
|
-
{ bin: '
|
|
48
|
+
{ bin: 'sandbox', label: 'exec', args: ['exec'], after: ['--', 'echo', 'hi'], config: true },
|
|
49
49
|
sandbox('restart', 'restart'),
|
|
50
50
|
sandbox('stop', 'stop'),
|
|
51
51
|
sandbox('show', 'show'),
|
|
52
52
|
sandbox('hide', 'hide'),
|
|
53
53
|
sandbox('generate', 'generate'),
|
|
54
54
|
sandbox('launch', 'launch'),
|
|
55
|
-
{ bin: '
|
|
56
|
-
{ bin: '
|
|
57
|
-
{ bin: '
|
|
58
|
-
{ bin: '
|
|
59
|
-
{ bin: '
|
|
60
|
-
{ bin: 'claude-host', label: 'claude-host --version', args: ['--version'] },
|
|
55
|
+
{ bin: 'sandbox', label: 'unknown command', args: ['frobnicate'] },
|
|
56
|
+
{ bin: 'sandbox', label: 'claude --headless --continue', args: ['claude', '--headless', '--continue'], config: true },
|
|
57
|
+
{ bin: 'sandbox', label: 'codex', args: ['codex'], config: true },
|
|
58
|
+
{ bin: 'sandbox', label: 'gemini', args: ['gemini'], config: true },
|
|
59
|
+
{ bin: 'sandbox', label: 'host claude --version', args: ['host', 'claude', '--', '--version'] },
|
|
61
60
|
];
|
|
62
61
|
export const hostSimSuite = {
|
|
63
62
|
name: 'host-sim',
|
|
@@ -93,7 +92,7 @@ export const hostSimSuite = {
|
|
|
93
92
|
const args = command.config ? [...command.args, '--config', pc.config, ...(command.after ?? [])] : command.args;
|
|
94
93
|
const r = await f.runBin(command.bin, args, { cwd: project, env: envFor(pc.home), timeoutMs: 120000 });
|
|
95
94
|
const crashed = r.status === null || r.status > 1 || /Unexpected error|node:internal|^\s+at .+:\d+:\d+\)?$/m.test(r.out);
|
|
96
|
-
const unexplained = r.status === 1 && !/(^|\n)(Error: |error: )/.test(r.out) && command.
|
|
95
|
+
const unexplained = r.status === 1 && !/(^|\n)(Error: |error: )/.test(r.out) && !command.label.startsWith('host ');
|
|
97
96
|
if (crashed || unexplained)
|
|
98
97
|
broken.push(command.label +
|
|
99
98
|
' (exit ' +
|
|
@@ -110,8 +109,8 @@ export const hostSimSuite = {
|
|
|
110
109
|
await runOne(next.command, next.index);
|
|
111
110
|
});
|
|
112
111
|
const initRuns = (async () => {
|
|
113
|
-
await runOne({ bin: '
|
|
114
|
-
await runOne({ bin: '
|
|
112
|
+
await runOne({ bin: 'sandbox', label: 'config init', args: ['config', 'init'] }, 1000, initPc);
|
|
113
|
+
await runOne({ bin: 'sandbox', label: 'config init (again)', args: ['config', 'init'] }, 1001, initPc);
|
|
115
114
|
})();
|
|
116
115
|
await Promise.all([...workers, initRuns]);
|
|
117
116
|
t.equal(broken, [], 'all ' + (COMMANDS.length + 2) + ' commands work or fail with a clear message when run outside a sandbox');
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
|
-
import { shimText } from '../host/install/index.js';
|
|
6
|
+
import { REMOVED_COMMANDS, shimText } from '../host/install/index.js';
|
|
7
7
|
import { nativeScript } from '../host/native.js';
|
|
8
8
|
import { SYSTEM } from '../shared/system.js';
|
|
9
9
|
/** The text install.ps1 writes for one shim, run by powershell.exe as a script file. */
|
|
@@ -39,11 +39,15 @@ function installerText(t, script) {
|
|
|
39
39
|
const cases = [
|
|
40
40
|
['sandbox', undefined],
|
|
41
41
|
['claude-sbx', 'dist/bin/claude-sbx.js'],
|
|
42
|
-
['
|
|
42
|
+
['sbx-show', undefined],
|
|
43
43
|
];
|
|
44
44
|
for (const [name, target] of cases)
|
|
45
45
|
for (const kind of ['cmd', 'sh'])
|
|
46
46
|
t.equal(printedShim(script, kind, name, target), shimText(name, kind, target), name + '.' + kind + ' is identical');
|
|
47
|
+
t.section('install.ps1 and package.json: the commands');
|
|
48
|
+
const bin = JSON.parse(fs.readFileSync(path.join(t.packageRoot, 'package.json'), 'utf8')).bin;
|
|
49
|
+
t.equal(Object.keys(bin).sort(), ['sandbox', 'windows-development-sandbox'], 'the package declares only those commands');
|
|
50
|
+
t.equal(Object.keys(REMOVED_COMMANDS).filter((name) => !source.includes("'" + name + "'")), [], 'the installer removes the shim of every removed command');
|
|
47
51
|
t.includes(printedShim(script, 'cmd', 'x y'), 'exit', 'a name that is not a command name is refused');
|
|
48
52
|
}
|
|
49
53
|
export const installSuite = {
|
|
@@ -8,11 +8,12 @@ import { DEFAULTS } from '../core/config/index.js';
|
|
|
8
8
|
import { OFF_IS_SAFER } from '../core/local-config/index.js';
|
|
9
9
|
import { HOST_SOURCES } from '../host/accounts/index.js';
|
|
10
10
|
import { prepareStorePackages, stagedPackageDir, StorePackages } from '../host/apps/index.js';
|
|
11
|
+
import { prepareServiceAccounts } from '../host/accounts/index.js';
|
|
11
12
|
import { checkedPackage } from '../guest/apps/store-check.js';
|
|
12
13
|
import { peekPendingPerplexityApp, perplexityAppPendingFile } from '../guest/accounts/kinds/perplexityapp.js';
|
|
13
14
|
import { perplexityAppShortcutArgs, restoreAppSignIn } from '../guest/browser/perplexity-app.js';
|
|
14
|
-
import { appsToInstall, appById, isStorePackageFile, PERPLEXITY_DESKTOP_FAMILY, PERPLEXITY_DESKTOP_PACKAGE, PERPLEXITY_DESKTOP_PRODUCT_ID, storeApps, StorePackageManifestSchema, storePackageRole, } from '../shared/apps/index.js';
|
|
15
|
-
import { appUsersOn, PAYLOADS, SERVICE_SPECS, serviceWanted, usedByText, } from '../shared/accounts/index.js';
|
|
15
|
+
import { appsToInstall, appById, isStorePackageFile, PERPLEXITY_DESKTOP_FAMILY, PERPLEXITY_DESKTOP_PACKAGE, PERPLEXITY_DESKTOP_PRODUCT_ID, assignStoreRoles, storeApps, StorePackageManifestSchema, storePackageFitsX64, storePackageRole, } from '../shared/apps/index.js';
|
|
16
|
+
import { appsNeededFor, appUsersOn, PAYLOADS, SERVICE_SPECS, serviceWanted, usedByText, } from '../shared/accounts/index.js';
|
|
16
17
|
const MAIN = 'PerplexityAI.PerplexityApp_1.2.3.0_x64__3jh4kjrg4dzr2.msix';
|
|
17
18
|
const FRAMEWORK = 'Microsoft.VCLibs.140.00_14.0.33519.0_x64__8wekyb3d8bbwe.appx';
|
|
18
19
|
const sha = (text) => crypto.createHash('sha256').update(text).digest('hex');
|
|
@@ -137,7 +138,9 @@ export const storeAppSuite = {
|
|
|
137
138
|
run: fakeStore({}, false).run,
|
|
138
139
|
});
|
|
139
140
|
t.check(outcome.notes.length === 1 &&
|
|
140
|
-
outcome.notes[0].includes('
|
|
141
|
+
outcome.notes[0].includes('NOT installed in this sandbox') &&
|
|
142
|
+
outcome.notes[0].includes('no Store access') &&
|
|
143
|
+
outcome.notes[0].startsWith('perplexity-desktop:') &&
|
|
141
144
|
outcome.notes[0].includes('winget download --id 9P9XG917PWCJ'), 'with nothing kept the launch goes on, with a note that says what to fix');
|
|
142
145
|
const good = await prepareStorePackages([app], {
|
|
143
146
|
storageRoot: emptyRoot,
|
|
@@ -145,12 +148,67 @@ export const storeAppSuite = {
|
|
|
145
148
|
run: fakeStore({ [MAIN]: 'a' }).run,
|
|
146
149
|
});
|
|
147
150
|
t.check(good.notes[0].includes('downloaded') && fs.existsSync(path.join(stagedPackageDir(emptyData, app.id), MAIN)), 'and a working Store stages it');
|
|
151
|
+
t.section('frameworks: the right builds, and the app told from them');
|
|
152
|
+
const VCLIBS_X86 = 'Microsoft.VCLibs.140.00_14.0.33519.0_x86__8wekyb3d8bbwe.appx';
|
|
153
|
+
const VCLIBS_ARM = 'Microsoft.VCLibs.140.00_14.0.33519.0_arm64__8wekyb3d8bbwe.appx';
|
|
154
|
+
t.check(storePackageFitsX64(FRAMEWORK) &&
|
|
155
|
+
!storePackageFitsX64(VCLIBS_X86) &&
|
|
156
|
+
!storePackageFitsX64(VCLIBS_ARM) &&
|
|
157
|
+
storePackageFitsX64('Microsoft.WindowsAppRuntime.1.6_6000.311.0_neutral__8wekyb3d8bbwe.msix'), '32-bit and ARM frameworks are left out, x64 and neutral ones stay');
|
|
158
|
+
t.equal(assignStoreRoles(['Perplexity_x64.msixbundle', FRAMEWORK], PERPLEXITY_DESKTOP_PACKAGE, [FRAMEWORK]).map((f) => f.role), ['main', 'dependency'], 'an app winget names another way is still found: it is the file outside Dependencies');
|
|
159
|
+
t.check((() => {
|
|
160
|
+
try {
|
|
161
|
+
assignStoreRoles([FRAMEWORK], PERPLEXITY_DESKTOP_PACKAGE, [FRAMEWORK]);
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
return String(e).includes('only frameworks');
|
|
166
|
+
}
|
|
167
|
+
})(), 'frameworks without the app are refused with a plain reason');
|
|
168
|
+
const rich = await new StorePackages({
|
|
169
|
+
storageRoot: t.dir('pc3', 'storage'),
|
|
170
|
+
dataPath: t.dir('pc3', 'storage', 'data', 'set'),
|
|
171
|
+
run: (_exe, args) => {
|
|
172
|
+
const out = args[args.indexOf('-OutDir') + 1];
|
|
173
|
+
fs.mkdirSync(out, { recursive: true });
|
|
174
|
+
for (const n of ['Perplexity_1.2_x64.msixbundle', FRAMEWORK, VCLIBS_X86, VCLIBS_ARM])
|
|
175
|
+
fs.writeFileSync(path.join(out, n), n);
|
|
176
|
+
return Promise.resolve({
|
|
177
|
+
status: 0,
|
|
178
|
+
stdout: JSON.stringify({
|
|
179
|
+
ok: true,
|
|
180
|
+
message: 'ok',
|
|
181
|
+
files: ['Perplexity_1.2_x64.msixbundle', FRAMEWORK, VCLIBS_X86, VCLIBS_ARM],
|
|
182
|
+
dependencies: [FRAMEWORK, VCLIBS_X86, VCLIBS_ARM],
|
|
183
|
+
}),
|
|
184
|
+
stderr: '',
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
}).fetch(app);
|
|
188
|
+
t.check(rich.files.map((f) => f.role).join() === 'main,dependency' &&
|
|
189
|
+
rich.files.every((f) => storePackageFitsX64(f.name)), 'a fetch keeps the app and only the x64 framework, with the roles the script reported');
|
|
190
|
+
t.section('a sign-in for an app that is not installed says so');
|
|
191
|
+
t.equal(appsNeededFor('perplexityapp'), ['perplexity-desktop'], 'the app a sign-in needs');
|
|
192
|
+
t.equal(appsNeededFor('epic'), [], 'a sign-in a toolchain uses needs no app');
|
|
193
|
+
const sharing = { ...DEFAULTS, accounts: { ...DEFAULTS.accounts, perplexityapp: { share: true } } };
|
|
194
|
+
const deliverFor = (on) => prepareServiceAccounts({
|
|
195
|
+
config: sharing,
|
|
196
|
+
others: [],
|
|
197
|
+
on: new Set(on),
|
|
198
|
+
storageRoot: t.dir('pc4', 'storage'),
|
|
199
|
+
dataPath: t.dir('pc4', 'storage', 'data', 'set'),
|
|
200
|
+
launchId: 'L-1',
|
|
201
|
+
isRunning: () => false,
|
|
202
|
+
});
|
|
203
|
+
const missing = deliverFor([]);
|
|
204
|
+
t.check(missing.notes.some((n) => n.includes('accounts.perplexityapp.share') && n.includes('Fix: turn the app on in Apps')) && !missing.leased.length, 'share on with the app off: a launch note names the setting and the app to turn on');
|
|
205
|
+
t.check(!deliverFor(['perplexityapp']).notes.some((n) => n.includes('is not installed')), 'with the app on there is no such note');
|
|
148
206
|
t.section('the sandbox checks the kept files');
|
|
149
207
|
const checked = await checkedPackage(staged);
|
|
150
208
|
t.check(path.basename(checked.main) === MAIN && checked.dependencies.map((d) => path.basename(d)).join() === FRAMEWORK, 'the app and its frameworks, in order');
|
|
151
209
|
fs.writeFileSync(path.join(staged, FRAMEWORK), 'tampered');
|
|
152
210
|
t.check((await t.thrownMessage(() => checkedPackage(staged))).includes('missing or changed'), 'a changed file stops the install');
|
|
153
|
-
t.check((await t.thrownMessage(() => checkedPackage(t.dir('nothing')))).includes('not
|
|
211
|
+
t.check((await t.thrownMessage(() => checkedPackage(t.dir('nothing')))).includes('did not hand over its package'), 'no package says so');
|
|
154
212
|
t.section('the shortcut');
|
|
155
213
|
const args = perplexityAppShortcutArgs('C:\\AgentLaunch\\sandbox\\scripts\\start-store-app.ps1', 'C:\\Users\\X\\profile');
|
|
156
214
|
t.check(args.includes('-File "C:\\AgentLaunch\\sandbox\\scripts\\start-store-app.ps1"') &&
|
|
@@ -563,7 +563,7 @@ export function sampleAuditLog(dir) {
|
|
|
563
563
|
proc(94, 'C:\\AgentData\\tools\\uv\\uv.exe', 'uv tool install aider-chat', {}),
|
|
564
564
|
{ t: at(90), k: 'session', a: 'start', p: 'claude', w: SESSIONS[0].cwd, c: claude.s, ...claude, sv: undefined },
|
|
565
565
|
proc(88, git, 'git status --short', claude),
|
|
566
|
-
proc(87, node, 'node dist/bin/
|
|
566
|
+
proc(87, node, 'node dist/bin/sandbox.js self-test audit', claude, 2),
|
|
567
567
|
file(80, 'src/host/audit/collect.ts', ws, claude),
|
|
568
568
|
file(79, 'src/app/features/audit/audit.def.ts', ws, claude),
|
|
569
569
|
proc(70, 'C:\\nvm\\nodejs\\npm.cmd', 'npm run build', claude),
|
|
@@ -613,7 +613,7 @@ export function sampleAuditLog(dir) {
|
|
|
613
613
|
...codex,
|
|
614
614
|
sv: undefined,
|
|
615
615
|
},
|
|
616
|
-
proc(5, node, 'node dist/bin/
|
|
616
|
+
proc(5, node, 'node dist/bin/sandbox.js self-test app', claude),
|
|
617
617
|
{ t: at(4), k: 'remote', p: 'sessions.restart', a: 'ok', au: "device Josh's iPhone" },
|
|
618
618
|
{ t: at(3), k: 'remote', p: 'settings.set', a: 'refused', au: 'Windows user DESKTOP-JOSH\\sam' },
|
|
619
619
|
]);
|
|
@@ -58,3 +58,8 @@ export declare function usedByText(usedBy: readonly string[]): string;
|
|
|
58
58
|
export declare function appUsersOn(install: readonly string[]): AppUser[];
|
|
59
59
|
/** Whether a sign-in is wanted when these toolchains (and apps) are on. */
|
|
60
60
|
export declare function serviceWanted(kind: ServiceKind, on: ReadonlySet<UsedBy>): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* For a sign-in that only apps use (Perplexity API, Comet, the Perplexity app): the apps.install ids to turn on for it
|
|
63
|
+
* to be handed over; empty for a kind a toolchain uses. A sign-in shared with its app off would otherwise do nothing.
|
|
64
|
+
*/
|
|
65
|
+
export declare function appsNeededFor(kind: ServiceKind): string[];
|
|
@@ -256,3 +256,11 @@ export function serviceWanted(kind, on) {
|
|
|
256
256
|
return true;
|
|
257
257
|
return spec.usedBy.includes('android') && ANDROID_USERS.some((u) => on.has(u));
|
|
258
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* For a sign-in that only apps use (Perplexity API, Comet, the Perplexity app): the apps.install ids to turn on for it
|
|
261
|
+
* to be handed over; empty for a kind a toolchain uses. A sign-in shared with its app off would otherwise do nothing.
|
|
262
|
+
*/
|
|
263
|
+
export function appsNeededFor(kind) {
|
|
264
|
+
const { usedBy } = SERVICE_SPECS[kind];
|
|
265
|
+
return usedBy.every((u) => u in APP_USERS) ? usedBy.flatMap((u) => [...APP_USERS[u]]) : [];
|
|
266
|
+
}
|
|
@@ -5,4 +5,4 @@ export { exeMatcher, appsToInstall, appsNeedMesa, appsNeedUv, storeApps } from '
|
|
|
5
5
|
export { signerMatches, type AppSigner, type Signature } from '../signer.js';
|
|
6
6
|
export { AGENTS_WEB_APPS, BROWSER_USE_VERSION, CHROME_DEVTOOLS_MCP_VERSION, COMET_DOWNLOAD_URL, PERPLEXITY_DESKTOP_FAMILY, PERPLEXITY_DESKTOP_PACKAGE, PERPLEXITY_DESKTOP_PRODUCT_ID, COMET_MCP_VERSION, COMET_SIGNER, PERPLEXITY_COMPUTER_MCP_URL, PERPLEXITY_MCP_VERSION, } from './catalogue-agents-web.js';
|
|
7
7
|
export { ADDON_MCP_PREFIX, addonGuideLine, addonMcpServer, type AddonPlace, claudeManagedSettings, codexWithAddonMcpServers, type CodexMcpServer, HEADROOM_ENV, HEADROOM_PORT, HEADROOM_UPSTREAM, headroomArgs, headroomEnv, headroomGuideLine, headroomUrl, type ManagedSettings, type McpServerConfig, rtkHookCommand, RTK_GUIDE_LINE, withAddonMcpServers, } from './agent-addons.js';
|
|
8
|
-
export { isStorePackageFile, STORE_PACKAGE_MANIFEST, StorePackageFileSchema, StorePackageManifestSchema, storePackageRole, type StorePackageFile, type StorePackageManifest, } from './store-package.js';
|
|
8
|
+
export { isStorePackageFile, STORE_PACKAGE_MANIFEST, StorePackageFileSchema, StorePackageManifestSchema, storePackageRole, storePackageFitsX64, assignStoreRoles, type StorePackageFile, type StorePackageManifest, } from './store-package.js';
|
|
@@ -5,4 +5,4 @@ export { exeMatcher, appsToInstall, appsNeedMesa, appsNeedUv, storeApps } from '
|
|
|
5
5
|
export { signerMatches } from '../signer.js';
|
|
6
6
|
export { AGENTS_WEB_APPS, BROWSER_USE_VERSION, CHROME_DEVTOOLS_MCP_VERSION, COMET_DOWNLOAD_URL, PERPLEXITY_DESKTOP_FAMILY, PERPLEXITY_DESKTOP_PACKAGE, PERPLEXITY_DESKTOP_PRODUCT_ID, COMET_MCP_VERSION, COMET_SIGNER, PERPLEXITY_COMPUTER_MCP_URL, PERPLEXITY_MCP_VERSION, } from './catalogue-agents-web.js';
|
|
7
7
|
export { ADDON_MCP_PREFIX, addonGuideLine, addonMcpServer, claudeManagedSettings, codexWithAddonMcpServers, HEADROOM_ENV, HEADROOM_PORT, HEADROOM_UPSTREAM, headroomArgs, headroomEnv, headroomGuideLine, headroomUrl, rtkHookCommand, RTK_GUIDE_LINE, withAddonMcpServers, } from './agent-addons.js';
|
|
8
|
-
export { isStorePackageFile, STORE_PACKAGE_MANIFEST, StorePackageFileSchema, StorePackageManifestSchema, storePackageRole, } from './store-package.js';
|
|
8
|
+
export { isStorePackageFile, STORE_PACKAGE_MANIFEST, StorePackageFileSchema, StorePackageManifestSchema, storePackageRole, storePackageFitsX64, assignStoreRoles, } from './store-package.js';
|
|
@@ -14,6 +14,7 @@ export declare function appsNeedMesa(apps: Config['apps'] | undefined): boolean;
|
|
|
14
14
|
/** The chosen apps that come from the Microsoft Store: what this PC has to fetch for them. */
|
|
15
15
|
export declare function storeApps(install: readonly string[]): {
|
|
16
16
|
id: string;
|
|
17
|
+
name: string;
|
|
17
18
|
productId: string;
|
|
18
19
|
packageName: string;
|
|
19
20
|
}[];
|
|
@@ -50,9 +50,10 @@ export function appsNeedMesa(apps) {
|
|
|
50
50
|
/** The chosen apps that come from the Microsoft Store: what this PC has to fetch for them. */
|
|
51
51
|
export function storeApps(install) {
|
|
52
52
|
return install.flatMap((id) => {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
const entry = appById(id);
|
|
54
|
+
const source = entry?.source;
|
|
55
|
+
return entry && source !== undefined && 'store' in source
|
|
56
|
+
? [{ id, name: entry.name, productId: source.store.productId, packageName: source.store.packageName }]
|
|
56
57
|
: [];
|
|
57
58
|
});
|
|
58
59
|
}
|
|
@@ -29,3 +29,16 @@ export declare const STORE_PACKAGE_MANIFEST = "store-package.json";
|
|
|
29
29
|
export declare function isStorePackageFile(name: string): boolean;
|
|
30
30
|
/** The app itself (named <package name>_<version>_...), as opposed to a framework it depends on (VCLibs ...). */
|
|
31
31
|
export declare function storePackageRole(fileName: string, packageName: string): 'main' | 'dependency';
|
|
32
|
+
/**
|
|
33
|
+
* Whether a dependency package suits this sandbox (64-bit Windows): winget may also fetch the 32-bit and ARM builds
|
|
34
|
+
* of a framework, and Add-AppxPackage is only handed the builds that can run here.
|
|
35
|
+
*/
|
|
36
|
+
export declare function storePackageFitsX64(fileName: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Which fetched file is the app and which are its frameworks. The app is named after its package; when winget names
|
|
39
|
+
* it otherwise, the one file that did not come from the Dependencies folder is the app. Throws when that is unclear.
|
|
40
|
+
*/
|
|
41
|
+
export declare function assignStoreRoles(names: readonly string[], packageName: string, dependencyNames: readonly string[]): {
|
|
42
|
+
name: string;
|
|
43
|
+
role: 'main' | 'dependency';
|
|
44
|
+
}[];
|
|
@@ -32,3 +32,27 @@ export function isStorePackageFile(name) {
|
|
|
32
32
|
export function storePackageRole(fileName, packageName) {
|
|
33
33
|
return fileName.toLowerCase().startsWith(packageName.toLowerCase() + '_') ? 'main' : 'dependency';
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Whether a dependency package suits this sandbox (64-bit Windows): winget may also fetch the 32-bit and ARM builds
|
|
37
|
+
* of a framework, and Add-AppxPackage is only handed the builds that can run here.
|
|
38
|
+
*/
|
|
39
|
+
export function storePackageFitsX64(fileName) {
|
|
40
|
+
return !/[_.-](x86|arm|arm64)(?=[_.-]|$)/i.test(fileName.replace(/\.(msix|appx|msixbundle|appxbundle)$/i, ''));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Which fetched file is the app and which are its frameworks. The app is named after its package; when winget names
|
|
44
|
+
* it otherwise, the one file that did not come from the Dependencies folder is the app. Throws when that is unclear.
|
|
45
|
+
*/
|
|
46
|
+
export function assignStoreRoles(names, packageName, dependencyNames) {
|
|
47
|
+
const listed = new Set(dependencyNames.map((n) => n.toLowerCase()));
|
|
48
|
+
let mains = names.filter((n) => storePackageRole(n, packageName) === 'main');
|
|
49
|
+
if (mains.length === 0)
|
|
50
|
+
mains = names.filter((n) => !listed.has(n.toLowerCase()));
|
|
51
|
+
if (mains.length > 1)
|
|
52
|
+
mains = mains.filter(storePackageFitsX64);
|
|
53
|
+
if (mains.length !== 1)
|
|
54
|
+
throw new Error(mains.length === 0
|
|
55
|
+
? 'winget gave only frameworks, not the app itself (' + names.join(', ') + ')'
|
|
56
|
+
: 'winget gave more than one app package and it is not clear which to install (' + mains.join(', ') + ')');
|
|
57
|
+
return names.map((name) => ({ name, role: name === mains[0] ? 'main' : 'dependency' }));
|
|
58
|
+
}
|
|
@@ -8,9 +8,9 @@ using System.Runtime.InteropServices;
|
|
|
8
8
|
[assembly: AssemblyCompany("windows-development-sandbox (open source)")]
|
|
9
9
|
[assembly: AssemblyProduct("Windows Development Sandbox")]
|
|
10
10
|
[assembly: AssemblyCopyright("MIT License, windows-development-sandbox contributors")]
|
|
11
|
-
[assembly: AssemblyVersion("1.
|
|
12
|
-
[assembly: AssemblyFileVersion("1.
|
|
13
|
-
[assembly: AssemblyInformationalVersion("1.
|
|
11
|
+
[assembly: AssemblyVersion("1.29.0.0")]
|
|
12
|
+
[assembly: AssemblyFileVersion("1.29.0.0")]
|
|
13
|
+
[assembly: AssemblyInformationalVersion("1.29.0")]
|
|
14
14
|
[assembly: ComVisible(false)]
|
|
15
15
|
// computecore.dll and computestorage.dll are not KnownDLLs: load them from System32 only, never from the exe's folder.
|
|
16
16
|
[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
|
@@ -8,7 +8,7 @@ using System.Runtime.InteropServices;
|
|
|
8
8
|
[assembly: AssemblyCompany("windows-development-sandbox (open source)")]
|
|
9
9
|
[assembly: AssemblyProduct("Windows Development Sandbox")]
|
|
10
10
|
[assembly: AssemblyCopyright("MIT License, windows-development-sandbox contributors")]
|
|
11
|
-
[assembly: AssemblyVersion("1.
|
|
12
|
-
[assembly: AssemblyFileVersion("1.
|
|
13
|
-
[assembly: AssemblyInformationalVersion("1.
|
|
11
|
+
[assembly: AssemblyVersion("1.29.0.0")]
|
|
12
|
+
[assembly: AssemblyFileVersion("1.29.0.0")]
|
|
13
|
+
[assembly: AssemblyInformationalVersion("1.29.0")]
|
|
14
14
|
[assembly: ComVisible(false)]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The Windows Sandbox client window: hidden for a headless sandbox (the VM keeps running), shown again by
|
|
1
|
+
// The Windows Sandbox client window: hidden for a headless sandbox (the VM keeps running), shown again by sandbox show.
|
|
2
2
|
// HostTools hide-window / show-window, or Add-Type from scripts\*-window.ps1 when HostTools cannot be built. C# 5.
|
|
3
3
|
using System;
|
|
4
4
|
using System.Collections.Generic;
|
|
@@ -9,7 +9,7 @@ using System.Runtime.InteropServices;
|
|
|
9
9
|
[assembly: AssemblyCompany("windows-development-sandbox (open source)")]
|
|
10
10
|
[assembly: AssemblyProduct("Windows Development Sandbox")]
|
|
11
11
|
[assembly: AssemblyCopyright("MIT License, windows-development-sandbox contributors")]
|
|
12
|
-
[assembly: AssemblyVersion("1.
|
|
13
|
-
[assembly: AssemblyFileVersion("1.
|
|
14
|
-
[assembly: AssemblyInformationalVersion("1.
|
|
12
|
+
[assembly: AssemblyVersion("1.29.0.0")]
|
|
13
|
+
[assembly: AssemblyFileVersion("1.29.0.0")]
|
|
14
|
+
[assembly: AssemblyInformationalVersion("1.29.0")]
|
|
15
15
|
[assembly: ComVisible(false)]
|