@stim-cli/server 1.20.2 → 1.22.0

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.
@@ -4,6 +4,6 @@ const encode = (version) => version.split(".", 3).reduce((sum, part) => sum * 1e
4
4
  if (encode(process.versions.node) < encode("22.12.0")) {
5
5
  process.stderr.write(`STIM_NODE_UNSUPPORTED: stim-server needs Node 22.12.0 or later; this is Node ${process.versions.node} at ${process.execPath}. To run stim-server where a project pins an older Node, see https://stim.appandflow.com/docs/requirements#older-node-pins\n`);
6
6
  process.exitCode = 1;
7
- } else import("./stim-server-FWcuNpW7.mjs");
7
+ } else import("./stim-server-BKcEacbU.mjs");
8
8
  //#endregion
9
9
  export {};
@@ -0,0 +1,13 @@
1
+ //#region src/service-runner.d.ts
2
+ /**
3
+ * The Windows supervisor of `stim-server service`. It imports nothing but Node, so `install` can copy this one file
4
+ * beside the service and it keeps working whatever server version the service runs.
5
+ */
6
+ interface SupervisorState {
7
+ supervisor: number;
8
+ server: number | null;
9
+ runs: number;
10
+ lastExitCode: string | null;
11
+ }
12
+ //#endregion
13
+ export { SupervisorState };
@@ -0,0 +1,96 @@
1
+ import { spawn } from "node:child_process";
2
+ import { appendFileSync, closeSync, mkdirSync, openSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { setTimeout } from "node:timers/promises";
5
+ //#region src/service-runner.ts
6
+ function program(file) {
7
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
8
+ const value = parsed && typeof parsed === "object" ? { ...parsed } : {};
9
+ const argv = Array.isArray(value.programArguments) ? value.programArguments.filter((entry) => typeof entry === "string") : [];
10
+ if (value.managed !== true || !argv.length || typeof value.logPath !== "string") throw new Error(`${file} is not a service file install wrote.`);
11
+ const environment = {};
12
+ if (value.environment && typeof value.environment === "object") {
13
+ for (const [name, entry] of Object.entries(value.environment)) if (typeof entry === "string") environment[name] = entry;
14
+ }
15
+ return {
16
+ argv,
17
+ environment,
18
+ workingDirectory: typeof value.workingDirectory === "string" ? value.workingDirectory : "",
19
+ logPath: value.logPath
20
+ };
21
+ }
22
+ async function supervise(file, statePath, throttleMs) {
23
+ let runs = 0;
24
+ let lastExitCode = null;
25
+ const log = (line) => {
26
+ try {
27
+ appendFileSync(join(dirname(statePath), "supervisor.log"), `${(/* @__PURE__ */ new Date()).toISOString()} ${line}\n`);
28
+ } catch {}
29
+ };
30
+ const record = (server) => {
31
+ const state = {
32
+ supervisor: process.pid,
33
+ server,
34
+ runs,
35
+ lastExitCode
36
+ };
37
+ try {
38
+ mkdirSync(dirname(statePath), { recursive: true });
39
+ const partial = `${statePath}.${process.pid}.tmp`;
40
+ writeFileSync(partial, `${JSON.stringify(state)}\n`);
41
+ renameSync(partial, statePath);
42
+ } catch (error) {
43
+ log(`could not record ${statePath}: ${error instanceof Error ? error.message : String(error)}`);
44
+ }
45
+ };
46
+ record(null);
47
+ for (;;) {
48
+ try {
49
+ lastExitCode = await runOnce(file, (server) => {
50
+ runs += 1;
51
+ record(server);
52
+ });
53
+ } catch (error) {
54
+ lastExitCode = `supervisor: ${error instanceof Error ? error.message : String(error)}`;
55
+ log(lastExitCode);
56
+ }
57
+ record(null);
58
+ await setTimeout(throttleMs);
59
+ }
60
+ }
61
+ /** Starts the server once from the service file and resolves with how it ended. */
62
+ async function runOnce(file, started) {
63
+ const { argv, environment, workingDirectory, logPath } = program(file);
64
+ const [node = "", ...args] = argv;
65
+ mkdirSync(dirname(logPath), { recursive: true });
66
+ const out = openSync(logPath, "a");
67
+ const child = spawn(node, args, {
68
+ cwd: workingDirectory || void 0,
69
+ env: {
70
+ ...process.env,
71
+ ...environment
72
+ },
73
+ stdio: [
74
+ "ignore",
75
+ out,
76
+ out
77
+ ],
78
+ windowsHide: true
79
+ });
80
+ closeSync(out);
81
+ started(child.pid ?? null);
82
+ return new Promise((resolve) => {
83
+ child.once("error", (error) => resolve(error.message));
84
+ child.once("exit", (code, signal) => resolve(code === null ? `signal ${signal}` : String(code)));
85
+ });
86
+ }
87
+ const [file, statePath, throttle] = process.argv.slice(2);
88
+ if (!file || !statePath || !/^\d+$/.test(throttle ?? "")) {
89
+ process.stderr.write("usage: stim-service-runner <service file> <state file> <restart delay seconds>\n");
90
+ process.exitCode = 2;
91
+ } else supervise(file, statePath, Number(throttle) * 1e3).catch((error) => {
92
+ process.stderr.write(`stim-service-runner: ${error instanceof Error ? error.message : String(error)}\n`);
93
+ process.exitCode = 1;
94
+ });
95
+ //#endregion
96
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stim-cli/server",
3
- "version": "1.20.2",
3
+ "version": "1.22.0",
4
4
  "description": "Serves Stim status to paired clients over Tailscale, and runs reload and stop for clients granted control.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -28,8 +28,13 @@
28
28
  "./protocol.schema.json": "./dist/protocol.schema.json"
29
29
  },
30
30
  "dependencies": {
31
- "@stim-cli/core": "^1.20.2",
32
- "stim": "1.20.2",
31
+ "@stim-cli/agent-driver": "^1.22.0",
32
+ "@stim-cli/agent-driver-agent-device": "^1.22.0",
33
+ "@stim-cli/cache": "^1.22.0",
34
+ "@stim-cli/core": "^1.22.0",
35
+ "@stim-cli/transport": "^1.22.0",
36
+ "@stim-cli/transport-tailscale": "^1.22.0",
37
+ "stim": "1.22.0",
33
38
  "ws": "^8.21.3"
34
39
  },
35
40
  "devDependencies": {
@@ -1 +0,0 @@
1
- export {}
@@ -1,46 +0,0 @@
1
- import { execFile } from "node:child_process";
2
- import { sep } from "node:path";
3
- import { findProjectRoot, readStatsReport, statsProjectKey } from "@stim-cli/core/state";
4
- //#region src/stats-read.ts
5
- let git = null;
6
- let cancelled = false;
7
- const cancel = () => {
8
- cancelled = true;
9
- git?.kill("SIGKILL");
10
- };
11
- process.on("SIGTERM", cancel);
12
- process.on("message", (message) => {
13
- if (message === "cancel") cancel();
14
- });
15
- function gitPath(root, args) {
16
- return new Promise((resolve) => {
17
- git = execFile("git", [
18
- "-C",
19
- root,
20
- "rev-parse",
21
- ...args
22
- ], { encoding: "utf8" }, (error, stdout) => {
23
- git = null;
24
- const path = error ? "" : stdout.trim();
25
- resolve(path ? sep === "/" ? path : path.replaceAll("/", sep) : null);
26
- });
27
- });
28
- }
29
- try {
30
- const root = findProjectRoot(process.cwd());
31
- const commonDir = root ? await gitPath(root, ["--path-format=absolute", "--git-common-dir"]) : null;
32
- const repoRoot = root && !cancelled ? await gitPath(root, ["--show-toplevel"]) : null;
33
- if (!cancelled) {
34
- const key = root ? statsProjectKey({
35
- root,
36
- commonDir,
37
- repoRoot
38
- }) : null;
39
- const { report } = readStatsReport(key, Date.now());
40
- process.stdout.write(`${JSON.stringify(report)}\n`);
41
- }
42
- } finally {
43
- if (process.connected) process.disconnect();
44
- }
45
- //#endregion
46
- export {};