run-spaceapp 0.1.10 → 0.1.12
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 +18 -19
- package/package.json +4 -3
- package/src/cli.mjs +11 -5
- package/src/index.mjs +14 -6
- package/src/prerequisites.mjs +179 -10
package/README.md
CHANGED
|
@@ -2,22 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Cross-platform Docker launcher for SpaceApp.
|
|
4
4
|
|
|
5
|
-
Linux:
|
|
5
|
+
Run the same command in Linux, macOS, or Windows 11 Command Prompt:
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
8
|
+
npx --yes run-spaceapp install
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
To keep a global `spaceapp` command for later operations, install it
|
|
12
|
+
separately:
|
|
12
13
|
|
|
13
14
|
```bash
|
|
14
|
-
npm install -g run-spaceapp
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Windows 11 PowerShell:
|
|
18
|
-
|
|
19
|
-
```powershell
|
|
20
|
-
npm install -g run-spaceapp; if ($LASTEXITCODE -eq 0) { spaceapp install }
|
|
15
|
+
npm install -g run-spaceapp
|
|
21
16
|
```
|
|
22
17
|
|
|
23
18
|
The launcher requires Node.js 20.11 or newer, 4 CPUs, 8 GB RAM, and 15 GiB free
|
|
@@ -28,15 +23,19 @@ images. Windows prefers Windows Package Manager's hash-pinned Docker Desktop
|
|
|
28
23
|
manifest and retains a signed direct-download fallback. Docker Desktop license
|
|
29
24
|
acceptance and Linux `docker` group membership require confirmation inside the
|
|
30
25
|
same command. On Windows, approve any UAC prompt required by WSL2 or Docker
|
|
31
|
-
Desktop.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
26
|
+
Desktop. Use the **Command Prompt** profile because a restricted PowerShell
|
|
27
|
+
policy can block npm's `npx.ps1` before SpaceApp starts. On Docker Desktop's
|
|
28
|
+
first launch, select **Skip** in the top-right of the **Welcome to Docker**
|
|
29
|
+
window (or sign in), accept any remaining Docker prompt, and keep the terminal
|
|
30
|
+
open. SpaceApp waits for up to ten minutes and continues automatically when
|
|
31
|
+
Docker is ready. If WSL2 requires one restart, SpaceApp registers a one-time
|
|
32
|
+
resume and asks before scheduling it. Sign back in after Windows restarts; the
|
|
33
|
+
same install continues automatically in Command Prompt without entering a
|
|
34
|
+
second command.
|
|
35
|
+
|
|
36
|
+
The launcher defaults to the `light` profile on every system. Select
|
|
37
|
+
`--profile standard` explicitly when managed Chromium is required and the host
|
|
38
|
+
has the recommended resources.
|
|
40
39
|
|
|
41
40
|
Light mode retains every bundled CLI and the core data/workflow services while
|
|
42
41
|
omitting managed Chromium. Resource limits are maximums, not immediate RAM or
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "run-spaceapp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "Cross-platform Docker launcher for the SpaceApp self-hosted agent workspace",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"spaceapp": "bin/spaceapp.mjs"
|
|
8
|
+
"spaceapp": "bin/spaceapp.mjs",
|
|
9
|
+
"run-spaceapp": "bin/spaceapp.mjs"
|
|
9
10
|
},
|
|
10
11
|
"exports": {
|
|
11
12
|
".": "./src/index.mjs"
|
|
@@ -45,5 +46,5 @@
|
|
|
45
46
|
"access": "public",
|
|
46
47
|
"provenance": true
|
|
47
48
|
},
|
|
48
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "55dff6314db5ded932361275b85a5a2008920128"
|
|
49
50
|
}
|
package/src/cli.mjs
CHANGED
|
@@ -33,11 +33,11 @@ const SETUP_STATUS_TIMEOUT_MS = 10_000;
|
|
|
33
33
|
const trustedCommands = new Set([
|
|
34
34
|
"codesign",
|
|
35
35
|
"docker",
|
|
36
|
-
"explorer.exe",
|
|
37
36
|
"hdiutil",
|
|
38
37
|
"open",
|
|
39
38
|
"powershell.exe",
|
|
40
39
|
"sg",
|
|
40
|
+
"shutdown.exe",
|
|
41
41
|
"spctl",
|
|
42
42
|
"sudo",
|
|
43
43
|
"winget.exe",
|
|
@@ -45,7 +45,9 @@ const trustedCommands = new Set([
|
|
|
45
45
|
"xdg-open"
|
|
46
46
|
]);
|
|
47
47
|
const trustedEnvironmentNames = new Set([
|
|
48
|
-
"SPACEAPP_DOCKER_INSTALLER_PATH"
|
|
48
|
+
"SPACEAPP_DOCKER_INSTALLER_PATH",
|
|
49
|
+
"SPACEAPP_OPEN_URL",
|
|
50
|
+
"SPACEAPP_RESUME_SCRIPT_PATH"
|
|
49
51
|
]);
|
|
50
52
|
|
|
51
53
|
export async function run(argv, {
|
|
@@ -295,7 +297,7 @@ async function installCommand(args, {
|
|
|
295
297
|
stdout,
|
|
296
298
|
stderr,
|
|
297
299
|
execute,
|
|
298
|
-
installArgs: { requestedProfile, noOpen }
|
|
300
|
+
installArgs: { root, requestedProfile, noOpen }
|
|
299
301
|
});
|
|
300
302
|
if (prerequisiteResult.reexecuted || prerequisiteResult.code !== 0) {
|
|
301
303
|
if (prerequisiteResult.code !== 0) {
|
|
@@ -810,12 +812,12 @@ function spawnTrustedCommand(spec, options) {
|
|
|
810
812
|
switch (spec.command) {
|
|
811
813
|
case "codesign": return spawn("codesign", args, options);
|
|
812
814
|
case "docker": return spawn("docker", args, options);
|
|
813
|
-
case "explorer.exe": return spawn("explorer.exe", args, options);
|
|
814
815
|
case "hdiutil": return spawn("hdiutil", args, options);
|
|
815
816
|
case "open": return spawn("open", args, options);
|
|
816
817
|
case "powershell.exe":
|
|
817
818
|
return spawn("powershell.exe", windowsPowerShellArgs(spec.operation), options);
|
|
818
819
|
case "sg": return spawn("sg", args, options);
|
|
820
|
+
case "shutdown.exe": return spawn("shutdown.exe", args, options);
|
|
819
821
|
case "spctl": return spawn("spctl", args, options);
|
|
820
822
|
case "sudo": return spawn("sudo", args, options);
|
|
821
823
|
case "winget.exe": return spawn("winget.exe", args, options);
|
|
@@ -844,7 +846,11 @@ function openBrowser(url, platform, execute, io) {
|
|
|
844
846
|
return execute({ command: "open", args: [url] }, io);
|
|
845
847
|
}
|
|
846
848
|
if (platform === "win32") {
|
|
847
|
-
return execute({
|
|
849
|
+
return execute({
|
|
850
|
+
command: "powershell.exe",
|
|
851
|
+
operation: "open-spaceapp-browser",
|
|
852
|
+
env: { SPACEAPP_OPEN_URL: url }
|
|
853
|
+
}, io);
|
|
848
854
|
}
|
|
849
855
|
return execute({ command: "xdg-open", args: [url] }, io);
|
|
850
856
|
}
|
package/src/index.mjs
CHANGED
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
} from "./string-utils.mjs";
|
|
21
21
|
|
|
22
22
|
const CONFIG_SCHEMA_VERSION = 2;
|
|
23
|
-
const AUTO_LIGHT_MEMORY_THRESHOLD_BYTES = 12 * 1024 ** 3;
|
|
24
23
|
const MIN_INSTALL_CPU_COUNT = 4;
|
|
25
24
|
const MIN_INSTALL_MEMORY_BYTES = 8_000_000_000;
|
|
26
25
|
const MIN_INSTALL_MEMORY_LABEL = "8 GB";
|
|
@@ -100,7 +99,7 @@ export function resolveInstallProfile(requestedProfile, totalMemoryBytes) {
|
|
|
100
99
|
if (!Number.isFinite(totalMemoryBytes) || totalMemoryBytes <= 0) {
|
|
101
100
|
throw new Error("Total system memory must be available for automatic profile selection.");
|
|
102
101
|
}
|
|
103
|
-
return
|
|
102
|
+
return "light";
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
export async function inspectSystemResources(root) {
|
|
@@ -140,7 +139,7 @@ export function installResourceChecks(resources) {
|
|
|
140
139
|
];
|
|
141
140
|
}
|
|
142
141
|
|
|
143
|
-
export function createDefaultConfig({ version, profile = "
|
|
142
|
+
export function createDefaultConfig({ version, profile = "light" }) {
|
|
144
143
|
assertVersion(version);
|
|
145
144
|
if (profile !== "light" && profile !== "standard") {
|
|
146
145
|
throw new Error("Default config requires a resolved light or standard profile.");
|
|
@@ -484,6 +483,9 @@ export async function initializeInstallation(root, {
|
|
|
484
483
|
mkdir(join(root, "backups"), { recursive: true, mode: 0o700 }),
|
|
485
484
|
mkdir(join(root, "secrets", "providers"), { recursive: true, mode: 0o700 })
|
|
486
485
|
]);
|
|
486
|
+
const resolvedProfile = profile === undefined
|
|
487
|
+
? undefined
|
|
488
|
+
: resolveInstallProfile(profile, totalmem());
|
|
487
489
|
let config;
|
|
488
490
|
try {
|
|
489
491
|
config = await loadConfig(root);
|
|
@@ -491,10 +493,16 @@ export async function initializeInstallation(root, {
|
|
|
491
493
|
if (error?.code !== "ENOENT") {
|
|
492
494
|
throw error;
|
|
493
495
|
}
|
|
494
|
-
config = createDefaultConfig({ version, profile:
|
|
496
|
+
config = createDefaultConfig({ version, profile: resolvedProfile ?? "light" });
|
|
495
497
|
}
|
|
496
|
-
if (
|
|
497
|
-
|
|
498
|
+
if (config.version !== version) {
|
|
499
|
+
config = {
|
|
500
|
+
...config,
|
|
501
|
+
version,
|
|
502
|
+
previousVersion: config.version
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
if (resolvedProfile !== undefined) {
|
|
498
506
|
config = { ...config, profile: resolvedProfile };
|
|
499
507
|
}
|
|
500
508
|
await saveConfig(root, config);
|
package/src/prerequisites.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createWriteStream } from "node:fs";
|
|
2
|
-
import { access, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { access, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join, posix, win32 } from "node:path";
|
|
5
5
|
import process from "node:process";
|
|
@@ -43,6 +43,20 @@ export function windowsPowerShellArgs(operation) {
|
|
|
43
43
|
"; exit $process.ExitCode",
|
|
44
44
|
"} catch { exit 1 }"
|
|
45
45
|
].join(" "),
|
|
46
|
+
"windows-restart-pending": [
|
|
47
|
+
"$pending = (Test-Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootPending') -or (Test-Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update\\RebootRequired')",
|
|
48
|
+
"if ($pending) { exit 0 }",
|
|
49
|
+
"exit 1"
|
|
50
|
+
].join("; "),
|
|
51
|
+
"register-spaceapp-resume": [
|
|
52
|
+
"$ErrorActionPreference = 'Stop'",
|
|
53
|
+
"$path = $env:SPACEAPP_RESUME_SCRIPT_PATH",
|
|
54
|
+
"if ([string]::IsNullOrWhiteSpace($path) -or -not (Test-Path -LiteralPath $path -PathType Leaf)) { exit 1 }",
|
|
55
|
+
"$runOnce = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce'",
|
|
56
|
+
"New-Item -Path $runOnce -Force | Out-Null",
|
|
57
|
+
"$command = 'cmd.exe /d /k call \"' + $path + '\"'",
|
|
58
|
+
"New-ItemProperty -Path $runOnce -Name 'SpaceAppResumeInstall' -Value $command -PropertyType String -Force | Out-Null"
|
|
59
|
+
].join("; "),
|
|
46
60
|
"verify-docker-installer": [
|
|
47
61
|
"$path = $env:SPACEAPP_DOCKER_INSTALLER_PATH",
|
|
48
62
|
"if ([string]::IsNullOrWhiteSpace($path)) { exit 1 }",
|
|
@@ -59,6 +73,17 @@ export function windowsPowerShellArgs(operation) {
|
|
|
59
73
|
"$path = $env:SPACEAPP_DOCKER_DESKTOP_PATH",
|
|
60
74
|
"if ([string]::IsNullOrWhiteSpace($path)) { exit 1 }",
|
|
61
75
|
"Start-Process -FilePath $path"
|
|
76
|
+
].join("; "),
|
|
77
|
+
"open-spaceapp-browser": [
|
|
78
|
+
"$ErrorActionPreference = 'Stop'",
|
|
79
|
+
"$url = $env:SPACEAPP_OPEN_URL",
|
|
80
|
+
"$uri = $null",
|
|
81
|
+
"if ([string]::IsNullOrWhiteSpace($url) -or -not [Uri]::TryCreate($url, [UriKind]::Absolute, [ref]$uri)) { exit 1 }",
|
|
82
|
+
"if ($uri.Scheme -ne 'http' -or @('127.0.0.1','0.0.0.0') -notcontains $uri.Host -or $uri.Port -lt 1024 -or $uri.Port -gt 65535) { exit 1 }",
|
|
83
|
+
"$roots = @([Environment]::GetEnvironmentVariable('ProgramFiles(x86)'), [Environment]::GetEnvironmentVariable('ProgramFiles'), [Environment]::GetEnvironmentVariable('LOCALAPPDATA')) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }",
|
|
84
|
+
"$edge = $roots | ForEach-Object { Join-Path $_ 'Microsoft\\Edge\\Application\\msedge.exe' } | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } | Select-Object -First 1",
|
|
85
|
+
"if ($edge) { Start-Process -FilePath $edge -ArgumentList @('--no-first-run','--no-default-browser-check',$url); exit 0 }",
|
|
86
|
+
"Start-Process -FilePath 'explorer.exe' -ArgumentList @($url)"
|
|
62
87
|
].join("; ")
|
|
63
88
|
};
|
|
64
89
|
if (!Object.hasOwn(scripts, operation)) {
|
|
@@ -101,7 +126,9 @@ export async function ensureDockerAvailable({
|
|
|
101
126
|
pathExists = fileExists,
|
|
102
127
|
sleep = wait,
|
|
103
128
|
osRelease,
|
|
104
|
-
installArgs
|
|
129
|
+
installArgs,
|
|
130
|
+
scheduleResume = scheduleWindowsInstallResume,
|
|
131
|
+
confirmRestart = confirmWindowsRestart
|
|
105
132
|
}) {
|
|
106
133
|
if (await dockerIsReady(execute)) {
|
|
107
134
|
return { code: 0, reexecuted: false };
|
|
@@ -117,7 +144,10 @@ export async function ensureDockerAvailable({
|
|
|
117
144
|
launch,
|
|
118
145
|
download,
|
|
119
146
|
pathExists,
|
|
120
|
-
sleep
|
|
147
|
+
sleep,
|
|
148
|
+
installArgs,
|
|
149
|
+
scheduleResume,
|
|
150
|
+
confirmRestart
|
|
121
151
|
});
|
|
122
152
|
}
|
|
123
153
|
if (platform === "darwin") {
|
|
@@ -158,7 +188,10 @@ async function ensureWindowsDocker({
|
|
|
158
188
|
launch,
|
|
159
189
|
download,
|
|
160
190
|
pathExists,
|
|
161
|
-
sleep
|
|
191
|
+
sleep,
|
|
192
|
+
installArgs,
|
|
193
|
+
scheduleResume,
|
|
194
|
+
confirmRestart
|
|
162
195
|
}) {
|
|
163
196
|
const paths = windowsDockerPaths(env);
|
|
164
197
|
let application = await firstExisting(paths.applications, pathExists);
|
|
@@ -172,6 +205,7 @@ async function ensureWindowsDocker({
|
|
|
172
205
|
}
|
|
173
206
|
}
|
|
174
207
|
|
|
208
|
+
let wslInstalledNow = false;
|
|
175
209
|
const wslCode = await execute(
|
|
176
210
|
{ command: "wsl.exe", args: ["--version"] },
|
|
177
211
|
{ stdin, stdout: null, stderr: null }
|
|
@@ -191,16 +225,64 @@ async function ensureWindowsDocker({
|
|
|
191
225
|
);
|
|
192
226
|
return { code: installWslCode, reexecuted: false };
|
|
193
227
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
228
|
+
wslInstalledNow = true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const restartPendingCode = await execute(
|
|
232
|
+
{ command: "powershell.exe", operation: "windows-restart-pending" },
|
|
233
|
+
{ stdin, stdout: null, stderr: null }
|
|
234
|
+
);
|
|
235
|
+
if (wslInstalledNow || restartPendingCode === 0) {
|
|
236
|
+
const scheduleCode = await scheduleResume({
|
|
237
|
+
...installArgs,
|
|
238
|
+
execute,
|
|
239
|
+
stdin,
|
|
240
|
+
stdout,
|
|
241
|
+
stderr
|
|
242
|
+
});
|
|
243
|
+
if (scheduleCode !== 0) {
|
|
244
|
+
stderr.write(
|
|
245
|
+
"Windows must restart to finish WSL2, but SpaceApp could not schedule automatic continuation.\n" +
|
|
246
|
+
"Restart Windows, then run the same SpaceApp command again.\n"
|
|
247
|
+
);
|
|
248
|
+
return { code: scheduleCode, reexecuted: false };
|
|
249
|
+
}
|
|
250
|
+
stdout.write(
|
|
251
|
+
"Windows must restart to finish enabling WSL2.\n" +
|
|
252
|
+
"SpaceApp is scheduled to resume automatically after you sign in.\n"
|
|
253
|
+
);
|
|
254
|
+
const restartNow = await confirmRestart({
|
|
255
|
+
stdin,
|
|
256
|
+
stdout,
|
|
257
|
+
question: "Restart Windows in 15 seconds and continue SpaceApp after sign-in? [y/N] "
|
|
258
|
+
});
|
|
259
|
+
if (!restartNow) {
|
|
260
|
+
stdout.write(
|
|
261
|
+
"Restart Windows when ready. SpaceApp will resume automatically the next time you sign in.\n"
|
|
262
|
+
);
|
|
263
|
+
return { code: 0, reexecuted: true };
|
|
264
|
+
}
|
|
265
|
+
const restartCode = await execute(
|
|
266
|
+
{
|
|
267
|
+
command: "shutdown.exe",
|
|
268
|
+
args: [
|
|
269
|
+
"/r",
|
|
270
|
+
"/t", "15",
|
|
271
|
+
"/c", "SpaceApp will continue automatically after you sign in."
|
|
272
|
+
]
|
|
273
|
+
},
|
|
274
|
+
{ stdin, stdout, stderr }
|
|
197
275
|
);
|
|
198
|
-
if (
|
|
276
|
+
if (restartCode !== 0) {
|
|
199
277
|
stderr.write(
|
|
200
|
-
"Windows
|
|
278
|
+
"Windows restart could not be scheduled. Restart Windows manually; SpaceApp will resume after sign-in.\n"
|
|
201
279
|
);
|
|
202
|
-
return { code:
|
|
280
|
+
return { code: restartCode, reexecuted: false };
|
|
203
281
|
}
|
|
282
|
+
stdout.write(
|
|
283
|
+
"Windows will restart in 15 seconds. Save your work; run \"shutdown /a\" to cancel the countdown.\n"
|
|
284
|
+
);
|
|
285
|
+
return { code: 0, reexecuted: true };
|
|
204
286
|
}
|
|
205
287
|
|
|
206
288
|
if (!application) {
|
|
@@ -510,6 +592,10 @@ async function confirmDesktopInstall({ platformName, stdin, stdout }) {
|
|
|
510
592
|
});
|
|
511
593
|
}
|
|
512
594
|
|
|
595
|
+
async function confirmWindowsRestart({ stdin, stdout, question }) {
|
|
596
|
+
return confirmQuestion({ stdin, stdout, question });
|
|
597
|
+
}
|
|
598
|
+
|
|
513
599
|
async function confirmQuestion({ stdin, stdout, question }) {
|
|
514
600
|
const readline = createInterface({ input: stdin, output: stdout, terminal: Boolean(stdin.isTTY) });
|
|
515
601
|
try {
|
|
@@ -716,10 +802,93 @@ function linuxReentryCommand({ requestedProfile = "auto", noOpen = false } = {})
|
|
|
716
802
|
].map(shellQuote).join(" ");
|
|
717
803
|
}
|
|
718
804
|
|
|
805
|
+
export function windowsResumeScript({
|
|
806
|
+
executable = process.execPath,
|
|
807
|
+
entrypoint = fileURLToPath(new URL("../bin/spaceapp.mjs", import.meta.url)),
|
|
808
|
+
requestedProfile = "auto",
|
|
809
|
+
noOpen = false
|
|
810
|
+
} = {}) {
|
|
811
|
+
if (!["auto", "light", "standard"].includes(requestedProfile)) {
|
|
812
|
+
throw new Error("Invalid SpaceApp profile for Windows resume.");
|
|
813
|
+
}
|
|
814
|
+
if (typeof noOpen !== "boolean") {
|
|
815
|
+
throw new Error("Invalid SpaceApp browser option for Windows resume.");
|
|
816
|
+
}
|
|
817
|
+
for (const [name, value] of [["executable", executable], ["entrypoint", entrypoint]]) {
|
|
818
|
+
if (
|
|
819
|
+
typeof value !== "string" ||
|
|
820
|
+
!win32.isAbsolute(value) ||
|
|
821
|
+
/["\r\n%]/.test(value)
|
|
822
|
+
) {
|
|
823
|
+
throw new Error(`Invalid SpaceApp Windows resume ${name}.`);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
const command = [
|
|
827
|
+
windowsBatchQuote(executable),
|
|
828
|
+
windowsBatchQuote(entrypoint),
|
|
829
|
+
"install",
|
|
830
|
+
"--profile",
|
|
831
|
+
requestedProfile,
|
|
832
|
+
...(noOpen ? ["--no-open"] : [])
|
|
833
|
+
].join(" ");
|
|
834
|
+
return [
|
|
835
|
+
"@echo off",
|
|
836
|
+
"setlocal",
|
|
837
|
+
command,
|
|
838
|
+
'set "SPACEAPP_RESUME_EXIT=%ERRORLEVEL%"',
|
|
839
|
+
'if "%SPACEAPP_RESUME_EXIT%"=="0" del /f /q "%~f0"',
|
|
840
|
+
'if not "%SPACEAPP_RESUME_EXIT%"=="0" echo SpaceApp setup needs attention. Review the error above, then run this file again.',
|
|
841
|
+
"exit /b %SPACEAPP_RESUME_EXIT%",
|
|
842
|
+
""
|
|
843
|
+
].join("\r\n");
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
async function scheduleWindowsInstallResume({
|
|
847
|
+
root,
|
|
848
|
+
requestedProfile = "auto",
|
|
849
|
+
noOpen = false,
|
|
850
|
+
execute,
|
|
851
|
+
stdin,
|
|
852
|
+
stdout,
|
|
853
|
+
stderr
|
|
854
|
+
} = {}) {
|
|
855
|
+
if (
|
|
856
|
+
typeof root !== "string" ||
|
|
857
|
+
!win32.isAbsolute(root) ||
|
|
858
|
+
/["\r\n%]/.test(root)
|
|
859
|
+
) {
|
|
860
|
+
stderr?.write("SpaceApp could not determine a safe Windows resume path.\n");
|
|
861
|
+
return 1;
|
|
862
|
+
}
|
|
863
|
+
const resumePath = win32.join(root, "resume-install.cmd");
|
|
864
|
+
await mkdir(root, { recursive: true, mode: 0o700 });
|
|
865
|
+
await writeFile(
|
|
866
|
+
resumePath,
|
|
867
|
+
windowsResumeScript({ requestedProfile, noOpen }),
|
|
868
|
+
{ encoding: "utf8", mode: 0o600 }
|
|
869
|
+
);
|
|
870
|
+
const code = await execute(
|
|
871
|
+
{
|
|
872
|
+
command: "powershell.exe",
|
|
873
|
+
operation: "register-spaceapp-resume",
|
|
874
|
+
env: { SPACEAPP_RESUME_SCRIPT_PATH: resumePath }
|
|
875
|
+
},
|
|
876
|
+
{ stdin, stdout: null, stderr }
|
|
877
|
+
);
|
|
878
|
+
if (code !== 0) {
|
|
879
|
+
await rm(resumePath, { force: true });
|
|
880
|
+
}
|
|
881
|
+
return code;
|
|
882
|
+
}
|
|
883
|
+
|
|
719
884
|
function shellQuote(value) {
|
|
720
885
|
return `'${String(value).replaceAll("'", "'\\''")}'`;
|
|
721
886
|
}
|
|
722
887
|
|
|
888
|
+
function windowsBatchQuote(value) {
|
|
889
|
+
return `"${String(value)}"`;
|
|
890
|
+
}
|
|
891
|
+
|
|
723
892
|
async function waitForDocker({ execute, sleep, attempts = 90 }) {
|
|
724
893
|
for (let attempt = 0; attempt <= attempts; attempt += 1) {
|
|
725
894
|
if (await dockerIsReady(execute)) {
|