@allwright.dev/core 0.0.36 → 0.0.38
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/dist/bootstrap.d.ts +1 -1
- package/dist/bootstrap.js +139 -25
- package/dist/runtime.js +2 -2
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
- package/proto/core/v1/common.proto +1 -0
package/dist/bootstrap.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function ensureRuntimeReady(serverAddr: string): Promise<
|
|
1
|
+
export declare function ensureRuntimeReady(serverAddr: string): Promise<string>;
|
|
2
2
|
export declare function shutdownManagedServer(): Promise<void>;
|
package/dist/bootstrap.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import net from "node:net";
|
|
2
3
|
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { spawn, spawnSync } from "node:child_process";
|
|
@@ -18,24 +19,42 @@ const PROTO_ROOT = fileURLToPath(new URL("../proto/", import.meta.url));
|
|
|
18
19
|
const ENGINE_PROTO_PATH = fileURLToPath(new URL("../proto/engine/v1/engine.proto", import.meta.url));
|
|
19
20
|
let managedServer = null;
|
|
20
21
|
let managedServerAddr = null;
|
|
22
|
+
let managedServerBaseAddr = null;
|
|
21
23
|
export async function ensureRuntimeReady(serverAddr) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
const expectedVersion = expectedRuntimeVersion();
|
|
25
|
+
const status = await pingServer(serverAddr);
|
|
26
|
+
if (status) {
|
|
27
|
+
if (status.version === expectedVersion) {
|
|
28
|
+
return serverAddr;
|
|
29
|
+
}
|
|
30
|
+
if (!isLocalServerAddr(serverAddr)) {
|
|
31
|
+
throw new Error(`allwright server at ${serverAddr} is running version ${displayVersion(status.version)} but this client expects ${expectedVersion}`);
|
|
32
|
+
}
|
|
24
33
|
}
|
|
25
34
|
if (!isLocalServerAddr(serverAddr)) {
|
|
26
35
|
throw new Error(`allwright could not reach engine server at ${serverAddr}. Automatic startup is only supported for local addresses.`);
|
|
27
36
|
}
|
|
28
|
-
if (managedServer && !managedServer.killed && managedServer.exitCode === null &&
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
if (managedServer && !managedServer.killed && managedServer.exitCode === null && managedServerBaseAddr === serverAddr && managedServerAddr) {
|
|
38
|
+
return waitForServer(managedServerAddr, expectedVersion);
|
|
39
|
+
}
|
|
40
|
+
if (managedServer && !managedServer.killed && managedServer.exitCode === null) {
|
|
41
|
+
managedServer.kill("SIGTERM");
|
|
42
|
+
managedServer = null;
|
|
43
|
+
managedServerAddr = null;
|
|
44
|
+
managedServerBaseAddr = null;
|
|
45
|
+
}
|
|
46
|
+
const cliPath = await ensureCliAvailable(expectedVersion);
|
|
47
|
+
ensureWebPlugin(cliPath, expectedVersion);
|
|
48
|
+
let resolvedServerAddr = serverAddr;
|
|
49
|
+
if (status && status.version !== expectedVersion) {
|
|
50
|
+
resolvedServerAddr = await allocateManagedServerAddr(serverAddr);
|
|
31
51
|
}
|
|
32
|
-
|
|
33
|
-
ensureWebPlugin(cliPath);
|
|
34
|
-
managedServer = spawn(cliPath, ["serve", "--listen-addr", cliListenAddr(serverAddr)], {
|
|
52
|
+
managedServer = spawn(cliPath, ["serve", "--listen-addr", cliListenAddr(resolvedServerAddr)], {
|
|
35
53
|
stdio: "ignore",
|
|
36
54
|
});
|
|
37
|
-
managedServerAddr =
|
|
38
|
-
|
|
55
|
+
managedServerAddr = resolvedServerAddr;
|
|
56
|
+
managedServerBaseAddr = serverAddr;
|
|
57
|
+
return waitForServer(resolvedServerAddr, expectedVersion);
|
|
39
58
|
}
|
|
40
59
|
export async function shutdownManagedServer() {
|
|
41
60
|
if (managedServer && managedServer.exitCode === null && !managedServer.killed) {
|
|
@@ -43,17 +62,19 @@ export async function shutdownManagedServer() {
|
|
|
43
62
|
}
|
|
44
63
|
managedServer = null;
|
|
45
64
|
managedServerAddr = null;
|
|
65
|
+
managedServerBaseAddr = null;
|
|
46
66
|
}
|
|
47
|
-
async function waitForServer(serverAddr) {
|
|
67
|
+
async function waitForServer(serverAddr, expectedVersion) {
|
|
48
68
|
const deadline = Date.now() + STARTUP_TIMEOUT_MS;
|
|
49
69
|
while (Date.now() < deadline) {
|
|
50
|
-
|
|
51
|
-
|
|
70
|
+
const status = await pingServer(serverAddr);
|
|
71
|
+
if (status?.version === expectedVersion) {
|
|
72
|
+
return serverAddr;
|
|
52
73
|
}
|
|
53
74
|
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
54
75
|
}
|
|
55
76
|
await shutdownManagedServer();
|
|
56
|
-
throw new Error(`timed out waiting for allwright server at ${serverAddr} to become ready`);
|
|
77
|
+
throw new Error(`timed out waiting for allwright server at ${serverAddr} to become ready with version ${expectedVersion}`);
|
|
57
78
|
}
|
|
58
79
|
async function pingServer(serverAddr) {
|
|
59
80
|
const loaded = protoLoader.loadSync(ENGINE_PROTO_PATH, {
|
|
@@ -67,23 +88,31 @@ async function pingServer(serverAddr) {
|
|
|
67
88
|
const proto = grpc.loadPackageDefinition(loaded);
|
|
68
89
|
const client = new proto.allwright.engine.v1.EngineService(serverAddr, grpc.credentials.createInsecure());
|
|
69
90
|
return await new Promise((resolve) => {
|
|
70
|
-
client.Ping({}, new grpc.Metadata(), { deadline: new Date(Date.now() + PING_TIMEOUT_MS) }, (error) => {
|
|
91
|
+
client.Ping({}, new grpc.Metadata(), { deadline: new Date(Date.now() + PING_TIMEOUT_MS) }, (error, response) => {
|
|
71
92
|
client.close();
|
|
72
|
-
|
|
93
|
+
if (error) {
|
|
94
|
+
resolve(null);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
resolve({ version: normalizeReleaseVersion(response.version ?? "") });
|
|
73
98
|
});
|
|
74
99
|
});
|
|
75
100
|
}
|
|
76
|
-
async function ensureCliAvailable() {
|
|
101
|
+
async function ensureCliAvailable(expectedVersion) {
|
|
77
102
|
const envPath = process.env[ALLWRIGHT_CLI_PATH_ENV_VAR]?.trim();
|
|
78
|
-
if (envPath && isFile(envPath)) {
|
|
103
|
+
if (envPath && isFile(envPath) && cliVersionMatches(envPath, expectedVersion)) {
|
|
79
104
|
return envPath;
|
|
80
105
|
}
|
|
81
106
|
const bundled = path.join(allwrightHome(), "bin", cliFilename());
|
|
82
|
-
if (isFile(bundled)) {
|
|
107
|
+
if (isFile(bundled) && cliVersionMatches(bundled, expectedVersion)) {
|
|
83
108
|
return bundled;
|
|
84
109
|
}
|
|
110
|
+
const repoLocal = repoLocalCliPath();
|
|
111
|
+
if (repoLocal && cliVersionMatches(repoLocal, expectedVersion)) {
|
|
112
|
+
return repoLocal;
|
|
113
|
+
}
|
|
85
114
|
const fromPath = resolveFromPath(cliFilename());
|
|
86
|
-
if (fromPath) {
|
|
115
|
+
if (fromPath && cliVersionMatches(fromPath, expectedVersion)) {
|
|
87
116
|
return fromPath;
|
|
88
117
|
}
|
|
89
118
|
if (!autoInstallEnabled()) {
|
|
@@ -108,18 +137,20 @@ async function installCli() {
|
|
|
108
137
|
fs.rmSync(assetPath, { force: true });
|
|
109
138
|
return cliPath;
|
|
110
139
|
}
|
|
111
|
-
function ensureWebPlugin(cliPath) {
|
|
140
|
+
function ensureWebPlugin(cliPath, expectedVersion) {
|
|
112
141
|
const pluginPath = path.join(allwrightHome(), "plugins", "web", "lib", webPluginFilename());
|
|
113
|
-
if (isFile(pluginPath)) {
|
|
142
|
+
if (isFile(pluginPath) && installedPluginVersion("web") === expectedVersion) {
|
|
114
143
|
return;
|
|
115
144
|
}
|
|
116
|
-
const
|
|
117
|
-
const result = spawnSync(cliPath, ["plugin", "install", "web", "--version", normalizeReleaseVersion(version)], {
|
|
145
|
+
const result = spawnSync(cliPath, ["plugin", "install", "web", "--version", expectedVersion], {
|
|
118
146
|
stdio: "ignore",
|
|
119
147
|
});
|
|
120
148
|
if (result.status !== 0 || !isFile(pluginPath)) {
|
|
121
149
|
throw new Error("allwright attempted to install the `web` plugin automatically, but the install did not complete successfully");
|
|
122
150
|
}
|
|
151
|
+
if (installedPluginVersion("web") !== expectedVersion) {
|
|
152
|
+
throw new Error(`allwright attempted to install the \`web\` plugin automatically, but version ${expectedVersion} is still not active`);
|
|
153
|
+
}
|
|
123
154
|
}
|
|
124
155
|
async function resolveReleaseTag() {
|
|
125
156
|
const version = process.env[ALLWRIGHT_VERSION_ENV_VAR]?.trim() || DEFAULT_RELEASE_VERSION;
|
|
@@ -189,13 +220,96 @@ function normalizeReleaseTag(version) {
|
|
|
189
220
|
function normalizeReleaseVersion(version) {
|
|
190
221
|
return version.replace(/^v/, "");
|
|
191
222
|
}
|
|
223
|
+
function expectedRuntimeVersion() {
|
|
224
|
+
return normalizeReleaseVersion(process.env[ALLWRIGHT_VERSION_ENV_VAR]?.trim() || DEFAULT_RELEASE_VERSION);
|
|
225
|
+
}
|
|
192
226
|
function cliListenAddr(serverAddr) {
|
|
193
227
|
return serverAddr.replace(/^https?:\/\//, "");
|
|
194
228
|
}
|
|
195
229
|
function isLocalServerAddr(serverAddr) {
|
|
196
|
-
const host =
|
|
230
|
+
const host = parseServerHost(serverAddr);
|
|
197
231
|
return host === "127.0.0.1" || host === "localhost" || host === "::1";
|
|
198
232
|
}
|
|
233
|
+
function installedPluginVersion(pluginId) {
|
|
234
|
+
const manifestPath = path.join(allwrightHome(), "plugins.txt");
|
|
235
|
+
if (!isFile(manifestPath)) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
for (const line of fs.readFileSync(manifestPath, "utf8").split(/\r?\n/)) {
|
|
239
|
+
const trimmed = line.trim();
|
|
240
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const [id, , version] = trimmed.split("\t", 3);
|
|
244
|
+
if (id === pluginId && version) {
|
|
245
|
+
return normalizeReleaseVersion(version);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
async function allocateManagedServerAddr(serverAddr) {
|
|
251
|
+
const host = localBindingHost(serverAddr);
|
|
252
|
+
const port = await new Promise((resolve, reject) => {
|
|
253
|
+
const server = net.createServer();
|
|
254
|
+
server.unref();
|
|
255
|
+
server.on("error", reject);
|
|
256
|
+
server.listen(0, host, () => {
|
|
257
|
+
const address = server.address();
|
|
258
|
+
if (!address || typeof address === "string") {
|
|
259
|
+
server.close(() => reject(new Error("failed to reserve a local port for allwright")));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const { port } = address;
|
|
263
|
+
server.close((error) => {
|
|
264
|
+
if (error) {
|
|
265
|
+
reject(error);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
resolve(port);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
return host.includes(":") ? `http://[${host}]:${port}` : `http://${host}:${port}`;
|
|
273
|
+
}
|
|
274
|
+
function localBindingHost(serverAddr) {
|
|
275
|
+
const host = parseServerHost(serverAddr);
|
|
276
|
+
return host === "::1" ? "::1" : "127.0.0.1";
|
|
277
|
+
}
|
|
278
|
+
function displayVersion(version) {
|
|
279
|
+
return version || "unknown";
|
|
280
|
+
}
|
|
281
|
+
function repoLocalCliPath() {
|
|
282
|
+
const repoRoot = fileURLToPath(new URL("../../..", import.meta.url));
|
|
283
|
+
for (const candidate of [
|
|
284
|
+
path.join(repoRoot, "target", "debug", cliFilename()),
|
|
285
|
+
path.join(repoRoot, "target", "release", cliFilename()),
|
|
286
|
+
]) {
|
|
287
|
+
if (isFile(candidate)) {
|
|
288
|
+
return candidate;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
function cliVersionMatches(cliPath, expectedVersion) {
|
|
294
|
+
const result = spawnSync(cliPath, ["--version"], { stdio: ["ignore", "pipe", "ignore"], encoding: "utf8" });
|
|
295
|
+
if (result.status !== 0) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
for (const token of result.stdout.split(/\s+/)) {
|
|
299
|
+
if (/^\d/.test(token)) {
|
|
300
|
+
return normalizeReleaseVersion(token) === expectedVersion;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
function parseServerHost(serverAddr) {
|
|
306
|
+
const listenAddr = cliListenAddr(serverAddr);
|
|
307
|
+
const ipv6Match = listenAddr.match(/^\[([^\]]+)\]/);
|
|
308
|
+
if (ipv6Match) {
|
|
309
|
+
return ipv6Match[1] ?? "";
|
|
310
|
+
}
|
|
311
|
+
return listenAddr.split(":")[0] ?? "";
|
|
312
|
+
}
|
|
199
313
|
function allwrightHome() {
|
|
200
314
|
return process.env[ALLWRIGHT_HOME_ENV_VAR]?.trim() || path.join(os.homedir(), ".allwright");
|
|
201
315
|
}
|
package/dist/runtime.js
CHANGED
|
@@ -78,7 +78,7 @@ export function resolveLaunchBrowserArgs(browserKindOrOptions) {
|
|
|
78
78
|
}
|
|
79
79
|
async function createRuntime() {
|
|
80
80
|
const serverAddr = configuredServerAddr();
|
|
81
|
-
await ensureRuntimeReady(serverAddr);
|
|
81
|
+
const resolvedServerAddr = await ensureRuntimeReady(serverAddr);
|
|
82
82
|
const loaded = protoLoader.loadSync(ENGINE_PROTO_PATH, {
|
|
83
83
|
includeDirs: [PROTO_ROOT],
|
|
84
84
|
keepCase: false,
|
|
@@ -89,7 +89,7 @@ async function createRuntime() {
|
|
|
89
89
|
});
|
|
90
90
|
const proto = grpc.loadPackageDefinition(loaded);
|
|
91
91
|
const ClientCtor = proto.allwright.engine.v1.EngineService;
|
|
92
|
-
const client = new ClientCtor(
|
|
92
|
+
const client = new ClientCtor(resolvedServerAddr, grpc.credentials.createInsecure());
|
|
93
93
|
return { client };
|
|
94
94
|
}
|
|
95
95
|
function configuredServerAddr() {
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED