run-spaceapp 0.1.25 → 0.1.27
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 +3 -3
- package/src/cli.mjs +87 -7
- package/src/index.mjs +15 -4
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "run-spaceapp",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"spaceappRuntimeVersion": "0.1.
|
|
3
|
+
"version": "0.1.27",
|
|
4
|
+
"spaceappRuntimeVersion": "0.1.27",
|
|
5
5
|
"spaceappHostRootRuntimeCompatible": true,
|
|
6
6
|
"description": "Cross-platform Docker launcher for the SpaceApp self-hosted agent workspace",
|
|
7
7
|
"license": "Apache-2.0",
|
|
@@ -52,5 +52,5 @@
|
|
|
52
52
|
"access": "public",
|
|
53
53
|
"provenance": true
|
|
54
54
|
},
|
|
55
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "ac787b875a13b367f6d9d0cf8d9d063816ab36a9"
|
|
56
56
|
}
|
package/src/cli.mjs
CHANGED
|
@@ -82,6 +82,17 @@ const trustedEnvironmentNames = new Set([
|
|
|
82
82
|
"SPACEAPP_RESUME_SCRIPT_PATH"
|
|
83
83
|
]);
|
|
84
84
|
|
|
85
|
+
export function writeWindowsCredentialHint(platform, stderr) {
|
|
86
|
+
if (platform !== "win32") {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
stderr.write(
|
|
90
|
+
"If the failure above mentions Docker credentials (\"A specified logon session does not exist\"), " +
|
|
91
|
+
"Docker Desktop's credential helper requires an interactive Windows session. " +
|
|
92
|
+
`Run "${UNIVERSAL_COMMAND} install" from your desktop terminal (not SSH/CI), or open Docker Desktop and sign in once, then retry.\n`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
85
96
|
export async function withHeadlessDockerConfig(platform, spec, run) {
|
|
86
97
|
if (platform !== "win32") {
|
|
87
98
|
return run(spec);
|
|
@@ -1078,6 +1089,7 @@ async function repairRuntime({ root, config, platform, stdin, stdout, stderr, ex
|
|
|
1078
1089
|
(pullSpec) => execute(pullSpec, { stdin, stdout, stderr })
|
|
1079
1090
|
);
|
|
1080
1091
|
if (pullCode !== 0) {
|
|
1092
|
+
writeWindowsCredentialHint(platform, stderr);
|
|
1081
1093
|
return pullCode;
|
|
1082
1094
|
}
|
|
1083
1095
|
const upCode = await execute(
|
|
@@ -1127,6 +1139,7 @@ async function performUpdate({
|
|
|
1127
1139
|
(pullSpec) => execute(pullSpec, { stdin, stdout, stderr })
|
|
1128
1140
|
);
|
|
1129
1141
|
if (pullCode !== 0) {
|
|
1142
|
+
writeWindowsCredentialHint(platform, stderr);
|
|
1130
1143
|
throw new Error(`Image pull failed with Docker exit ${pullCode}.`);
|
|
1131
1144
|
}
|
|
1132
1145
|
const upCode = await execute(
|
|
@@ -1848,20 +1861,87 @@ async function executeWithDockerDiagnostics(execute, spec, io, { platform, stder
|
|
|
1848
1861
|
export async function readSecret(stdin, stdout, prompt, { mask = true } = {}) {
|
|
1849
1862
|
stdout.write(prompt);
|
|
1850
1863
|
if (!stdin.isTTY || typeof stdin.setRawMode !== "function") {
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1864
|
+
// Non-TTY stdin (e.g. piped input on Windows through npx.cmd). Read only
|
|
1865
|
+
// up to the first newline so an interactive console that reports non-TTY
|
|
1866
|
+
// stdin still works: previously this drained the stream to EOF, so typing
|
|
1867
|
+
// "y" + Enter looked frozen and a second keystroke produced "y\ny" and
|
|
1868
|
+
// "Please answer y or n." loops. When the stream ends before a newline
|
|
1869
|
+
// (fully piped answers), everything read is returned as-is so "y\nn\n"
|
|
1870
|
+
// piped input still yields successive valid answers. Event listeners are
|
|
1871
|
+
// used instead of the Readable async iterator because an abandoned
|
|
1872
|
+
// iterator can destroy the stream, breaking the next prompt.
|
|
1873
|
+
const value = await new Promise((resolve) => {
|
|
1874
|
+
let answer = "";
|
|
1875
|
+
const finish = (result) => {
|
|
1876
|
+
cleanup();
|
|
1877
|
+
resolve(result);
|
|
1878
|
+
};
|
|
1879
|
+
// Paused-mode reads: consume exactly one line from the buffer, leaving
|
|
1880
|
+
// the rest (and the stream itself) intact for the next prompt.
|
|
1881
|
+
const onReadable = () => {
|
|
1882
|
+
let chunk;
|
|
1883
|
+
while ((chunk = stdin.read()) !== null) {
|
|
1884
|
+
for (const character of String(chunk)) {
|
|
1885
|
+
if (character === "\r" || character === "\n") {
|
|
1886
|
+
finish(answer);
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
answer += character;
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
};
|
|
1893
|
+
const onEnd = () => finish(answer);
|
|
1894
|
+
const onClose = () => finish(answer);
|
|
1895
|
+
const cleanup = () => {
|
|
1896
|
+
stdin.off("readable", onReadable);
|
|
1897
|
+
stdin.off("end", onEnd);
|
|
1898
|
+
stdin.off("close", onClose);
|
|
1899
|
+
};
|
|
1900
|
+
if (typeof stdin.read === "function" && typeof stdin.on === "function") {
|
|
1901
|
+
stdin.on("readable", onReadable);
|
|
1902
|
+
stdin.once("end", onEnd);
|
|
1903
|
+
stdin.once("close", onClose);
|
|
1904
|
+
onReadable();
|
|
1905
|
+
} else {
|
|
1906
|
+
// Fallback for exotic streams without readable-mode support.
|
|
1907
|
+
const iterator = stdin[Symbol.asyncIterator]();
|
|
1908
|
+
(async () => {
|
|
1909
|
+
for (;;) {
|
|
1910
|
+
const step = await iterator.next();
|
|
1911
|
+
if (step.done) {
|
|
1912
|
+
finish(answer);
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
for (const character of String(step.value)) {
|
|
1916
|
+
if (character === "\r" || character === "\n") {
|
|
1917
|
+
finish(answer);
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1920
|
+
answer += character;
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
})().catch(() => finish(answer));
|
|
1924
|
+
}
|
|
1925
|
+
});
|
|
1855
1926
|
stdout.write("\n");
|
|
1856
|
-
return value
|
|
1927
|
+
return value;
|
|
1857
1928
|
}
|
|
1858
1929
|
stdin.setRawMode(true);
|
|
1859
1930
|
stdin.resume();
|
|
1860
1931
|
stdin.setEncoding("utf8");
|
|
1861
1932
|
let value = "";
|
|
1862
1933
|
try {
|
|
1863
|
-
|
|
1864
|
-
|
|
1934
|
+
// Drive the iterator manually instead of breaking a for-await loop:
|
|
1935
|
+
// breaking a for-await early calls the Readable async iterator's return(),
|
|
1936
|
+
// which DESTROYS the stream, so any later prompt would abort. Stopping
|
|
1937
|
+
// without calling return() leaves the stream paused and intact.
|
|
1938
|
+
const iterator = stdin[Symbol.asyncIterator]();
|
|
1939
|
+
for (;;) {
|
|
1940
|
+
const step = await iterator.next();
|
|
1941
|
+
if (step.done) {
|
|
1942
|
+
break;
|
|
1943
|
+
}
|
|
1944
|
+
for (const character of step.value) {
|
|
1865
1945
|
if (character === "\u0003") {
|
|
1866
1946
|
throw new Error("Input cancelled.");
|
|
1867
1947
|
}
|
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 {
|