run-spaceapp 0.1.26 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "run-spaceapp",
3
- "version": "0.1.26",
4
- "spaceappRuntimeVersion": "0.1.26",
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": "828ab44d62e2786effdc03066807f5370603f6ba"
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,12 +1861,70 @@ 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
- let value = "";
1852
- for await (const chunk of stdin) {
1853
- value += chunk;
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.replace(/[\r\n]+$/, "");
1927
+ return value;
1857
1928
  }
1858
1929
  stdin.setRawMode(true);
1859
1930
  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
- try {
178
- await stat(target);
179
- } catch {
180
- target = dirname(root);
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 {