@nyssance/tauri-hasgard 0.6.0-alpha.1 → 0.6.0-alpha.4
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/README.md +1 -1
- package/dist/index.js +377 -461
- package/dist/process-supervisor.js +78 -29
- package/dist/window.d.ts +1 -1
- package/package.json +7 -8
- package/dist/chunk-N7YDYAKI.js +0 -31
|
@@ -1,12 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
} from "
|
|
1
|
+
// src/process-supervisor.js
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { lstatSync, unlinkSync } from "node:fs";
|
|
4
|
+
import { createConnection } from "node:net";
|
|
5
|
+
import { setTimeout as delay2 } from "node:timers/promises";
|
|
6
|
+
|
|
7
|
+
// src/process-group.js
|
|
8
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
9
|
+
function signalGroup(pid, signal) {
|
|
10
|
+
try {
|
|
11
|
+
process.kill(-pid, signal);
|
|
12
|
+
return true;
|
|
13
|
+
} catch (error) {
|
|
14
|
+
if (error.code === "ESRCH")
|
|
15
|
+
return false;
|
|
16
|
+
if (signal === 0 && error.code === "EPERM")
|
|
17
|
+
return true;
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function waitForExit(pid, timeoutMs) {
|
|
22
|
+
const deadline = Date.now() + timeoutMs;
|
|
23
|
+
while (signalGroup(pid, 0)) {
|
|
24
|
+
if (Date.now() >= deadline)
|
|
25
|
+
return false;
|
|
26
|
+
await delay(25);
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
async function stopProcessGroup(pid) {
|
|
31
|
+
signalGroup(pid, "SIGTERM");
|
|
32
|
+
if (!await waitForExit(pid, 5000)) {
|
|
33
|
+
signalGroup(pid, "SIGKILL");
|
|
34
|
+
if (!await waitForExit(pid, 5000))
|
|
35
|
+
throw new Error(`Hasgard process group ${pid} survived SIGKILL`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
4
38
|
|
|
5
39
|
// src/process-supervisor.js
|
|
6
|
-
import { spawn } from "child_process";
|
|
7
|
-
import { lstatSync, unlinkSync } from "fs";
|
|
8
|
-
import { createConnection } from "net";
|
|
9
|
-
import { setTimeout as delay } from "timers/promises";
|
|
10
40
|
var application;
|
|
11
41
|
var starting = false;
|
|
12
42
|
var stopping;
|
|
@@ -19,7 +49,8 @@ function identity(path) {
|
|
|
19
49
|
const stat = lstatSync(path);
|
|
20
50
|
return { dev: stat.dev, ino: stat.ino, socket: stat.isSocket() };
|
|
21
51
|
} catch (error) {
|
|
22
|
-
if (error.code === "ENOENT")
|
|
52
|
+
if (error.code === "ENOENT")
|
|
53
|
+
return;
|
|
23
54
|
throw error;
|
|
24
55
|
}
|
|
25
56
|
}
|
|
@@ -27,7 +58,8 @@ function sameIdentity(first, second) {
|
|
|
27
58
|
return first && second && first.dev === second.dev && first.ino === second.ino;
|
|
28
59
|
}
|
|
29
60
|
function captureEndpoint() {
|
|
30
|
-
if (ownedEndpoint || !endpoint)
|
|
61
|
+
if (ownedEndpoint || !endpoint)
|
|
62
|
+
return;
|
|
31
63
|
const current = identity(endpoint);
|
|
32
64
|
if (current?.socket && !sameIdentity(current, initialEndpoint)) {
|
|
33
65
|
ownedEndpoint = current;
|
|
@@ -35,7 +67,8 @@ function captureEndpoint() {
|
|
|
35
67
|
}
|
|
36
68
|
}
|
|
37
69
|
async function removeOwnedEndpoint() {
|
|
38
|
-
if (!ownedEndpoint || !sameIdentity(identity(endpoint), ownedEndpoint))
|
|
70
|
+
if (!ownedEndpoint || !sameIdentity(identity(endpoint), ownedEndpoint))
|
|
71
|
+
return;
|
|
39
72
|
const accepting = await new Promise((resolve, reject) => {
|
|
40
73
|
const socket = createConnection({ path: endpoint });
|
|
41
74
|
socket.once("connect", () => {
|
|
@@ -43,10 +76,12 @@ async function removeOwnedEndpoint() {
|
|
|
43
76
|
resolve(true);
|
|
44
77
|
});
|
|
45
78
|
socket.once("error", (error) => {
|
|
46
|
-
if (error.code === "ECONNREFUSED" || error.code === "ENOENT")
|
|
47
|
-
|
|
79
|
+
if (error.code === "ECONNREFUSED" || error.code === "ENOENT")
|
|
80
|
+
resolve(false);
|
|
81
|
+
else
|
|
82
|
+
reject(error);
|
|
48
83
|
});
|
|
49
|
-
socket.setTimeout(
|
|
84
|
+
socket.setTimeout(1000, () => {
|
|
50
85
|
socket.destroy();
|
|
51
86
|
reject(new Error("Endpoint cleanup probe timed out"));
|
|
52
87
|
});
|
|
@@ -55,35 +90,44 @@ async function removeOwnedEndpoint() {
|
|
|
55
90
|
try {
|
|
56
91
|
unlinkSync(endpoint);
|
|
57
92
|
} catch (error) {
|
|
58
|
-
if (error.code !== "ENOENT")
|
|
93
|
+
if (error.code !== "ENOENT")
|
|
94
|
+
throw error;
|
|
59
95
|
}
|
|
60
96
|
}
|
|
61
97
|
}
|
|
62
98
|
process.stderr.on("error", (error) => {
|
|
63
|
-
if (error.code !== "EPIPE")
|
|
99
|
+
if (error.code !== "EPIPE")
|
|
100
|
+
shutdown(1);
|
|
64
101
|
});
|
|
65
102
|
function send(message, stream) {
|
|
66
|
-
if (!process.connected)
|
|
103
|
+
if (!process.connected)
|
|
104
|
+
return;
|
|
67
105
|
const writable = process.send(message, (error) => {
|
|
68
106
|
if (error) {
|
|
69
107
|
if (error.code !== "ERR_IPC_CHANNEL_CLOSED" && error.code !== "EPIPE")
|
|
70
108
|
process.stderr.write(`Hasgard supervisor IPC: ${error.message}
|
|
71
109
|
`);
|
|
72
|
-
if (process.connected)
|
|
73
|
-
|
|
110
|
+
if (process.connected)
|
|
111
|
+
process.disconnect();
|
|
112
|
+
} else
|
|
113
|
+
stream?.resume();
|
|
74
114
|
});
|
|
75
|
-
if (!writable)
|
|
115
|
+
if (!writable)
|
|
116
|
+
stream?.pause();
|
|
76
117
|
}
|
|
77
118
|
process.once("message", (config) => {
|
|
78
|
-
if (!process.connected || stopping)
|
|
119
|
+
if (!process.connected || stopping)
|
|
120
|
+
return;
|
|
79
121
|
starting = true;
|
|
80
122
|
try {
|
|
81
123
|
endpoint = config.env.TAURI_HASGARD_SOCKET;
|
|
82
|
-
if (typeof endpoint !== "string" || !endpoint)
|
|
124
|
+
if (typeof endpoint !== "string" || !endpoint)
|
|
125
|
+
throw new Error("Supervisor requires TAURI_HASGARD_SOCKET");
|
|
83
126
|
initialEndpoint = identity(endpoint);
|
|
84
127
|
endpointWatch = setInterval(captureEndpoint, 25);
|
|
85
128
|
process.on("message", (message) => {
|
|
86
|
-
if (message.type !== "endpoint")
|
|
129
|
+
if (message.type !== "endpoint")
|
|
130
|
+
return;
|
|
87
131
|
const current = identity(endpoint);
|
|
88
132
|
if (!current?.socket || !sameIdentity(current, message.identity)) {
|
|
89
133
|
send({ type: "error", message: "Application endpoint changed during readiness" });
|
|
@@ -109,7 +153,8 @@ process.once("message", (config) => {
|
|
|
109
153
|
}
|
|
110
154
|
});
|
|
111
155
|
process.once("disconnect", () => shutdown(0));
|
|
112
|
-
for (const signal of ["SIGTERM", "SIGINT", "SIGHUP"])
|
|
156
|
+
for (const signal of ["SIGTERM", "SIGINT", "SIGHUP"])
|
|
157
|
+
process.on(signal, () => shutdown(0));
|
|
113
158
|
process.on("uncaughtException", (error) => {
|
|
114
159
|
process.stderr.write(`Hasgard supervisor error: ${error.stack}
|
|
115
160
|
`);
|
|
@@ -121,15 +166,17 @@ process.on("unhandledRejection", (error) => {
|
|
|
121
166
|
shutdown(1);
|
|
122
167
|
});
|
|
123
168
|
function shutdown(exitCode) {
|
|
124
|
-
if (!stopping)
|
|
169
|
+
if (!stopping)
|
|
170
|
+
stopping = cleanup(exitCode);
|
|
125
171
|
}
|
|
126
172
|
async function cleanup(exitCode) {
|
|
127
173
|
clearInterval(endpointWatch);
|
|
128
174
|
const pid = application?.pid;
|
|
129
|
-
if (!starting || pid ===
|
|
175
|
+
if (!starting || pid === undefined)
|
|
176
|
+
process.exit(exitCode);
|
|
130
177
|
let groupStopped = false;
|
|
131
178
|
let endpointFailures = 0;
|
|
132
|
-
for (
|
|
179
|
+
for (;; ) {
|
|
133
180
|
try {
|
|
134
181
|
let endpointError;
|
|
135
182
|
try {
|
|
@@ -141,14 +188,16 @@ async function cleanup(exitCode) {
|
|
|
141
188
|
await stopProcessGroup(pid);
|
|
142
189
|
groupStopped = true;
|
|
143
190
|
}
|
|
144
|
-
if (endpointError)
|
|
191
|
+
if (endpointError)
|
|
192
|
+
throw endpointError;
|
|
145
193
|
await removeOwnedEndpoint();
|
|
146
194
|
process.exit(exitCode);
|
|
147
195
|
} catch (error) {
|
|
148
196
|
process.stderr.write(`Hasgard orphan cleanup: ${error.message}
|
|
149
197
|
`);
|
|
150
|
-
if (groupStopped && ++endpointFailures >= 3)
|
|
151
|
-
|
|
198
|
+
if (groupStopped && ++endpointFailures >= 3)
|
|
199
|
+
process.exit(1);
|
|
200
|
+
await delay2(1000);
|
|
152
201
|
}
|
|
153
202
|
}
|
|
154
203
|
}
|
package/dist/window.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HasgardRpcClient } from "./rpc-client.js";
|
|
1
|
+
import type { HasgardRpcClient } from "./rpc-client.js";
|
|
2
2
|
import type { BoundingBox, ClickOptions, ConsoleLogEntry, ConsoleLogOptions, DialogListing, DialogPolicy, DiffOptions, DragOffset, DropFile, FileInput, FilterOptions, FormsDump, FulfillOptions, HasgardTarget, JsonValue, NativeScreenshot, NativeScreenshotOptions, NetworkRequestEntry, NetworkRequestOptions, PressOptions, QueryDimension, RecorderEntry, RecorderResult, RecorderStatus, RoleQuery, RouteListing, RouteOptions, ScrollOptions, Snapshot, SnapshotDiff, SnapshotOptions, StorageListing, StorageOptions, TextQuery, WaitForFunctionOptions, WaitOptions, WatchChanges, WatchOptions, WindowInfo, WindowState } from "./types.js";
|
|
3
3
|
export declare const byRef: (ref: string) => HasgardTarget;
|
|
4
4
|
export declare const bySelector: (selector: string) => HasgardTarget;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nyssance/tauri-hasgard",
|
|
3
|
-
"version": "0.6.0-alpha.
|
|
3
|
+
"version": "0.6.0-alpha.4",
|
|
4
4
|
"description": "Playwright Test fixtures for native Tauri Hasgard automation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,24 +18,23 @@
|
|
|
18
18
|
"NOTICE.md"
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
|
-
"build": "bun
|
|
21
|
+
"build": "bun run clean && bun build src/index.ts src/process-supervisor.js --target node --format esm --outdir dist --packages external && bun node_modules/typescript/bin/tsc -p tsconfig.json --noEmit false --emitDeclarationOnly --declaration true",
|
|
22
22
|
"dev": "bun run --parallel dev:js dev:types",
|
|
23
23
|
"test": "vitest run",
|
|
24
24
|
"test:watch": "vitest",
|
|
25
|
-
"typecheck": "
|
|
25
|
+
"typecheck": "bun check --no-script",
|
|
26
26
|
"clean": "rm -rf dist",
|
|
27
|
-
"dev:js": "
|
|
27
|
+
"dev:js": "bun build src/index.ts src/process-supervisor.js --target node --format esm --outdir dist --packages external --watch",
|
|
28
28
|
"dev:types": "tsc -p tsconfig.json --noEmit false --emitDeclarationOnly --declaration true --watch"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"@playwright/test": ">=1.61.1 <2"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@playwright/test": "1.
|
|
35
|
-
"@types/node": "^26.
|
|
36
|
-
"tsup": "^8.5.1",
|
|
34
|
+
"@playwright/test": "1.64.1",
|
|
35
|
+
"@types/node": "^26.6.5",
|
|
37
36
|
"typescript": "^7.0.2",
|
|
38
|
-
"vitest": "^
|
|
37
|
+
"vitest": "^5.0.5"
|
|
39
38
|
},
|
|
40
39
|
"keywords": [
|
|
41
40
|
"tauri",
|
package/dist/chunk-N7YDYAKI.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// src/process-group.js
|
|
2
|
-
import { setTimeout as delay } from "timers/promises";
|
|
3
|
-
function signalGroup(pid, signal) {
|
|
4
|
-
try {
|
|
5
|
-
process.kill(-pid, signal);
|
|
6
|
-
return true;
|
|
7
|
-
} catch (error) {
|
|
8
|
-
if (error.code === "ESRCH") return false;
|
|
9
|
-
if (signal === 0 && error.code === "EPERM") return true;
|
|
10
|
-
throw error;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
async function waitForExit(pid, timeoutMs) {
|
|
14
|
-
const deadline = Date.now() + timeoutMs;
|
|
15
|
-
while (signalGroup(pid, 0)) {
|
|
16
|
-
if (Date.now() >= deadline) return false;
|
|
17
|
-
await delay(25);
|
|
18
|
-
}
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
async function stopProcessGroup(pid) {
|
|
22
|
-
signalGroup(pid, "SIGTERM");
|
|
23
|
-
if (!await waitForExit(pid, 5e3)) {
|
|
24
|
-
signalGroup(pid, "SIGKILL");
|
|
25
|
-
if (!await waitForExit(pid, 5e3)) throw new Error(`Hasgard process group ${pid} survived SIGKILL`);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export {
|
|
30
|
-
stopProcessGroup
|
|
31
|
-
};
|