run-spaceapp 0.1.26 → 0.1.28
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/package.json +4 -4
- package/src/cli.mjs +95 -5
- package/src/index.mjs +15 -4
- package/templates/compose.yml +1 -0
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "run-spaceapp",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"spaceappRuntimeVersion": "0.1.
|
|
5
|
-
"spaceappHostRootRuntimeCompatible":
|
|
3
|
+
"version": "0.1.28",
|
|
4
|
+
"spaceappRuntimeVersion": "0.1.27",
|
|
5
|
+
"spaceappHostRootRuntimeCompatible": false,
|
|
6
6
|
"description": "Cross-platform Docker launcher for the SpaceApp self-hosted agent workspace",
|
|
7
7
|
"license": "Apache-2.0",
|
|
8
8
|
"type": "module",
|
|
@@ -52,5 +52,5 @@
|
|
|
52
52
|
"access": "public",
|
|
53
53
|
"provenance": true
|
|
54
54
|
},
|
|
55
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "7bd6c6cb1f93e30fe9a38ddf90a103c22536b5d4"
|
|
56
56
|
}
|
package/src/cli.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
3
|
import { createReadStream, createWriteStream } from "node:fs";
|
|
4
|
+
import { createInterface } from "node:readline";
|
|
4
5
|
import {
|
|
5
6
|
copyFile,
|
|
6
7
|
mkdir,
|
|
@@ -82,6 +83,17 @@ const trustedEnvironmentNames = new Set([
|
|
|
82
83
|
"SPACEAPP_RESUME_SCRIPT_PATH"
|
|
83
84
|
]);
|
|
84
85
|
|
|
86
|
+
export function writeWindowsCredentialHint(platform, stderr) {
|
|
87
|
+
if (platform !== "win32") {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
stderr.write(
|
|
91
|
+
"If the failure above mentions Docker credentials (\"A specified logon session does not exist\"), " +
|
|
92
|
+
"Docker Desktop's credential helper requires an interactive Windows session. " +
|
|
93
|
+
`Run "${UNIVERSAL_COMMAND} install" from your desktop terminal (not SSH/CI), or open Docker Desktop and sign in once, then retry.\n`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
85
97
|
export async function withHeadlessDockerConfig(platform, spec, run) {
|
|
86
98
|
if (platform !== "win32") {
|
|
87
99
|
return run(spec);
|
|
@@ -1078,6 +1090,7 @@ async function repairRuntime({ root, config, platform, stdin, stdout, stderr, ex
|
|
|
1078
1090
|
(pullSpec) => execute(pullSpec, { stdin, stdout, stderr })
|
|
1079
1091
|
);
|
|
1080
1092
|
if (pullCode !== 0) {
|
|
1093
|
+
writeWindowsCredentialHint(platform, stderr);
|
|
1081
1094
|
return pullCode;
|
|
1082
1095
|
}
|
|
1083
1096
|
const upCode = await execute(
|
|
@@ -1127,6 +1140,7 @@ async function performUpdate({
|
|
|
1127
1140
|
(pullSpec) => execute(pullSpec, { stdin, stdout, stderr })
|
|
1128
1141
|
);
|
|
1129
1142
|
if (pullCode !== 0) {
|
|
1143
|
+
writeWindowsCredentialHint(platform, stderr);
|
|
1130
1144
|
throw new Error(`Image pull failed with Docker exit ${pullCode}.`);
|
|
1131
1145
|
}
|
|
1132
1146
|
const upCode = await execute(
|
|
@@ -1848,12 +1862,88 @@ async function executeWithDockerDiagnostics(execute, spec, io, { platform, stder
|
|
|
1848
1862
|
export async function readSecret(stdin, stdout, prompt, { mask = true } = {}) {
|
|
1849
1863
|
stdout.write(prompt);
|
|
1850
1864
|
if (!stdin.isTTY || typeof stdin.setRawMode !== "function") {
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1865
|
+
// Non-TTY stdin (e.g. piped input on Windows through npx.cmd). Read only
|
|
1866
|
+
// up to the first newline so an interactive console that reports non-TTY
|
|
1867
|
+
// stdin still works: previously this drained the stream to EOF, so typing
|
|
1868
|
+
// "y" + Enter looked frozen and a second keystroke produced "y\ny" and
|
|
1869
|
+
// "Please answer y or n." loops. When the stream ends before a newline
|
|
1870
|
+
// (fully piped answers), everything read is returned as-is so "y\nn\n"
|
|
1871
|
+
// piped input still yields successive valid answers. Event listeners are
|
|
1872
|
+
// used instead of the Readable async iterator because an abandoned
|
|
1873
|
+
// iterator can destroy the stream, breaking the next prompt.
|
|
1874
|
+
const value = await new Promise((resolve) => {
|
|
1875
|
+
let answer = "";
|
|
1876
|
+
const finish = (result) => {
|
|
1877
|
+
cleanup();
|
|
1878
|
+
resolve(result);
|
|
1879
|
+
};
|
|
1880
|
+
// Paused-mode reads: consume exactly one line from the buffer, leaving
|
|
1881
|
+
// the rest (and the stream itself) intact for the next prompt.
|
|
1882
|
+
const onReadable = () => {
|
|
1883
|
+
let chunk;
|
|
1884
|
+
while ((chunk = stdin.read()) !== null) {
|
|
1885
|
+
for (const character of String(chunk)) {
|
|
1886
|
+
if (character === "\r" || character === "\n") {
|
|
1887
|
+
finish(answer);
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
answer += character;
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
};
|
|
1894
|
+
const onEnd = () => finish(answer);
|
|
1895
|
+
const onClose = () => finish(answer);
|
|
1896
|
+
const cleanup = () => {
|
|
1897
|
+
stdin.off("readable", onReadable);
|
|
1898
|
+
stdin.off("end", onEnd);
|
|
1899
|
+
stdin.off("close", onClose);
|
|
1900
|
+
};
|
|
1901
|
+
if (typeof stdin.read === "function" && typeof stdin.on === "function") {
|
|
1902
|
+
stdin.on("readable", onReadable);
|
|
1903
|
+
stdin.once("end", onEnd);
|
|
1904
|
+
stdin.once("close", onClose);
|
|
1905
|
+
onReadable();
|
|
1906
|
+
} else {
|
|
1907
|
+
// Fallback for exotic streams without readable-mode support.
|
|
1908
|
+
const iterator = stdin[Symbol.asyncIterator]();
|
|
1909
|
+
(async () => {
|
|
1910
|
+
for (;;) {
|
|
1911
|
+
const step = await iterator.next();
|
|
1912
|
+
if (step.done) {
|
|
1913
|
+
finish(answer);
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
for (const character of String(step.value)) {
|
|
1917
|
+
if (character === "\r" || character === "\n") {
|
|
1918
|
+
finish(answer);
|
|
1919
|
+
return;
|
|
1920
|
+
}
|
|
1921
|
+
answer += character;
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
})().catch(() => finish(answer));
|
|
1925
|
+
}
|
|
1926
|
+
});
|
|
1927
|
+
stdout.write("\n");
|
|
1928
|
+
return value;
|
|
1929
|
+
}
|
|
1930
|
+
if (process.platform === "win32") {
|
|
1931
|
+
// Windows consoles (PowerShell 7 / ConPTY) do not reliably forward
|
|
1932
|
+
// raw-mode keystrokes to native children: a "y" + Enter can be frozen,
|
|
1933
|
+
// split across chunks, or doubled ("yy" -> "Please answer y or n.").
|
|
1934
|
+
// Read cooked console lines without setRawMode instead; the console
|
|
1935
|
+
// itself provides line editing and echo.
|
|
1936
|
+
const rl = createInterface({ input: stdin, terminal: false, crlfDelay: Infinity });
|
|
1937
|
+
const cooked = await new Promise((resolve) => {
|
|
1938
|
+
let got = null;
|
|
1939
|
+
rl.on("line", (line) => {
|
|
1940
|
+
got = line;
|
|
1941
|
+
rl.close();
|
|
1942
|
+
});
|
|
1943
|
+
rl.on("close", () => resolve(got ?? ""));
|
|
1944
|
+
});
|
|
1855
1945
|
stdout.write("\n");
|
|
1856
|
-
return
|
|
1946
|
+
return cooked;
|
|
1857
1947
|
}
|
|
1858
1948
|
stdin.setRawMode(true);
|
|
1859
1949
|
stdin.resume();
|
package/src/index.mjs
CHANGED
|
@@ -173,11 +173,22 @@ export function upgradePath(sourceVersion, targetVersion) {
|
|
|
173
173
|
|
|
174
174
|
export async function inspectSystemResources(root) {
|
|
175
175
|
validateHome(root);
|
|
176
|
+
// Stat the nearest existing ancestor: on a clean Linux home the install
|
|
177
|
+
// root's parent (~/.config) may not exist yet, and statfs on a missing
|
|
178
|
+
// path throws ENOENT. Walking up guarantees a real mount point for the
|
|
179
|
+
// free-disk measurement.
|
|
176
180
|
let target = root;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
+
for (;;) {
|
|
182
|
+
try {
|
|
183
|
+
await stat(target);
|
|
184
|
+
break;
|
|
185
|
+
} catch {
|
|
186
|
+
const parent = dirname(target);
|
|
187
|
+
if (parent === target) {
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
target = parent;
|
|
191
|
+
}
|
|
181
192
|
}
|
|
182
193
|
const fileSystem = await statfs(target);
|
|
183
194
|
return {
|
package/templates/compose.yml
CHANGED
|
@@ -10,6 +10,7 @@ services:
|
|
|
10
10
|
environment:
|
|
11
11
|
SPACE_PUBLIC_DISTRIBUTION: "true"
|
|
12
12
|
SPACE_DEV_LOGIN: "false"
|
|
13
|
+
SPACE_APP_VERSION: "${SPACEAPP_IMAGE_TAG}"
|
|
13
14
|
SPACE_TELEMETRY_ENABLED: "${SPACEAPP_TELEMETRY}"
|
|
14
15
|
SPACE_RUNTIME_STORE: postgres
|
|
15
16
|
SPACE_DATABASE_URL_SOURCE_FILE: /run/secrets/database-url
|