windows-development-sandbox 1.25.1 → 1.27.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/config.example.json +12 -3
- package/dist/app/features/accounts/services.def.d.ts +1 -1
- package/dist/app/features/accounts/services.def.js +1 -1
- package/dist/app/features/defs.d.ts +198 -3
- package/dist/app/features/defs.js +8 -0
- package/dist/app/features/settings/categories.def.js +1 -0
- package/dist/app/features/unity/index.d.ts +2 -0
- package/dist/app/features/unity/index.js +3 -0
- package/dist/app/features/unity/unity.def.d.ts +294 -0
- package/dist/app/features/unity/unity.def.js +471 -0
- package/dist/app/features/unity/unity.run.d.ts +142 -0
- package/dist/app/features/unity/unity.run.js +363 -0
- package/dist/app/registry.js +6 -0
- package/dist/bin/agent-sandbox.js +2 -2
- package/dist/bin/{chunk-F4PVBN23.js → chunk-6WWMBCT2.js} +3580 -1962
- package/dist/bin/{chunk-3GAXLATK.js → chunk-JXL6ZOO3.js} +72 -5
- package/dist/bin/{chunk-G3TG4ZT7.js → chunk-ZXCAVVS4.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 +12 -0
- package/dist/cli/commands/registry.js +3 -0
- package/dist/cli/manifest.d.ts +33 -1
- package/dist/cli/manifest.js +32 -1
- 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 +40 -3
- package/dist/cli/menu/views.js +2 -0
- package/dist/core/config/defaults.js +16 -3
- package/dist/core/schema/accounts.d.ts +3 -0
- package/dist/core/schema/accounts.js +1 -0
- package/dist/core/schema/config.d.ts +29 -13
- package/dist/core/schema/github.d.ts +1 -0
- package/dist/core/schema/github.js +3 -0
- package/dist/core/schema/pc-installs.d.ts +2 -1
- package/dist/core/schema/pc-installs.js +6 -1
- package/dist/core/schema/toolchains.d.ts +8 -0
- package/dist/core/schema/toolchains.js +19 -4
- package/dist/guest/accounts/adapters.js +2 -0
- package/dist/guest/accounts/apply.d.ts +4 -2
- package/dist/guest/accounts/kinds/perplexityapp.d.ts +7 -0
- package/dist/guest/accounts/kinds/perplexityapp.js +53 -0
- package/dist/guest/accounts/kinds/unity.d.ts +27 -0
- package/dist/guest/accounts/kinds/unity.js +164 -7
- package/dist/guest/apps/app-installer.js +18 -0
- package/dist/guest/apps/store-app.d.ts +4 -0
- package/dist/guest/apps/store-app.js +19 -0
- package/dist/guest/apps/store-check.d.ts +8 -0
- package/dist/guest/apps/store-check.js +33 -0
- package/dist/guest/boot/boot.js +1 -1
- package/dist/guest/boot/guide.js +18 -0
- package/dist/guest/browser/comet.d.ts +3 -1
- package/dist/guest/browser/comet.js +1 -1
- package/dist/guest/browser/perplexity-app.d.ts +12 -0
- package/dist/guest/browser/perplexity-app.js +60 -0
- package/dist/guest/daemon/daemon.js +2 -0
- package/dist/guest/devtools/devtool.d.ts +1 -1
- package/dist/guest/devtools/registry.js +1 -2
- package/dist/guest/entries/tool.js +1 -1
- package/dist/guest/setup/addon-mcp.d.ts +7 -2
- package/dist/guest/setup/addon-mcp.js +19 -9
- package/dist/guest/setup/github-token.js +4 -4
- package/dist/guest/setup/unity-agent.d.ts +11 -0
- package/dist/guest/setup/unity-agent.js +36 -0
- package/dist/guest/toolchains/engines/unity-cli.d.ts +24 -0
- package/dist/guest/toolchains/engines/unity-cli.js +74 -0
- package/dist/guest/toolchains/engines/unity-hub.d.ts +2 -0
- package/dist/guest/toolchains/engines/unity-hub.js +27 -0
- package/dist/guest/toolchains/engines/unity-release.d.ts +4 -15
- package/dist/guest/toolchains/engines/unity-release.js +17 -5
- package/dist/guest/toolchains/engines/unity-status.d.ts +11 -0
- package/dist/guest/toolchains/engines/unity-status.js +39 -0
- package/dist/guest/toolchains/engines/unity.d.ts +20 -1
- package/dist/guest/toolchains/engines/unity.js +250 -3
- package/dist/guest/toolchains/plan.js +5 -1
- package/dist/guest/toolchains/tools/cli.js +2 -2
- package/dist/host/accounts/deliver.js +4 -0
- package/dist/host/accounts/kinds/perplexityapp.d.ts +2 -0
- package/dist/host/accounts/kinds/perplexityapp.js +8 -0
- package/dist/host/accounts/kinds/unity.d.ts +1 -1
- package/dist/host/accounts/kinds/unity.js +60 -8
- package/dist/host/accounts/leases.d.ts +2 -1
- package/dist/host/accounts/sources.js +2 -0
- package/dist/host/apps/index.d.ts +1 -0
- package/dist/host/apps/index.js +2 -0
- package/dist/host/apps/store-package.d.ts +51 -0
- package/dist/host/apps/store-package.js +209 -0
- package/dist/host/github/deliver.d.ts +8 -3
- package/dist/host/github/deliver.js +42 -29
- package/dist/host/github/index.d.ts +1 -0
- package/dist/host/github/index.js +1 -0
- package/dist/host/github/pc-login.d.ts +27 -0
- package/dist/host/github/pc-login.js +129 -0
- package/dist/host/pc-installs/discover.d.ts +4 -0
- package/dist/host/pc-installs/discover.js +16 -4
- package/dist/host/pc-installs/source.d.ts +1 -1
- package/dist/host/pc-installs/unity-assets.d.ts +5 -0
- package/dist/host/pc-installs/unity-assets.js +18 -0
- package/dist/host/pc-installs/unity.d.ts +20 -0
- package/dist/host/pc-installs/unity.js +135 -2
- package/dist/runtime/agent.js +116 -11
- package/dist/runtime/daemon.js +2170 -976
- package/dist/runtime/db.js +116 -11
- package/dist/runtime/main.js +2543 -1364
- package/dist/runtime/netproxy.js +116 -11
- package/dist/runtime/tool.js +133 -31
- package/dist/runtime/view.js +116 -11
- package/dist/selftest/agents-web.test.js +2 -1
- package/dist/selftest/engines.test.js +2 -2
- package/dist/selftest/github.test.js +64 -1
- package/dist/selftest/index.js +10 -0
- package/dist/selftest/menu-screens.js +3 -1
- package/dist/selftest/persisted-installs.test.js +1 -1
- package/dist/selftest/service-accounts.test.js +1 -1
- package/dist/selftest/store-app.test.d.ts +2 -0
- package/dist/selftest/store-app.test.js +162 -0
- package/dist/selftest/support/engine-golden.js +7 -0
- package/dist/selftest/support/fixtures.d.ts +10 -1
- package/dist/selftest/support/unity-sample.d.ts +2 -0
- package/dist/selftest/support/unity-sample.js +80 -0
- package/dist/selftest/unity-accounts.test.d.ts +2 -0
- package/dist/selftest/unity-accounts.test.js +106 -0
- package/dist/selftest/unity-agent.test.d.ts +2 -0
- package/dist/selftest/unity-agent.test.js +47 -0
- package/dist/selftest/unity-page.test.d.ts +2 -0
- package/dist/selftest/unity-page.test.js +177 -0
- package/dist/selftest/unity-setup.test.d.ts +2 -0
- package/dist/selftest/unity-setup.test.js +49 -0
- package/dist/selftest/unity.test.js +238 -3
- package/dist/shared/accounts/index.d.ts +1 -0
- package/dist/shared/accounts/index.js +1 -0
- package/dist/shared/accounts/kinds/unity.d.ts +28 -0
- package/dist/shared/accounts/kinds/unity.js +88 -0
- package/dist/shared/accounts/kinds.d.ts +2 -2
- package/dist/shared/accounts/kinds.js +23 -1
- package/dist/shared/accounts/payloads.d.ts +52 -0
- package/dist/shared/accounts/payloads.js +14 -2
- package/dist/shared/apps/catalogue-agents-web.d.ts +7 -0
- package/dist/shared/apps/catalogue-agents-web.js +23 -0
- package/dist/shared/apps/catalogue-gamedev.d.ts +3 -0
- package/dist/shared/apps/catalogue-gamedev.js +31 -0
- package/dist/shared/apps/catalogue.d.ts +8 -0
- package/dist/shared/apps/index.d.ts +3 -2
- package/dist/shared/apps/index.js +3 -2
- package/dist/shared/apps/selection.d.ts +6 -0
- package/dist/shared/apps/selection.js +9 -0
- package/dist/shared/apps/store-package.d.ts +31 -0
- package/dist/shared/apps/store-package.js +34 -0
- package/dist/shared/channel/schema.d.ts +24 -0
- package/dist/shared/channel/secrets.d.ts +17 -0
- package/dist/shared/paths/pc-installs.d.ts +1 -1
- package/dist/shared/paths/pc-installs.js +1 -0
- package/dist/shared/readiness.d.ts +2 -2
- package/dist/shared/readiness.js +2 -2
- package/dist/shared/unity/asset-cache.d.ts +20 -0
- package/dist/shared/unity/asset-cache.js +44 -0
- package/dist/shared/unity/cli.d.ts +40 -0
- package/dist/shared/unity/cli.js +77 -0
- package/dist/shared/unity/editors.d.ts +46 -0
- package/dist/shared/unity/editors.js +89 -0
- package/dist/shared/unity/guide.d.ts +9 -0
- package/dist/shared/unity/guide.js +84 -0
- package/dist/shared/unity/index.d.ts +9 -0
- package/dist/shared/unity/index.js +11 -0
- package/dist/shared/unity/manifest.d.ts +10 -0
- package/dist/shared/unity/manifest.js +53 -0
- package/dist/shared/unity/mcp.d.ts +8 -0
- package/dist/shared/unity/mcp.js +12 -0
- package/dist/shared/unity/release.d.ts +28 -0
- package/dist/shared/unity/release.js +83 -0
- package/dist/shared/unity/status.d.ts +59 -0
- package/dist/shared/unity/status.js +45 -0
- package/dist/shared/unity/version.d.ts +27 -0
- package/dist/shared/unity/version.js +75 -0
- package/dist/web/index.html +1 -1
- package/dist/web/{main-FNE6XEHA.js → main-VPMYTSUL.js} +67 -67
- 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/native/scripts/get-store-package.ps1 +58 -0
- package/package.json +1 -1
- package/sandbox/addons/openupm-cli/package-lock.json +2921 -0
- package/sandbox/addons/openupm-cli/package.json +7 -0
- package/sandbox/addons/unity-mcp/requirements.in +1 -0
- package/sandbox/addons/unity-mcp/requirements.txt +1165 -0
- package/sandbox/csharp/AppActivation.cs +38 -0
- package/sandbox/scripts/install-store-package.ps1 +28 -0
- package/sandbox/scripts/start-store-app.ps1 +29 -0
- package/schemas/config.schema.json +61 -6
- package/schemas/sandbox-config.schema.json +38 -5
- package/dist/guest/devtools/unity.d.ts +0 -2
- package/dist/guest/devtools/unity.js +0 -7
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// Microsoft Store apps for the sandbox (the Perplexity desktop app): this PC fetches the package files with its own
|
|
2
|
+
// Store tooling (native\scripts\get-store-package.ps1: winget download --source msstore, with your own Windows access;
|
|
3
|
+
// no account token is ever held here), keeps them in <storage>\store-packages\<product id>, and copies them into the
|
|
4
|
+
// data folder (tools\apps\<id>.package) where the sandbox installs them with Add-AppxPackage. The package keeps the
|
|
5
|
+
// publisher's signature: nothing is repacked. The files are hashed here, never trusted from the script's word.
|
|
6
|
+
import crypto from 'node:crypto';
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { spawn } from 'node:child_process';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { assertNoLinks, errMsg } from '../../core/util/index.js';
|
|
12
|
+
import { readValid } from '../../shared/io/index.js';
|
|
13
|
+
import { isStorePackageFile, STORE_PACKAGE_MANIFEST, StorePackageManifestSchema, storePackageRole, } from '../../shared/apps/index.js';
|
|
14
|
+
import { SYSTEM } from '../../shared/system.js';
|
|
15
|
+
import { nativeScript } from '../native.js';
|
|
16
|
+
import { hostDataFolder } from '../storage/contained/index.js';
|
|
17
|
+
import { refuseInSelfTest } from '../test-guard.js';
|
|
18
|
+
/** How long a fetched package is used before a launch looks for a newer one (a failed look keeps the old one). */
|
|
19
|
+
export const STORE_PACKAGE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
|
|
20
|
+
/** The download is about 300 MB and Store servers can be slow. */
|
|
21
|
+
const FETCH_TIMEOUT_MS = 20 * 60 * 1000;
|
|
22
|
+
const READ_CHUNK = 1024 * 1024;
|
|
23
|
+
const ScriptResultSchema = z.object({
|
|
24
|
+
ok: z.boolean(),
|
|
25
|
+
message: z.string().max(2000),
|
|
26
|
+
files: z.array(z.string().max(300)).max(64),
|
|
27
|
+
});
|
|
28
|
+
export const spawnStoreRunner = (exe, args, timeoutMs) => new Promise((resolve) => {
|
|
29
|
+
const child = spawn(exe, [...args], { windowsHide: true });
|
|
30
|
+
let stdout = '';
|
|
31
|
+
let stderr = '';
|
|
32
|
+
const timer = setTimeout(() => child.kill(), timeoutMs);
|
|
33
|
+
child.stdout.on('data', (d) => (stdout = (stdout + d.toString('utf8')).slice(-64 * 1024)));
|
|
34
|
+
child.stderr.on('data', (d) => (stderr = (stderr + d.toString('utf8')).slice(-64 * 1024)));
|
|
35
|
+
child.on('error', (e) => resolve({ status: null, stdout, stderr: stderr + e.message }));
|
|
36
|
+
child.on('close', (status) => {
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
resolve({ status, stdout, stderr });
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
function sha256Of(file) {
|
|
42
|
+
const hash = crypto.createHash('sha256');
|
|
43
|
+
const fd = fs.openSync(file, 'r');
|
|
44
|
+
try {
|
|
45
|
+
const chunk = Buffer.alloc(READ_CHUNK);
|
|
46
|
+
for (let n = fs.readSync(fd, chunk, 0, chunk.length, null); n > 0; n = fs.readSync(fd, chunk, 0, chunk.length, null))
|
|
47
|
+
hash.update(chunk.subarray(0, n));
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
fs.closeSync(fd);
|
|
51
|
+
}
|
|
52
|
+
return hash.digest('hex');
|
|
53
|
+
}
|
|
54
|
+
/** Where the data folder keeps an app's package for the sandbox. */
|
|
55
|
+
export function stagedPackageDir(dataPath, appId) {
|
|
56
|
+
return path.join(hostDataFolder(dataPath, 'tools'), 'apps', appId + '.package');
|
|
57
|
+
}
|
|
58
|
+
export class StorePackages {
|
|
59
|
+
o;
|
|
60
|
+
constructor(o) {
|
|
61
|
+
this.o = o;
|
|
62
|
+
}
|
|
63
|
+
cacheDir(productId) {
|
|
64
|
+
return path.join(this.o.storageRoot, 'store-packages', productId);
|
|
65
|
+
}
|
|
66
|
+
/** The cached package when its files are all there at the size hashed; null otherwise. */
|
|
67
|
+
cached(productId) {
|
|
68
|
+
const dir = this.cacheDir(productId);
|
|
69
|
+
const manifest = readValid(StorePackageManifestSchema, path.join(dir, STORE_PACKAGE_MANIFEST));
|
|
70
|
+
if (!manifest || manifest.productId !== productId)
|
|
71
|
+
return null;
|
|
72
|
+
const complete = manifest.files.every((f) => {
|
|
73
|
+
try {
|
|
74
|
+
return fs.statSync(path.join(dir, f.name)).size === f.bytes;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false; // A file is gone: fetch again.
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return complete ? manifest : null;
|
|
81
|
+
}
|
|
82
|
+
fresh(m) {
|
|
83
|
+
const at = Date.parse(m.fetchedAt);
|
|
84
|
+
return Number.isFinite(at) && (this.o.now?.() ?? Date.now()) - at < STORE_PACKAGE_MAX_AGE_MS;
|
|
85
|
+
}
|
|
86
|
+
/** Fetches the package into the cache with this PC's Store tooling and returns what was kept. */
|
|
87
|
+
async fetch(app) {
|
|
88
|
+
const run = this.o.run ?? spawnStoreRunner;
|
|
89
|
+
if (!this.o.run)
|
|
90
|
+
refuseInSelfTest('download from the Microsoft Store');
|
|
91
|
+
const work = this.cacheDir(app.productId) + '.new';
|
|
92
|
+
fs.rmSync(work, { recursive: true, force: true });
|
|
93
|
+
const out = await run(SYSTEM.powershell(), nativeScript('get-store-package.ps1', [
|
|
94
|
+
'-ProductId',
|
|
95
|
+
app.productId,
|
|
96
|
+
'-PackageName',
|
|
97
|
+
app.packageName,
|
|
98
|
+
'-OutDir',
|
|
99
|
+
work,
|
|
100
|
+
]), FETCH_TIMEOUT_MS);
|
|
101
|
+
try {
|
|
102
|
+
return this.keep(app, work, out);
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
fs.rmSync(work, { recursive: true, force: true });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Checks what the script fetched into `work`, hashes it and moves it into the cache. */
|
|
109
|
+
keep(app, work, out) {
|
|
110
|
+
const line = out.stdout.trim().split(/\r?\n/).pop() ?? '';
|
|
111
|
+
let parsed;
|
|
112
|
+
try {
|
|
113
|
+
parsed = ScriptResultSchema.parse(JSON.parse(line));
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
throw new Error('the Store tooling gave no answer' + (out.stderr ? ': ' + out.stderr.trim().slice(0, 200) : ''));
|
|
117
|
+
}
|
|
118
|
+
if (!parsed.ok)
|
|
119
|
+
throw new Error(parsed.message);
|
|
120
|
+
const files = parsed.files
|
|
121
|
+
.filter((n) => isStorePackageFile(n) && n === path.basename(n) && fs.existsSync(path.join(work, n)))
|
|
122
|
+
.map((name) => {
|
|
123
|
+
const full = path.join(work, name);
|
|
124
|
+
return {
|
|
125
|
+
name,
|
|
126
|
+
role: storePackageRole(name, app.packageName),
|
|
127
|
+
sha256: sha256Of(full),
|
|
128
|
+
bytes: fs.statSync(full).size,
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
const manifest = StorePackageManifestSchema.parse({
|
|
132
|
+
productId: app.productId,
|
|
133
|
+
fetchedAt: new Date(this.o.now?.() ?? Date.now()).toISOString(),
|
|
134
|
+
files,
|
|
135
|
+
});
|
|
136
|
+
fs.writeFileSync(path.join(work, STORE_PACKAGE_MANIFEST), JSON.stringify(manifest, null, 2), 'utf8');
|
|
137
|
+
const dest = this.cacheDir(app.productId);
|
|
138
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
139
|
+
fs.renameSync(work, dest);
|
|
140
|
+
return manifest;
|
|
141
|
+
}
|
|
142
|
+
/** The package to use: the cache when fresh, else a new fetch (the old one stands when that fails). */
|
|
143
|
+
async ensure(app) {
|
|
144
|
+
const have = this.cached(app.productId);
|
|
145
|
+
if (have && this.fresh(have))
|
|
146
|
+
return { manifest: have, note: 'kept from ' + have.fetchedAt.slice(0, 10) };
|
|
147
|
+
try {
|
|
148
|
+
return { manifest: await this.fetch(app), note: 'downloaded' };
|
|
149
|
+
}
|
|
150
|
+
catch (e) {
|
|
151
|
+
if (have)
|
|
152
|
+
return { manifest: have, note: 'kept (a newer one could not be fetched: ' + errMsg(e) + ')' };
|
|
153
|
+
throw e;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/** Copies the cached package into the data folder for the sandbox (files already there at the same size stay). */
|
|
157
|
+
stage(app, manifest) {
|
|
158
|
+
const tools = hostDataFolder(this.o.dataPath, 'tools');
|
|
159
|
+
const dest = stagedPackageDir(this.o.dataPath, app.id);
|
|
160
|
+
fs.mkdirSync(path.join(tools, 'apps'), { recursive: true });
|
|
161
|
+
assertNoLinks(tools, dest);
|
|
162
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
163
|
+
const wanted = new Set(manifest.files.map((f) => f.name.toLowerCase()));
|
|
164
|
+
for (const name of fs.readdirSync(dest)) {
|
|
165
|
+
if (name !== STORE_PACKAGE_MANIFEST && !wanted.has(name.toLowerCase()))
|
|
166
|
+
fs.rmSync(path.join(dest, name), { recursive: true, force: true });
|
|
167
|
+
}
|
|
168
|
+
const source = this.cacheDir(manifest.productId);
|
|
169
|
+
for (const f of manifest.files) {
|
|
170
|
+
const target = path.join(dest, f.name);
|
|
171
|
+
assertNoLinks(tools, target);
|
|
172
|
+
let same = false;
|
|
173
|
+
try {
|
|
174
|
+
same = fs.statSync(target).size === f.bytes;
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
same = false; // Not there yet.
|
|
178
|
+
}
|
|
179
|
+
if (!same)
|
|
180
|
+
fs.copyFileSync(path.join(source, f.name), target);
|
|
181
|
+
}
|
|
182
|
+
fs.writeFileSync(path.join(dest, STORE_PACKAGE_MANIFEST), JSON.stringify(manifest, null, 2), 'utf8');
|
|
183
|
+
return dest;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* For a launch: every Store app chosen gets its package fetched (or kept) and staged. A failure never stops the launch:
|
|
188
|
+
* it is a note, and the sandbox skips that app.
|
|
189
|
+
*/
|
|
190
|
+
export async function prepareStorePackages(apps, o) {
|
|
191
|
+
const notes = [];
|
|
192
|
+
const packages = new StorePackages(o);
|
|
193
|
+
for (const app of apps) {
|
|
194
|
+
try {
|
|
195
|
+
const { manifest, note } = await packages.ensure(app);
|
|
196
|
+
packages.stage(app, manifest);
|
|
197
|
+
notes.push(app.id + ': package ' + note + ' (' + manifest.files.length + ' files)');
|
|
198
|
+
}
|
|
199
|
+
catch (e) {
|
|
200
|
+
notes.push(app.id +
|
|
201
|
+
' is not prepared: ' +
|
|
202
|
+
errMsg(e) +
|
|
203
|
+
' Fix: sign in to the Microsoft Store on this PC and check that winget works (winget download --id ' +
|
|
204
|
+
app.productId +
|
|
205
|
+
' --source msstore), then launch again.');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return { notes };
|
|
209
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { BootWant } from '../../core/local-config/index.js';
|
|
2
2
|
import type { Config } from '../../core/types.js';
|
|
3
3
|
import { GithubToken } from './token.js';
|
|
4
|
-
|
|
4
|
+
import { type PcLoginOptions } from './pc-login.js';
|
|
5
|
+
/** github.shareToken or github.usePcLogin is on and git access is "full" for every project this sandbox boots with. */
|
|
5
6
|
export declare function githubTokenAllowed(config: Config, others?: readonly BootWant[]): boolean;
|
|
6
7
|
export interface GithubDelivery {
|
|
7
8
|
/** The token for the one-shot secrets file, when it is handed over. */
|
|
@@ -9,5 +10,9 @@ export interface GithubDelivery {
|
|
|
9
10
|
/** What to tell you about it (never the token). */
|
|
10
11
|
readonly notes: readonly string[];
|
|
11
12
|
}
|
|
12
|
-
/**
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* What this launch hands over. Never throws: a token that cannot be read is a note, and the launch goes on. A token
|
|
15
|
+
* saved for sandboxes (github.shareToken) comes first; then this PC's own gh login (github.usePcLogin), which says
|
|
16
|
+
* nothing when the PC has none.
|
|
17
|
+
*/
|
|
18
|
+
export declare function prepareGithubToken(config: Config, storageRoot: string, store?: GithubToken, others?: readonly BootWant[], now?: number, pc?: PcLoginOptions): GithubDelivery;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { GithubToken } from './token.js';
|
|
2
|
+
import { readPcGhLogin } from './pc-login.js';
|
|
2
3
|
/** The first project (the launch's own, then the others at boot) whose git access is not "full", or null. */
|
|
3
4
|
function stricterProject(config, others) {
|
|
4
5
|
if (config.provisioning.git.access !== 'full')
|
|
@@ -6,9 +7,9 @@ function stricterProject(config, others) {
|
|
|
6
7
|
const other = others.find((o) => o.config.provisioning.git.access !== 'full');
|
|
7
8
|
return other ? { name: other.name, access: other.config.provisioning.git.access } : null;
|
|
8
9
|
}
|
|
9
|
-
/** github.shareToken is on and git access is "full" for every project this sandbox boots with. */
|
|
10
|
+
/** github.shareToken or github.usePcLogin is on and git access is "full" for every project this sandbox boots with. */
|
|
10
11
|
export function githubTokenAllowed(config, others = []) {
|
|
11
|
-
return config.github.shareToken && stricterProject(config, others) === null;
|
|
12
|
+
return (config.github.shareToken || config.github.usePcLogin) && stricterProject(config, others) === null;
|
|
12
13
|
}
|
|
13
14
|
/** A token that ends within this many days is mentioned at launch. */
|
|
14
15
|
const EXPIRY_NOTE_DAYS = 7;
|
|
@@ -28,43 +29,55 @@ function expiryNotes(tokens, now = Date.now()) {
|
|
|
28
29
|
'. Make a new one in Accounts.',
|
|
29
30
|
];
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
const PC_LOGIN_NOTE = 'Your gh login on this PC is handed to this sandbox (github.usePcLogin): the agent can read it and use it for everything your account can.';
|
|
33
|
+
/**
|
|
34
|
+
* What this launch hands over. Never throws: a token that cannot be read is a note, and the launch goes on. A token
|
|
35
|
+
* saved for sandboxes (github.shareToken) comes first; then this PC's own gh login (github.usePcLogin), which says
|
|
36
|
+
* nothing when the PC has none.
|
|
37
|
+
*/
|
|
38
|
+
export function prepareGithubToken(config, storageRoot, store, others = [], now = Date.now(), pc = {}) {
|
|
39
|
+
const { shareToken, usePcLogin } = config.github;
|
|
40
|
+
if (!shareToken && !usePcLogin)
|
|
34
41
|
return { notes: [] };
|
|
35
42
|
const stricter = stricterProject(config, others);
|
|
36
43
|
if (stricter)
|
|
37
44
|
return {
|
|
38
45
|
notes: [
|
|
39
|
-
'github.
|
|
46
|
+
'github.' +
|
|
47
|
+
(shareToken ? 'shareToken' : 'usePcLogin') +
|
|
48
|
+
' is on, but git access is "' +
|
|
40
49
|
stricter.access +
|
|
41
50
|
'"' +
|
|
42
51
|
(stricter.name ? ' for ' + stricter.name + ' (also in this sandbox)' : ' in this sandbox') +
|
|
43
|
-
', so your GitHub
|
|
52
|
+
', so your GitHub login was not handed over (it needs provisioning.git.access "full").',
|
|
44
53
|
],
|
|
45
54
|
};
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
token =
|
|
55
|
+
const notes = [];
|
|
56
|
+
if (shareToken) {
|
|
57
|
+
const tokens = store ?? new GithubToken(storageRoot);
|
|
58
|
+
let token = null;
|
|
59
|
+
try {
|
|
60
|
+
token = tokens.read();
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// The reason may come from DPAPI or a damaged file; a fixed text is enough (and can never hold the token).
|
|
64
|
+
notes.push('Your saved GitHub token could not be read (only the Windows user that saved it can), so it was not handed over. Save it again: sandbox github login');
|
|
65
|
+
}
|
|
66
|
+
if (token)
|
|
67
|
+
return {
|
|
68
|
+
token,
|
|
69
|
+
notes: [
|
|
70
|
+
'Your GitHub token is handed to this sandbox (github.shareToken): the agent can read it and use it for whatever it allows.',
|
|
71
|
+
...expiryNotes(tokens, now),
|
|
72
|
+
],
|
|
73
|
+
};
|
|
74
|
+
if (!usePcLogin && !notes.length)
|
|
75
|
+
notes.push('github.shareToken is on, but no GitHub token is saved. Save one: sandbox github login');
|
|
50
76
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
notes: [
|
|
55
|
-
'Your saved GitHub token could not be read (only the Windows user that saved it can), so it was not handed over. Save it again: sandbox github login',
|
|
56
|
-
],
|
|
57
|
-
};
|
|
77
|
+
if (usePcLogin) {
|
|
78
|
+
const login = readPcGhLogin(pc);
|
|
79
|
+
if (login)
|
|
80
|
+
return { token: login, notes: [...notes, PC_LOGIN_NOTE] };
|
|
58
81
|
}
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
notes: ['github.shareToken is on, but no GitHub token is saved. Save one: sandbox github login'],
|
|
62
|
-
};
|
|
63
|
-
return {
|
|
64
|
-
token,
|
|
65
|
-
notes: [
|
|
66
|
-
'Your GitHub token is handed to this sandbox (github.shareToken): the agent can read it and use it for whatever it allows.',
|
|
67
|
-
...expiryNotes(tokens, now),
|
|
68
|
-
],
|
|
69
|
-
};
|
|
82
|
+
return { notes };
|
|
70
83
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { GithubToken, checkGithubToken, type SavedCheck, type TokenState } from './token.js';
|
|
2
2
|
export { githubTokenAllowed, prepareGithubToken, type GithubDelivery } from './deliver.js';
|
|
3
3
|
export { expiryFromHeader, verifyGithubToken, type GithubCheck, type GithubVerdict } from './verify.js';
|
|
4
|
+
export { parseHostsYml, readPcGhLogin, type PcLoginOptions, type RunGh } from './pc-login.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** What hosts.yml says about github.com: the token it holds (null when gh keeps it elsewhere) and the active user. */
|
|
2
|
+
export interface HostsLogin {
|
|
3
|
+
readonly signedIn: boolean;
|
|
4
|
+
readonly token: string | null;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The github.com login in gh's hosts.yml. A small line reader for the two shapes gh writes (the token under
|
|
8
|
+
* `users.<name>.oauth_token`, or `oauth_token` directly under the host): bounded, with no YAML library and no
|
|
9
|
+
* regex that can run away. The active user's token is preferred.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseHostsYml(text: string): HostsLogin;
|
|
12
|
+
/** Where gh keeps its settings on this PC (GH_CONFIG_DIR, XDG_CONFIG_HOME, or %APPDATA%\GitHub CLI). */
|
|
13
|
+
export declare function ghConfigFolder(env: Readonly<Record<string, string | undefined>>): string | null;
|
|
14
|
+
/** Where the GitHub CLI installer puts gh.exe (absolute paths only: the program is never found by name). */
|
|
15
|
+
export declare function ghExeCandidates(env: Readonly<Record<string, string | undefined>>): string[];
|
|
16
|
+
/** Runs gh.exe `auth token` and returns what it printed (null on any failure). Replaceable in tests. */
|
|
17
|
+
export type RunGh = (exe: string, configDir: string | null, env: Readonly<Record<string, string | undefined>>) => string | null;
|
|
18
|
+
export declare const runGhAuthToken: RunGh;
|
|
19
|
+
export interface PcLoginOptions {
|
|
20
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
21
|
+
readonly run?: RunGh;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The token of this PC's gh login for github.com, or null when there is none (or it cannot be read): no login is
|
|
25
|
+
* normal and silent. The result passes the same limits as a token in the secrets file.
|
|
26
|
+
*/
|
|
27
|
+
export declare function readPcGhLogin(options?: PcLoginOptions): string | null;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// The GitHub CLI login on this PC (github.usePcLogin): the github.com token, read only for a launch's one-shot file.
|
|
2
|
+
// It comes from gh's hosts.yml (a login made with --insecure-storage) or, when gh keeps it in Windows Credential
|
|
3
|
+
// Manager, from this PC's own gh.exe (`gh auth token`, by absolute path). Nothing here prints or logs the token.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import { GithubTokenSchema } from '../../shared/github/index.js';
|
|
8
|
+
const HOST = 'github.com';
|
|
9
|
+
const MAX_HOSTS_BYTES = 256 * 1024;
|
|
10
|
+
const MAX_HOSTS_LINES = 2000;
|
|
11
|
+
const GH_TIMEOUT_MS = 10_000;
|
|
12
|
+
const indentOf = (line) => line.length - line.trimStart().length;
|
|
13
|
+
const valueOf = (line, key) => {
|
|
14
|
+
const t = line.trim();
|
|
15
|
+
if (!t.startsWith(key + ':'))
|
|
16
|
+
return null;
|
|
17
|
+
return (t
|
|
18
|
+
.slice(key.length + 1)
|
|
19
|
+
.replace(/\s+#.*$/, '')
|
|
20
|
+
.trim()
|
|
21
|
+
.replace(/^(['"])(.*)\1$/, '$2') || null);
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* The github.com login in gh's hosts.yml. A small line reader for the two shapes gh writes (the token under
|
|
25
|
+
* `users.<name>.oauth_token`, or `oauth_token` directly under the host): bounded, with no YAML library and no
|
|
26
|
+
* regex that can run away. The active user's token is preferred.
|
|
27
|
+
*/
|
|
28
|
+
export function parseHostsYml(text) {
|
|
29
|
+
const lines = text.split(/\r?\n/, MAX_HOSTS_LINES);
|
|
30
|
+
let inHost = false;
|
|
31
|
+
let user = null;
|
|
32
|
+
let direct = null;
|
|
33
|
+
let inUsers = false;
|
|
34
|
+
let current = null;
|
|
35
|
+
const byUser = new Map();
|
|
36
|
+
let signedIn = false;
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
if (!line.trim() || line.trim().startsWith('#'))
|
|
39
|
+
continue;
|
|
40
|
+
const indent = indentOf(line);
|
|
41
|
+
if (indent === 0) {
|
|
42
|
+
inHost = line.trim() === HOST + ':';
|
|
43
|
+
signedIn = signedIn || inHost;
|
|
44
|
+
inUsers = false;
|
|
45
|
+
current = null;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (!inHost)
|
|
49
|
+
continue;
|
|
50
|
+
const t = line.trim();
|
|
51
|
+
if (indent <= 4) {
|
|
52
|
+
inUsers = t === 'users:';
|
|
53
|
+
current = null;
|
|
54
|
+
user = valueOf(line, 'user') ?? user;
|
|
55
|
+
direct = valueOf(line, 'oauth_token') ?? direct;
|
|
56
|
+
}
|
|
57
|
+
else if (inUsers && t.endsWith(':') && !t.includes(' ')) {
|
|
58
|
+
current = t.slice(0, -1);
|
|
59
|
+
}
|
|
60
|
+
else if (inUsers && current) {
|
|
61
|
+
const token = valueOf(line, 'oauth_token');
|
|
62
|
+
if (token)
|
|
63
|
+
byUser.set(current, token);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// With an active user, another user's token is never a stand-in (theirs may be in Credential Manager).
|
|
67
|
+
const token = user ? (byUser.get(user) ?? direct) : (direct ?? [...byUser.values()][0] ?? null);
|
|
68
|
+
return { signedIn, token };
|
|
69
|
+
}
|
|
70
|
+
/** Where gh keeps its settings on this PC (GH_CONFIG_DIR, XDG_CONFIG_HOME, or %APPDATA%\GitHub CLI). */
|
|
71
|
+
export function ghConfigFolder(env) {
|
|
72
|
+
if (env.GH_CONFIG_DIR)
|
|
73
|
+
return env.GH_CONFIG_DIR;
|
|
74
|
+
if (env.XDG_CONFIG_HOME)
|
|
75
|
+
return path.join(env.XDG_CONFIG_HOME, 'gh');
|
|
76
|
+
return env.APPDATA ? path.join(env.APPDATA, 'GitHub CLI') : null;
|
|
77
|
+
}
|
|
78
|
+
/** Where the GitHub CLI installer puts gh.exe (absolute paths only: the program is never found by name). */
|
|
79
|
+
export function ghExeCandidates(env) {
|
|
80
|
+
const roots = [
|
|
81
|
+
env.ProgramFiles && path.join(env.ProgramFiles, 'GitHub CLI'),
|
|
82
|
+
env['ProgramFiles(x86)'] && path.join(env['ProgramFiles(x86)'], 'GitHub CLI'),
|
|
83
|
+
env.LOCALAPPDATA && path.join(env.LOCALAPPDATA, 'Programs', 'GitHub CLI'),
|
|
84
|
+
];
|
|
85
|
+
return roots.filter((r) => !!r).map((r) => path.join(r, 'gh.exe'));
|
|
86
|
+
}
|
|
87
|
+
const TOKEN_VARIABLES = /^(GH_TOKEN|GITHUB_TOKEN|GH_ENTERPRISE_TOKEN|GITHUB_ENTERPRISE_TOKEN)$/i;
|
|
88
|
+
export const runGhAuthToken = (exe, configDir, base) => {
|
|
89
|
+
const env = Object.fromEntries(Object.entries(base).filter((e) => e[1] !== undefined && !TOKEN_VARIABLES.test(e[0])));
|
|
90
|
+
if (configDir)
|
|
91
|
+
env.GH_CONFIG_DIR = configDir;
|
|
92
|
+
env.GH_PROMPT_DISABLED = '1';
|
|
93
|
+
const result = spawnSync(exe, ['auth', 'token', '--hostname', HOST], {
|
|
94
|
+
env,
|
|
95
|
+
encoding: 'utf8',
|
|
96
|
+
windowsHide: true,
|
|
97
|
+
timeout: GH_TIMEOUT_MS,
|
|
98
|
+
maxBuffer: 16 * 1024,
|
|
99
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
100
|
+
});
|
|
101
|
+
return result.status === 0 ? String(result.stdout ?? '').trim() : null;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* The token of this PC's gh login for github.com, or null when there is none (or it cannot be read): no login is
|
|
105
|
+
* normal and silent. The result passes the same limits as a token in the secrets file.
|
|
106
|
+
*/
|
|
107
|
+
export function readPcGhLogin(options = {}) {
|
|
108
|
+
const env = options.env ?? process.env;
|
|
109
|
+
const dir = ghConfigFolder(env);
|
|
110
|
+
let login = { signedIn: false, token: null };
|
|
111
|
+
if (dir) {
|
|
112
|
+
try {
|
|
113
|
+
const file = path.join(dir, 'hosts.yml');
|
|
114
|
+
if (fs.statSync(file).size <= MAX_HOSTS_BYTES)
|
|
115
|
+
login = parseHostsYml(fs.readFileSync(file, 'utf8'));
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// No hosts.yml: gh is not signed in on this PC.
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
let token = login.token;
|
|
122
|
+
if (!token && login.signedIn) {
|
|
123
|
+
const run = options.run ?? runGhAuthToken;
|
|
124
|
+
const exe = ghExeCandidates(env).find((c) => fs.existsSync(c));
|
|
125
|
+
token = exe ? (run(exe, dir, env)?.trim() ?? null) : null;
|
|
126
|
+
}
|
|
127
|
+
const checked = GithubTokenSchema.safeParse(token ?? '');
|
|
128
|
+
return checked.success ? checked.data : null;
|
|
129
|
+
}
|
|
@@ -12,10 +12,14 @@ export interface DiscoverOptions {
|
|
|
12
12
|
readonly sources?: readonly PcInstallSource[];
|
|
13
13
|
/** This launch sets the sandbox up for Unreal ("auto" maps this PC's engines and vault cache then). */
|
|
14
14
|
readonly unrealOn?: boolean;
|
|
15
|
+
/** This launch sets the sandbox up for Unity ("auto" maps this PC's editors and Asset Store downloads then). */
|
|
16
|
+
readonly unityOn?: boolean;
|
|
15
17
|
}
|
|
16
18
|
export interface DiscoveredInstalls {
|
|
17
19
|
readonly mappings: ResolvedMapping[];
|
|
18
20
|
readonly manifest: PcInstallManifest;
|
|
19
21
|
readonly notes: string[];
|
|
20
22
|
}
|
|
23
|
+
/** The toolchain an "auto" setting follows. */
|
|
24
|
+
export declare const AUTO_FOLLOWS: Readonly<Partial<Record<PcInstallSource['setting'], 'unreal' | 'unity'>>>;
|
|
21
25
|
export declare function discoverPcInstalls(s: PcInstalls, o: DiscoverOptions, pc?: PcEnv): DiscoveredInstalls;
|
|
@@ -5,6 +5,7 @@ import { pcEnv } from '../accounts/index.js';
|
|
|
5
5
|
import { epicInstalls } from './epic.js';
|
|
6
6
|
import { steamInstalls } from './steam.js';
|
|
7
7
|
import { unityInstalls } from './unity.js';
|
|
8
|
+
import { unityAssets } from './unity-assets.js';
|
|
8
9
|
import { unrealFolders, unrealInstalls } from './unreal.js';
|
|
9
10
|
import { unrealVault } from './unreal-vault.js';
|
|
10
11
|
/** At most this many installs are mapped (each one is a mapping in the .wsb). */
|
|
@@ -16,18 +17,29 @@ export const PC_INSTALL_SOURCES = [
|
|
|
16
17
|
unrealInstalls,
|
|
17
18
|
unrealVault,
|
|
18
19
|
unityInstalls,
|
|
20
|
+
unityAssets,
|
|
19
21
|
];
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
+
/** The toolchain an "auto" setting follows. */
|
|
23
|
+
export const AUTO_FOLLOWS = {
|
|
24
|
+
unrealEngines: 'unreal',
|
|
25
|
+
unrealAssets: 'unreal',
|
|
26
|
+
unityEditors: 'unity',
|
|
27
|
+
unityAssets: 'unity',
|
|
28
|
+
};
|
|
29
|
+
/** What a setting asks for: false (nothing), true (everything) or a list; "auto" follows its toolchain. */
|
|
30
|
+
function wantedOf(s, setting, on) {
|
|
22
31
|
const value = s[setting];
|
|
23
|
-
|
|
32
|
+
if (value !== 'auto')
|
|
33
|
+
return value;
|
|
34
|
+
const follows = AUTO_FOLLOWS[setting];
|
|
35
|
+
return follows ? on[follows] : false;
|
|
24
36
|
}
|
|
25
37
|
export function discoverPcInstalls(s, o, pc = pcEnv()) {
|
|
26
38
|
const mappings = [];
|
|
27
39
|
const items = [];
|
|
28
40
|
const notes = [];
|
|
29
41
|
for (const source of o.sources ?? PC_INSTALL_SOURCES) {
|
|
30
|
-
const wanted = wantedOf(s, source.setting, o.unrealOn === true);
|
|
42
|
+
const wanted = wantedOf(s, source.setting, { unreal: o.unrealOn === true, unity: o.unityOn === true });
|
|
31
43
|
if (wanted === false || (Array.isArray(wanted) && !wanted.length))
|
|
32
44
|
continue;
|
|
33
45
|
let found = [];
|
|
@@ -3,7 +3,7 @@ import type { PcEnv } from '../accounts/index.js';
|
|
|
3
3
|
export interface PcInstallSource {
|
|
4
4
|
readonly kind: PcInstall['kind'];
|
|
5
5
|
/** The settings key that turns it on (pcInstalls.<setting>). */
|
|
6
|
-
readonly setting: 'steamGames' | 'epicGames' | 'unrealEngines' | 'unrealEngineFolders' | 'unrealAssets' | 'unityEditors';
|
|
6
|
+
readonly setting: 'steamGames' | 'epicGames' | 'unrealEngines' | 'unrealEngineFolders' | 'unrealAssets' | 'unityEditors' | 'unityAssets';
|
|
7
7
|
/** What is installed: everything (true) or the ones listed. Bounded reads only (registry, manifests). */
|
|
8
8
|
find(wanted: true | readonly (string | number)[], pc: PcEnv): PcInstall[];
|
|
9
9
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PcInstall } from '../../shared/paths/index.js';
|
|
2
|
+
import type { PcInstallSource } from './source.js';
|
|
3
|
+
/** The Asset Store folders as installs to map. */
|
|
4
|
+
export declare function unityAssetInstalls(dirs: readonly string[]): PcInstall[];
|
|
5
|
+
export declare const unityAssets: PcInstallSource;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { assetCacheEnv, unityAssetCacheDirs, unityPcFiles } from './unity.js';
|
|
2
|
+
/** The Asset Store folders as installs to map. */
|
|
3
|
+
export function unityAssetInstalls(dirs) {
|
|
4
|
+
return dirs.map((dir, i) => ({
|
|
5
|
+
kind: 'unity-assets',
|
|
6
|
+
id: i ? 'AssetStore' + (i + 1) : 'AssetStore',
|
|
7
|
+
name: 'Unity Asset Store downloads' + (i ? ' ' + (i + 1) : ''),
|
|
8
|
+
host: dir,
|
|
9
|
+
guest: '',
|
|
10
|
+
}));
|
|
11
|
+
}
|
|
12
|
+
export const unityAssets = {
|
|
13
|
+
kind: 'unity-assets',
|
|
14
|
+
setting: 'unityAssets',
|
|
15
|
+
find(_wanted, pc) {
|
|
16
|
+
return unityAssetInstalls(unityAssetCacheDirs(unityPcFiles(pc), assetCacheEnv(pc)));
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -1,2 +1,22 @@
|
|
|
1
|
+
import type { PcInstall } from '../../shared/paths/index.js';
|
|
2
|
+
import type { PcEnv } from '../accounts/index.js';
|
|
1
3
|
import type { PcInstallSource } from './source.js';
|
|
4
|
+
/** At most this many editors of this PC are mapped. */
|
|
5
|
+
export declare const MAX_PC_EDITORS = 12;
|
|
6
|
+
/** The files and folders of this PC the search reads (the self-test passes its own). */
|
|
7
|
+
export interface UnityPcFiles {
|
|
8
|
+
read(file: string, max: number): Buffer | null;
|
|
9
|
+
isDir(dir: string): boolean;
|
|
10
|
+
list(dir: string): string[];
|
|
11
|
+
readonly appData: string;
|
|
12
|
+
readonly programFiles: string;
|
|
13
|
+
}
|
|
14
|
+
/** The editors of this PC: all of them (true) or the listed versions. Each version once, each folder once. */
|
|
15
|
+
export declare function findUnityEditorsOnPc(files: UnityPcFiles, wanted: true | readonly string[]): PcInstall[];
|
|
16
|
+
/** The Asset Store-5.x folders of this PC (the environment's cache root first, then %APPDATA%\Unity), to map. */
|
|
17
|
+
export declare function unityAssetCacheDirs(files: UnityPcFiles, envValue: string | undefined): string[];
|
|
18
|
+
/** This PC's disk for the search. */
|
|
19
|
+
export declare function unityPcFiles(pc: PcEnv): UnityPcFiles;
|
|
20
|
+
/** The cache root the environment names: this terminal's variable, else the saved user one. */
|
|
21
|
+
export declare function assetCacheEnv(pc: PcEnv): string | undefined;
|
|
2
22
|
export declare const unityInstalls: PcInstallSource;
|