@zixt/host 0.0.85 → 0.0.87

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.
Files changed (2) hide show
  1. package/dist/index.js +170 -43
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7,9 +7,6 @@ var __export = (target, all) => {
7
7
  __defProp(target, name, { get: all[name], enumerable: true });
8
8
  };
9
9
 
10
- // src/index.ts
11
- import { fstatSync as fstatSync2 } from "node:fs";
12
-
13
10
  // src/supervisor.ts
14
11
  import { spawn as spawn5 } from "node:child_process";
15
12
  import { fstatSync } from "node:fs";
@@ -31,7 +28,7 @@ import { homedir as homedir3 } from "node:os";
31
28
  // package.json
32
29
  var package_default = {
33
30
  name: "@zixt/host",
34
- version: "0.0.85",
31
+ version: "0.0.87",
35
32
  type: "module",
36
33
  exports: {
37
34
  ".": "./src/client.ts",
@@ -25828,14 +25825,26 @@ setTimeout(() => { if (!launched) fail(); }, 130000).unref();
25828
25825
  var InstallerContainmentError = class extends Error {
25829
25826
  name = "InstallerContainmentError";
25830
25827
  };
25828
+ function describeInstallerExit(code, signal) {
25829
+ if (signal !== null) return `was killed by ${signal}`;
25830
+ if (code === null) return "ended without reporting an exit code";
25831
+ return `exited with code ${code}`;
25832
+ }
25831
25833
  async function installRelease(version2, options = {}) {
25832
- if (!VERSION_DIR.test(version2)) return null;
25834
+ const fail = (reason, detail) => {
25835
+ options.onFailure?.({ version: version2, reason, detail });
25836
+ return null;
25837
+ };
25838
+ if (!VERSION_DIR.test(version2)) {
25839
+ return fail("unsupported_version", "that is not a release name this Host can install");
25840
+ }
25833
25841
  const platform = options.platform ?? process.platform;
25834
25842
  const root = options.root ?? versionsRoot();
25835
25843
  const prefix = join8(root, version2);
25836
25844
  const entry = installedReleaseEntry(version2, root);
25837
25845
  if (await validReleaseAtPrefix(prefix, version2)) return entry;
25838
- if (options.signal?.aborted) return null;
25846
+ if (options.signal?.aborted)
25847
+ return fail("cancelled", "this Host was stopping before npm started");
25839
25848
  const installerCommand = options.installerCommand ?? await resolveInstallerCommand({ platform });
25840
25849
  await mkdir5(root, { recursive: true, mode: 448 });
25841
25850
  const staging = join8(root, `.install-${version2}-${process.pid}-${crypto.randomUUID()}`);
@@ -25870,9 +25879,12 @@ async function installRelease(version2, options = {}) {
25870
25879
  let child;
25871
25880
  try {
25872
25881
  child = spawnInstaller(staging, version2);
25873
- } catch {
25882
+ } catch (error52) {
25874
25883
  await rm5(staging, { recursive: true, force: true }).catch(() => void 0);
25875
- return null;
25884
+ return fail(
25885
+ "installer_unavailable",
25886
+ `${installerCommand} could not be started (${error52 instanceof Error ? error52.message : "unknown error"})`
25887
+ );
25876
25888
  }
25877
25889
  let resolveChildExited;
25878
25890
  const childExited = new Promise((resolveExit) => {
@@ -25894,6 +25906,8 @@ async function installRelease(version2, options = {}) {
25894
25906
  installerContainmentSetupError = error52;
25895
25907
  return null;
25896
25908
  }) : Promise.resolve(null);
25909
+ const timeoutMs = options.timeoutMs ?? INSTALL_TIMEOUT_MS;
25910
+ const outcome = { code: null, signal: null, timedOut: false, spawnError: null };
25897
25911
  const installed = await new Promise((resolve18, reject3) => {
25898
25912
  let finished = false;
25899
25913
  let cleanupStarted = false;
@@ -25948,13 +25962,21 @@ async function installRelease(version2, options = {}) {
25948
25962
  }
25949
25963
  })();
25950
25964
  };
25951
- const timer = setTimeout(requestCleanup, options.timeoutMs ?? INSTALL_TIMEOUT_MS);
25965
+ const timer = setTimeout(() => {
25966
+ outcome.timedOut = true;
25967
+ requestCleanup();
25968
+ }, timeoutMs);
25952
25969
  timer.unref?.();
25953
- child.once("error", () => {
25970
+ child.once("error", (error52) => {
25971
+ outcome.spawnError = error52.message;
25954
25972
  observeExit();
25955
25973
  requestCleanup();
25956
25974
  });
25957
- child.once("exit", (code) => {
25975
+ child.once("exit", (code, signal) => {
25976
+ if (!usesWindowsInstallerGuardian) {
25977
+ outcome.code = code;
25978
+ outcome.signal = signal;
25979
+ }
25958
25980
  observeExit();
25959
25981
  if (usesWindowsInstallerGuardian) {
25960
25982
  requestCleanup();
@@ -25974,6 +25996,9 @@ async function installRelease(version2, options = {}) {
25974
25996
  }
25975
25997
  const code = message.code;
25976
25998
  if (code !== null && (typeof code !== "number" || !Number.isInteger(code))) return;
25999
+ outcome.code = code;
26000
+ const signal = message.signal;
26001
+ outcome.signal = typeof signal === "string" ? signal : null;
25977
26002
  successfulCleanupResult = code === 0;
25978
26003
  requestCleanup();
25979
26004
  });
@@ -26004,8 +26029,32 @@ async function installRelease(version2, options = {}) {
26004
26029
  if (options.signal?.aborted) requestCleanup();
26005
26030
  });
26006
26031
  try {
26007
- if (options.signal?.aborted || !installed || !await validReleaseAtPrefix(staging, version2)) {
26008
- return null;
26032
+ if (options.signal?.aborted) {
26033
+ return fail("cancelled", "this Host was stopping before the install finished");
26034
+ }
26035
+ if (!installed) {
26036
+ if (outcome.timedOut) {
26037
+ return fail(
26038
+ "installer_timed_out",
26039
+ `${installerCommand} was still running after ${Math.round(timeoutMs / 1e3)}s and was stopped`
26040
+ );
26041
+ }
26042
+ if (outcome.spawnError !== null) {
26043
+ return fail(
26044
+ "installer_unavailable",
26045
+ `${installerCommand} failed to run (${outcome.spawnError})`
26046
+ );
26047
+ }
26048
+ return fail(
26049
+ "installer_failed",
26050
+ `${installerCommand} install ${PACKAGE_NAME}@${version2} ${describeInstallerExit(outcome.code, outcome.signal)}`
26051
+ );
26052
+ }
26053
+ if (!await validReleaseAtPrefix(staging, version2)) {
26054
+ return fail(
26055
+ "incomplete_release",
26056
+ `${installerCommand} reported success but left no runnable ${PACKAGE_NAME}@${version2} in ${staging}`
26057
+ );
26009
26058
  }
26010
26059
  if (await validReleaseAtPrefix(prefix, version2)) return entry;
26011
26060
  const existing = await lstat5(prefix).catch((error52) => {
@@ -26015,11 +26064,18 @@ async function installRelease(version2, options = {}) {
26015
26064
  if (existing) await rename3(prefix, quarantine);
26016
26065
  try {
26017
26066
  await rename3(staging, prefix);
26018
- } catch {
26019
- return await validReleaseAtPrefix(prefix, version2) ? entry : null;
26067
+ } catch (error52) {
26068
+ if (await validReleaseAtPrefix(prefix, version2)) return entry;
26069
+ return fail(
26070
+ "commit_failed",
26071
+ `the validated tree could not be moved into ${prefix} (${error52 instanceof Error ? error52.message : "unknown error"})`
26072
+ );
26020
26073
  }
26021
26074
  await syncDirectory3(root);
26022
- return await validReleaseAtPrefix(prefix, version2) ? entry : null;
26075
+ return await validReleaseAtPrefix(prefix, version2) ? entry : fail(
26076
+ "commit_failed",
26077
+ `${prefix} is not a runnable ${PACKAGE_NAME}@${version2} after installing`
26078
+ );
26023
26079
  } finally {
26024
26080
  await rm5(staging, { recursive: true, force: true }).catch(() => void 0);
26025
26081
  await rm5(quarantine, { recursive: true, force: true }).catch(() => void 0);
@@ -26407,15 +26463,15 @@ async function launchHostSupervisor(options = {}) {
26407
26463
  const onTerm = () => stop("SIGTERM");
26408
26464
  process.on("SIGINT", onInt);
26409
26465
  process.on("SIGTERM", onTerm);
26410
- let stdinIsPipe2 = options.parentStdinIsPipe ?? false;
26466
+ let stdinIsPipe = options.parentStdinIsPipe ?? false;
26411
26467
  if (options.parentStdinIsPipe === void 0) {
26412
26468
  try {
26413
26469
  const stat3 = fstatSync(0);
26414
- stdinIsPipe2 = !stat3.isCharacterDevice() && !stat3.isFile() && !process.stdin.isTTY;
26470
+ stdinIsPipe = !stat3.isCharacterDevice() && !stat3.isFile() && !process.stdin.isTTY;
26415
26471
  } catch {
26416
26472
  }
26417
26473
  }
26418
- const observesParentStdin = managedLifecycle && stdinIsPipe2;
26474
+ const observesParentStdin = managedLifecycle && stdinIsPipe;
26419
26475
  const onStdinEnd = () => stop("SIGTERM");
26420
26476
  const onStdinError = () => stop("SIGTERM");
26421
26477
  const onStdinData = (chunk) => {
@@ -26601,7 +26657,10 @@ async function superviseHost(options = {}) {
26601
26657
  platform,
26602
26658
  workerDiagnosticsRoot
26603
26659
  ));
26604
- const install = options.installVersion ?? ((version2, signal) => installRelease(version2, signal ? { signal } : {}));
26660
+ const install = options.installVersion ?? ((version2, signal, onFailure) => installRelease(version2, {
26661
+ ...signal ? { signal } : {},
26662
+ ...onFailure ? { onFailure } : {}
26663
+ }));
26605
26664
  const cleanupWorker = options.cleanupWorker ?? (options.spawnWorker ? async () => {
26606
26665
  } : null);
26607
26666
  const createWorkerContainment = options.createWorkerContainment ?? (platform === "win32" && !options.spawnWorker ? async (target, identityNonce, signal) => {
@@ -27023,6 +27082,9 @@ async function superviseHost(options = {}) {
27023
27082
  if (!await waitAfterCrash(runtimeMs)) return 0;
27024
27083
  continue;
27025
27084
  }
27085
+ log2(
27086
+ `Zixt Host: worker on ${command.version ?? "the launched copy"} asked to update (exit ${effectiveCode}${watchdogTriggered ? `, announced ${announcedExitCode} before its teardown was contained` : ""}); checking the published release`
27087
+ );
27026
27088
  if (watchdogTriggered) {
27027
27089
  await reportWorkerExit(workerExit, "installing the update it asked for anyway");
27028
27090
  }
@@ -27030,25 +27092,40 @@ async function superviseHost(options = {}) {
27030
27092
  if (orderlyValidatedBoundary) rollbackCandidate = null;
27031
27093
  const target = await published();
27032
27094
  let updateLanded = false;
27095
+ let rejectedUpdate = null;
27033
27096
  if (target === null) {
27034
27097
  log2(
27035
27098
  "Zixt Host: a newer release was reported but the registry is unreachable; staying on the current version"
27036
27099
  );
27037
27100
  } else if (target === command.version) {
27038
- log2(
27039
- `Zixt Host: this Machine already runs the newest published release (${target}), so an update cannot satisfy the cloud; waiting for a newer one`
27040
- );
27101
+ if (unsatisfiableUpdates === 0) {
27102
+ log2(
27103
+ `Zixt Host: ${target} is the newest published Zixt Host, so no update can satisfy this cloud; the cloud this Machine is paired to speaks a newer Host protocol than any published release`
27104
+ );
27105
+ log2(
27106
+ "Zixt Host: pair this Machine with a cloud running a published release, or start the Host from the same source checkout that cloud runs; this Machine connects by itself once a matching release is published"
27107
+ );
27108
+ }
27041
27109
  } else if (attempted.has(target)) {
27042
27110
  log2(`Zixt Host: already running ${target}; ignoring a repeated update request`);
27043
27111
  } else {
27044
27112
  let entry;
27113
+ const attempt = { failure: null };
27114
+ log2(`Zixt Host: installing ${target} over ${command.version ?? "the launched copy"}`);
27045
27115
  try {
27046
- entry = await install(target, shutdownController.signal);
27116
+ entry = await install(target, shutdownController.signal, (failure2) => {
27117
+ attempt.failure = failure2;
27118
+ });
27047
27119
  } catch (error52) {
27048
27120
  if (error52 instanceof InstallerContainmentError) {
27049
27121
  log2(`Zixt Host: ${error52.message}; handing recovery to the stable launcher`);
27050
27122
  return 1;
27051
27123
  }
27124
+ attempt.failure = {
27125
+ version: target,
27126
+ reason: "commit_failed",
27127
+ detail: error52 instanceof Error ? error52.message : "unknown error"
27128
+ };
27052
27129
  entry = null;
27053
27130
  }
27054
27131
  if (shuttingDown2) return 0;
@@ -27136,9 +27213,14 @@ async function superviseHost(options = {}) {
27136
27213
  }
27137
27214
  }
27138
27215
  } else {
27139
- log2(`Zixt Host: could not install ${target}; staying on the current version`);
27216
+ const failure2 = attempt.failure;
27217
+ log2(
27218
+ `Zixt Host: could not install ${target} (${failure2 ? `${failure2.reason}: ${failure2.detail}` : "no reason reported"}); staying on ${command.version ?? "the launched copy"}`
27219
+ );
27220
+ if (failure2?.reason !== "cancelled") rejectedUpdate = target;
27140
27221
  }
27141
27222
  }
27223
+ if (rejectedUpdate) command = { ...command, rejectedVersion: rejectedUpdate };
27142
27224
  unsatisfiableUpdates = updateLanded ? 0 : unsatisfiableUpdates + 1;
27143
27225
  if (unsatisfiableUpdates > 0) {
27144
27226
  if (!await waitOrShutdown(streakBackoffMs(unsatisfiableUpdates))) return 0;
@@ -27191,6 +27273,38 @@ function beginWorkerShutdown(options) {
27191
27273
  });
27192
27274
  }
27193
27275
 
27276
+ // src/parent-pipe.ts
27277
+ import { fstatSync as fstatSync2 } from "node:fs";
27278
+ var END_OF_TEXT = 3;
27279
+ function stdinIsParentPipe(stat3 = (fd) => fstatSync2(fd), isTTY = process.stdin.isTTY === true) {
27280
+ try {
27281
+ const stdin = stat3(0);
27282
+ return !stdin.isCharacterDevice() && !stdin.isFile() && !isTTY;
27283
+ } catch {
27284
+ return false;
27285
+ }
27286
+ }
27287
+ function watchParentPipe(pipe2, onStop) {
27288
+ const stop = () => onStop();
27289
+ const onData = (chunk) => {
27290
+ if (chunk.includes(END_OF_TEXT)) onStop();
27291
+ };
27292
+ pipe2.on("end", stop);
27293
+ pipe2.on("error", stop);
27294
+ pipe2.on("data", onData);
27295
+ pipe2.resume();
27296
+ let released = false;
27297
+ return () => {
27298
+ if (released) return;
27299
+ released = true;
27300
+ pipe2.off("end", stop);
27301
+ pipe2.off("error", stop);
27302
+ pipe2.off("data", onData);
27303
+ pipe2.pause();
27304
+ pipe2.unref?.();
27305
+ };
27306
+ }
27307
+
27194
27308
  // src/index.ts
27195
27309
  import { homedir as homedir14, hostname as hostname3 } from "node:os";
27196
27310
 
@@ -40712,6 +40826,8 @@ var connectionContext = connectionLogContext({
40712
40826
  });
40713
40827
  var stopUpdateWatch = () => {
40714
40828
  };
40829
+ var releaseParentPipeWatch = () => {
40830
+ };
40715
40831
  var updateRestartGate = createIdleUpdateRestartGate({
40716
40832
  activeTasks: () => activeSessions,
40717
40833
  deferred: (version2, activeTasks) => {
@@ -40860,9 +40976,10 @@ var client = new HostClient({
40860
40976
  queueMicrotask(() => shutdown(DO_NOT_RESTART_EXIT_CODE));
40861
40977
  break;
40862
40978
  case "incompatible":
40863
- log.error("Host update required", {
40979
+ log.error("Zixt Cloud refused this Host as too old to connect", {
40864
40980
  ...connectionContext,
40865
- next: "Pull the latest Zixt version and restart the Host"
40981
+ protocol: PROTOCOL_VERSION,
40982
+ next: packagedBuild ? "Zixt checks the published release now; nothing to do on this Machine" : "Update this checkout through Git and restart the Host"
40866
40983
  });
40867
40984
  queueMicrotask(() => shutdown(packagedBuild ? UPDATE_EXIT_CODE : DO_NOT_RESTART_EXIT_CODE));
40868
40985
  break;
@@ -40884,7 +41001,7 @@ var diagnosticsRoot = configuredWorkerDiagnosticsRoot();
40884
41001
  var reportedWorkerExits = diagnosticsRoot ? await readWorkerExits(diagnosticsRoot) : [];
40885
41002
  for (const { record: record2 } of reportedWorkerExits) {
40886
41003
  const { summary, context } = describeWorkerExit(record2);
40887
- log.error(`The previous Zixt Host worker ${summary}`, {
41004
+ replayConsoleLine("error", `The previous Zixt Host worker ${summary}`, {
40888
41005
  machine,
40889
41006
  at: record2.at,
40890
41007
  ...context,
@@ -40899,11 +41016,20 @@ function shutdown(exitCode = 0) {
40899
41016
  if (shuttingDown) process.exit(exitCode);
40900
41017
  shuttingDown = true;
40901
41018
  retainRunAssignments();
40902
- log.info("Stopping Zixt Host", { machine, activeTasks: activeSessions });
41019
+ log.info("Stopping Zixt Host", {
41020
+ machine,
41021
+ activeTasks: activeSessions,
41022
+ exit: exitCode,
41023
+ reason: exitCode === UPDATE_EXIT_CODE ? "installing a Zixt Host update" : exitCode === DO_NOT_RESTART_EXIT_CODE ? "this Machine needs attention before it restarts" : "stop requested"
41024
+ });
40903
41025
  beginWorkerShutdown({
40904
41026
  activeTasks: activeSessions,
40905
41027
  exitCode,
40906
- teardown: () => client.stop(),
41028
+ teardown: async () => {
41029
+ await client.stop();
41030
+ releaseParentPipeWatch();
41031
+ stopUpdateWatch();
41032
+ },
40907
41033
  stopHeartbeat: () => workerWatchdog.stop(),
40908
41034
  onForcedExit: ({ deadlineMs, code, teardownCompleted }) => {
40909
41035
  log.warn(
@@ -40912,6 +41038,9 @@ function shutdown(exitCode = 0) {
40912
41038
  machine,
40913
41039
  deadline: formatDuration(deadlineMs),
40914
41040
  exit: code,
41041
+ // A stop that had to unwind live runs exits as a crash instead, so
41042
+ // the honoured code can differ from the verdict this stop was for.
41043
+ ...code === exitCode ? {} : { requested: exitCode },
40915
41044
  activeTasks: activeSessions,
40916
41045
  next: "The Host restarts automatically; report this if it repeats"
40917
41046
  }
@@ -40919,6 +41048,15 @@ function shutdown(exitCode = 0) {
40919
41048
  }
40920
41049
  });
40921
41050
  }
41051
+ var skippedRelease = process.env.ZIXT_HOST_REJECT_VERSION;
41052
+ if (packagedBuild && skippedRelease) {
41053
+ log.warn("Skipping a Zixt Host release this Machine could not install", {
41054
+ machine,
41055
+ version: skippedRelease,
41056
+ running: HOST_VERSION,
41057
+ next: "This Machine keeps working and takes the next release; report it if it stays behind"
41058
+ });
41059
+ }
40922
41060
  stopUpdateWatch = !packagedBuild ? () => {
40923
41061
  } : watchForUpdates({
40924
41062
  onUpdateAvailable: (version2) => {
@@ -40928,17 +41066,6 @@ stopUpdateWatch = !packagedBuild ? () => {
40928
41066
  });
40929
41067
  process.on("SIGINT", () => shutdown());
40930
41068
  process.on("SIGTERM", () => shutdown());
40931
- var stdinIsPipe = false;
40932
- try {
40933
- const stat3 = fstatSync2(0);
40934
- stdinIsPipe = !stat3.isCharacterDevice() && !stat3.isFile() && !process.stdin.isTTY;
40935
- } catch {
40936
- }
40937
- if (stdinIsPipe) {
40938
- process.stdin.on("end", () => shutdown());
40939
- process.stdin.on("error", () => shutdown());
40940
- process.stdin.on("data", (chunk) => {
40941
- if (chunk.includes(3)) shutdown();
40942
- });
40943
- process.stdin.resume();
41069
+ if (stdinIsParentPipe()) {
41070
+ releaseParentPipeWatch = watchParentPipe(process.stdin, () => shutdown());
40944
41071
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zixt/host",
3
- "version": "0.0.85",
3
+ "version": "0.0.87",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/client.ts",