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 CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "run-spaceapp",
3
- "version": "0.1.26",
4
- "spaceappRuntimeVersion": "0.1.26",
5
- "spaceappHostRootRuntimeCompatible": true,
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": "828ab44d62e2786effdc03066807f5370603f6ba"
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
- let value = "";
1852
- for await (const chunk of stdin) {
1853
- value += chunk;
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 value.replace(/[\r\n]+$/, "");
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
- 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 {
@@ -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