@jskit-ai/jskit-catalog 0.2.34 → 0.2.35

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.
@@ -6,7 +6,7 @@
6
6
  "release": {
7
7
  "packages": {
8
8
  "@jskit-ai/agent": "0.1.2",
9
- "@jskit-ai/agent-docs": "0.1.282",
9
+ "@jskit-ai/agent-docs": "0.1.283",
10
10
  "@jskit-ai/agent-runtime": "0.1.3",
11
11
  "@jskit-ai/architecture-inspector": "0.1.11",
12
12
  "@jskit-ai/assistant-core": "0.2.27",
@@ -32,7 +32,7 @@
32
32
  "@jskit-ai/database-runtime-postgres": "0.1.259",
33
33
  "@jskit-ai/http-runtime": "0.2.13",
34
34
  "@jskit-ai/http-web": "0.2.21",
35
- "@jskit-ai/jskit-catalog": "0.2.34",
35
+ "@jskit-ai/jskit-catalog": "0.2.35",
36
36
  "@jskit-ai/json-rest-api-core": "0.2.14",
37
37
  "@jskit-ai/kernel": "0.2.13",
38
38
  "@jskit-ai/material-web": "0.1.3",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/jskit-catalog",
3
- "version": "0.2.34",
3
+ "version": "0.2.35",
4
4
  "description": "Published metadata catalog for JSKIT package metadata.",
5
5
  "type": "module",
6
6
  "files": [
@@ -4,10 +4,15 @@
4
4
  // Each worker's server environment is also left, readable only by this user, where the worker's own process takes it
5
5
  // (browser-tests.mjs). With the app's `signIn` module, each server is then signed in once, and its storage state left
6
6
  // for its worker. Each server's output goes to test-results/browser-test-servers/worker-<n>.log; a server that stops
7
- // ends the run.
7
+ // ends the run. A port that already answers is refused before anything is built or started: a server left there by an
8
+ // earlier run would answer for this run's. Stopped by Playwright (SIGTERM), by Ctrl-C, or by the end of the stdin pipe
9
+ // Playwright gives its webServer (a Playwright that ended without stopping it), the launcher stops its servers and
10
+ // waits for them, within Playwright's grace, before it exits.
8
11
  import { spawn } from "node:child_process";
9
- import { createWriteStream, mkdirSync, writeFileSync } from "node:fs";
12
+ import { once } from "node:events";
13
+ import { createWriteStream, fstatSync, mkdirSync, writeFileSync } from "node:fs";
10
14
  import { createRequire } from "node:module";
15
+ import net from "node:net";
11
16
  import path from "node:path";
12
17
  import process from "node:process";
13
18
  import { pathToFileURL } from "node:url";
@@ -16,6 +21,8 @@ import { buildFrontend } from "./browser-test-build.mjs";
16
21
  const SETTINGS_VARIABLE = "JSKIT_BROWSER_TESTS";
17
22
  const LOG_DIRECTORY = path.join("test-results", "browser-test-servers");
18
23
  const READY_TIMEOUT_MS = 60_000;
24
+ // Within the 10 s gracefulShutdown browser-tests.mjs gives Playwright's webServer; a server still running then is killed.
25
+ const STOP_WAIT_MS = 9_000;
19
26
 
20
27
  const seconds = (since) => `${((performance.now() - since) / 1000).toFixed(1)}s`;
21
28
  const words = (command) => String(command).trim().split(/\s+/u).filter(Boolean);
@@ -51,6 +58,21 @@ async function signInWorkers(appRoot, signIn, baseUrls, environments, signedInSt
51
58
  console.log(`[browser-tests] ${baseUrls.length} worker(s) signed in in ${seconds(started)}.`);
52
59
  }
53
60
 
61
+ const answers = (host, port) => new Promise((resolve) => {
62
+ const socket = net.connect(port, host);
63
+ socket.once("connect", () => { socket.destroy(); resolve(true); });
64
+ socket.once("error", () => resolve(false));
65
+ });
66
+
67
+ async function refuseBusyPorts(host, ports) {
68
+ const taken = await Promise.all(ports.map((port) => answers(host, port)));
69
+ const busy = ports.filter((_, index) => taken[index]);
70
+ if (busy.length > 0) {
71
+ throw new Error(`Port ${busy.join(", ")} on ${host} already answers: a server left there would answer for this run's. ` +
72
+ "Stop it, or set PLAYWRIGHT_PORT to free ports.");
73
+ }
74
+ }
75
+
54
76
  async function waitUntilReady(url, server, name) {
55
77
  const started = performance.now();
56
78
  const deadline = Date.now() + READY_TIMEOUT_MS;
@@ -72,12 +94,27 @@ async function waitUntilReady(url, server, name) {
72
94
 
73
95
  const servers = [];
74
96
  let stopping = false;
97
+ const running = (server) => server.exitCode === null && server.signalCode === null;
75
98
  function stopServers() {
76
99
  stopping = true;
77
- for (const server of servers) server.kill("SIGTERM");
100
+ for (const server of servers) if (running(server)) server.kill("SIGTERM");
101
+ }
102
+ async function stopAndExit(code) {
103
+ if (stopping) return;
104
+ stopServers();
105
+ const stopped = Promise.all(servers.filter(running).map((server) => once(server, "exit")));
106
+ await Promise.race([stopped, new Promise((resolve) => setTimeout(resolve, STOP_WAIT_MS))]);
107
+ for (const server of servers) if (running(server)) server.kill("SIGKILL");
108
+ process.exit(code);
109
+ }
110
+ process.once("SIGINT", () => stopAndExit(130));
111
+ process.once("SIGTERM", () => stopAndExit(143));
112
+ // Only a pipe ends when Playwright does; a launcher started without one (stdin ignored, /dev/null) would end at once.
113
+ const input = fstatSync(0);
114
+ if (input.isFIFO() || input.isSocket()) {
115
+ process.stdin.once("end", () => stopAndExit(1));
116
+ process.stdin.resume();
78
117
  }
79
- process.once("SIGINT", () => { stopServers(); process.exit(130); });
80
- process.once("SIGTERM", () => { stopServers(); process.exit(143); });
81
118
  process.once("exit", stopServers);
82
119
 
83
120
  const started = performance.now();
@@ -90,6 +127,7 @@ const baseEnvironment = { ...process.env };
90
127
  delete baseEnvironment[SETTINGS_VARIABLE];
91
128
  const baseUrls = Array.from({ length: workers }, (_, index) => `http://${host}:${basePort + index}`);
92
129
  try {
130
+ await refuseBusyPorts(host, baseUrls.map((_, index) => basePort + index));
93
131
  const [, workerEnvironments] = await Promise.all([
94
132
  buildFrontend({ appRoot, build }),
95
133
  prepareWorkers(appRoot, prepare, workers, baseUrls)
@@ -117,8 +155,7 @@ try {
117
155
  child.once("exit", (code, signal) => {
118
156
  if (stopping) return;
119
157
  console.error(`[browser-tests] ${name} stopped (${signal || `exit ${code}`}); log: ${log}`);
120
- stopServers();
121
- process.exit(1);
158
+ stopAndExit(1);
122
159
  });
123
160
  servers.push(child);
124
161
  return waitUntilReady(new URL(health, baseUrl), child, name);
@@ -127,6 +164,5 @@ try {
127
164
  console.log(`jskit browser tests: ${workers} server(s) ready in ${seconds(started)}; logs in ${LOG_DIRECTORY}.`);
128
165
  } catch (error) {
129
166
  console.error(`[browser-tests] ${error.message}`);
130
- stopServers();
131
- process.exit(1);
167
+ await stopAndExit(1);
132
168
  }