@yoqa/runner 0.3.19 → 0.3.21
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/index.js +140 -36
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16801,7 +16801,7 @@ function getCatalogDb() {
|
|
|
16801
16801
|
// package.json
|
|
16802
16802
|
var package_default = {
|
|
16803
16803
|
name: "@yoqa/runner",
|
|
16804
|
-
version: "0.3.
|
|
16804
|
+
version: "0.3.21",
|
|
16805
16805
|
description: "Yoqa local runner \u2014 Appium + catalog HTTP API (Bun). Start with yoqa serve or the yoqa-runner bin.",
|
|
16806
16806
|
license: "UNLICENSED",
|
|
16807
16807
|
type: "module",
|
|
@@ -17353,10 +17353,6 @@ var MJPEG_SETTINGS_BASE = {
|
|
|
17353
17353
|
mjpegScalingFactor: 50
|
|
17354
17354
|
};
|
|
17355
17355
|
var openByDeviceId = new Map;
|
|
17356
|
-
var exclusiveReleaseListener = null;
|
|
17357
|
-
function onDeviceSessionExclusiveRelease(listener) {
|
|
17358
|
-
exclusiveReleaseListener = listener;
|
|
17359
|
-
}
|
|
17360
17356
|
var DEAD_SESSION_RE = /session does not exist|invalid session id|no such session|terminated or not started|session is either terminated/i;
|
|
17361
17357
|
|
|
17362
17358
|
class DeadSessionError extends Error {
|
|
@@ -17441,7 +17437,6 @@ async function releaseExistingSession(deviceId) {
|
|
|
17441
17437
|
if (!existing)
|
|
17442
17438
|
return;
|
|
17443
17439
|
openByDeviceId.delete(deviceId);
|
|
17444
|
-
exclusiveReleaseListener?.(deviceId);
|
|
17445
17440
|
try {
|
|
17446
17441
|
await existing.quit();
|
|
17447
17442
|
} catch (error) {
|
|
@@ -17975,21 +17970,32 @@ async function createDeviceSession(options) {
|
|
|
17975
17970
|
|
|
17976
17971
|
// src/domains/devices/active-session.ts
|
|
17977
17972
|
var active = null;
|
|
17973
|
+
|
|
17974
|
+
class SessionBusyError extends Error {
|
|
17975
|
+
constructor(message) {
|
|
17976
|
+
super(message);
|
|
17977
|
+
this.name = "SessionBusyError";
|
|
17978
|
+
}
|
|
17979
|
+
}
|
|
17978
17980
|
var isMissingAppiumSessionError = isDeadSessionError;
|
|
17981
|
+
function toInfo(current) {
|
|
17982
|
+
return {
|
|
17983
|
+
deviceId: current.deviceId,
|
|
17984
|
+
platform: current.platform,
|
|
17985
|
+
connectedAt: current.connectedAt,
|
|
17986
|
+
mjpegPort: current.mjpegPort,
|
|
17987
|
+
streamReady: current.streamReady,
|
|
17988
|
+
streamUrl: current.streamUrl,
|
|
17989
|
+
heldByRun: current.heldByRunId != null
|
|
17990
|
+
};
|
|
17991
|
+
}
|
|
17979
17992
|
function getActiveSession() {
|
|
17980
17993
|
return active;
|
|
17981
17994
|
}
|
|
17982
17995
|
function getActiveSessionInfo() {
|
|
17983
17996
|
if (!active)
|
|
17984
17997
|
return null;
|
|
17985
|
-
return
|
|
17986
|
-
deviceId: active.deviceId,
|
|
17987
|
-
platform: active.platform,
|
|
17988
|
-
connectedAt: active.connectedAt,
|
|
17989
|
-
mjpegPort: active.mjpegPort,
|
|
17990
|
-
streamReady: active.streamReady,
|
|
17991
|
-
streamUrl: active.streamUrl
|
|
17992
|
-
};
|
|
17998
|
+
return toInfo(active);
|
|
17993
17999
|
}
|
|
17994
18000
|
function requireActiveSession() {
|
|
17995
18001
|
if (!active) {
|
|
@@ -17997,6 +18003,9 @@ function requireActiveSession() {
|
|
|
17997
18003
|
}
|
|
17998
18004
|
return active;
|
|
17999
18005
|
}
|
|
18006
|
+
function isActiveSessionHeldByRun() {
|
|
18007
|
+
return active?.heldByRunId != null;
|
|
18008
|
+
}
|
|
18000
18009
|
function abandonActiveSession() {
|
|
18001
18010
|
if (!active)
|
|
18002
18011
|
return null;
|
|
@@ -18006,20 +18015,12 @@ function abandonActiveSession() {
|
|
|
18006
18015
|
console.warn(`[yoqa-runner] abandoned dead Appium session for ${info?.platform} ${info?.deviceId}`);
|
|
18007
18016
|
return info;
|
|
18008
18017
|
}
|
|
18009
|
-
|
|
18010
|
-
if (active?.deviceId === deviceId) {
|
|
18011
|
-
abandonActiveSession();
|
|
18012
|
-
}
|
|
18013
|
-
});
|
|
18014
|
-
async function connectDevice(options) {
|
|
18015
|
-
if (active) {
|
|
18016
|
-
await disconnectDevice();
|
|
18017
|
-
}
|
|
18018
|
+
async function createAndRegister(options) {
|
|
18018
18019
|
const session = await createDeviceSession({
|
|
18019
18020
|
platform: options.platform,
|
|
18020
18021
|
deviceId: options.deviceId,
|
|
18021
|
-
appCaps: [],
|
|
18022
|
-
caseCaps: [],
|
|
18022
|
+
appCaps: options.appCaps ?? [],
|
|
18023
|
+
caseCaps: options.caseCaps ?? [],
|
|
18023
18024
|
bundleId: options.bundleId,
|
|
18024
18025
|
appPackage: options.appPackage,
|
|
18025
18026
|
onSessionDead: () => {
|
|
@@ -18033,8 +18034,19 @@ async function connectDevice(options) {
|
|
|
18033
18034
|
mjpegPort: session.mjpegPort,
|
|
18034
18035
|
streamReady: session.streamReady,
|
|
18035
18036
|
streamUrl: "/stream.mjpeg",
|
|
18036
|
-
session
|
|
18037
|
+
session,
|
|
18038
|
+
heldByRunId: options.heldByRunId
|
|
18037
18039
|
};
|
|
18040
|
+
return session;
|
|
18041
|
+
}
|
|
18042
|
+
async function connectDevice(options) {
|
|
18043
|
+
if (isActiveSessionHeldByRun()) {
|
|
18044
|
+
throw new SessionBusyError("A run is using the device session. Cancel the run before connecting another device.");
|
|
18045
|
+
}
|
|
18046
|
+
if (active) {
|
|
18047
|
+
await disconnectDevice();
|
|
18048
|
+
}
|
|
18049
|
+
await createAndRegister({ ...options, heldByRunId: null });
|
|
18038
18050
|
const info = getActiveSessionInfo();
|
|
18039
18051
|
if (!info) {
|
|
18040
18052
|
throw new Error("Failed to read active session after connect");
|
|
@@ -18044,6 +18056,9 @@ async function connectDevice(options) {
|
|
|
18044
18056
|
async function disconnectDevice() {
|
|
18045
18057
|
if (!active)
|
|
18046
18058
|
return null;
|
|
18059
|
+
if (isActiveSessionHeldByRun()) {
|
|
18060
|
+
throw new SessionBusyError("A run is using the device session. Cancel the run first.");
|
|
18061
|
+
}
|
|
18047
18062
|
const info = getActiveSessionInfo();
|
|
18048
18063
|
const session = active.session;
|
|
18049
18064
|
active = null;
|
|
@@ -18053,6 +18068,58 @@ async function disconnectDevice() {
|
|
|
18053
18068
|
} catch {}
|
|
18054
18069
|
return info;
|
|
18055
18070
|
}
|
|
18071
|
+
async function acquireSessionForRun(options) {
|
|
18072
|
+
const current = active;
|
|
18073
|
+
if (current?.heldByRunId && current.heldByRunId !== options.runId) {
|
|
18074
|
+
const session2 = await createDeviceSession({
|
|
18075
|
+
platform: options.platform,
|
|
18076
|
+
deviceId: options.deviceId,
|
|
18077
|
+
appCaps: options.appCaps ?? [],
|
|
18078
|
+
caseCaps: options.caseCaps ?? [],
|
|
18079
|
+
bundleId: options.bundleId,
|
|
18080
|
+
appPackage: options.appPackage,
|
|
18081
|
+
onSessionDead: () => {
|
|
18082
|
+
return;
|
|
18083
|
+
}
|
|
18084
|
+
});
|
|
18085
|
+
return { session: session2, shared: false };
|
|
18086
|
+
}
|
|
18087
|
+
if (current && current.deviceId === options.deviceId) {
|
|
18088
|
+
const healthy = await current.session.getWindowSize().then(() => true).catch(() => false);
|
|
18089
|
+
if (healthy) {
|
|
18090
|
+
current.heldByRunId = options.runId;
|
|
18091
|
+
return { session: current.session, shared: true };
|
|
18092
|
+
}
|
|
18093
|
+
console.warn(`[yoqa-runner] active session for ${options.deviceId} is dead \u2014 creating a fresh one for the run`);
|
|
18094
|
+
await disconnectDevice().catch(() => {
|
|
18095
|
+
return;
|
|
18096
|
+
});
|
|
18097
|
+
}
|
|
18098
|
+
if (current) {
|
|
18099
|
+
await disconnectDevice();
|
|
18100
|
+
}
|
|
18101
|
+
const session = await createAndRegister({
|
|
18102
|
+
deviceId: options.deviceId,
|
|
18103
|
+
platform: options.platform,
|
|
18104
|
+
appCaps: options.appCaps,
|
|
18105
|
+
caseCaps: options.caseCaps,
|
|
18106
|
+
bundleId: options.bundleId,
|
|
18107
|
+
appPackage: options.appPackage,
|
|
18108
|
+
heldByRunId: options.runId
|
|
18109
|
+
});
|
|
18110
|
+
return { session, shared: true };
|
|
18111
|
+
}
|
|
18112
|
+
async function releaseSessionFromRun(runId, session, shared) {
|
|
18113
|
+
if (!shared) {
|
|
18114
|
+
await session.quit().catch(() => {
|
|
18115
|
+
return;
|
|
18116
|
+
});
|
|
18117
|
+
return;
|
|
18118
|
+
}
|
|
18119
|
+
if (active && active.heldByRunId === runId) {
|
|
18120
|
+
active.heldByRunId = null;
|
|
18121
|
+
}
|
|
18122
|
+
}
|
|
18056
18123
|
|
|
18057
18124
|
// src/domains/servers/application.ts
|
|
18058
18125
|
function appiumLabel(info) {
|
|
@@ -24100,6 +24167,8 @@ var updateCaseRequestSchema = exports_external.object({
|
|
|
24100
24167
|
capabilities: exports_external.array(capabilitySchema).optional(),
|
|
24101
24168
|
script: caseScriptSchema.nullable().optional()
|
|
24102
24169
|
});
|
|
24170
|
+
var createCaseFlowsSchema = exports_external.array(createCaseRequestSchema.shape.flows.unwrap().element.strict());
|
|
24171
|
+
var updateCaseFlowsSchema = exports_external.array(updateCaseRequestSchema.shape.flows.unwrap().element.strict());
|
|
24103
24172
|
var catalogFlowSchema = exports_external.object({
|
|
24104
24173
|
id: exports_external.string().min(1),
|
|
24105
24174
|
appId: exports_external.string().min(1),
|
|
@@ -24351,7 +24420,8 @@ var activeDeviceResponseSchema = exports_external.object({
|
|
|
24351
24420
|
connectedAt: exports_external.number().int().nonnegative(),
|
|
24352
24421
|
mjpegPort: exports_external.number().int().positive().optional(),
|
|
24353
24422
|
streamReady: exports_external.boolean().optional(),
|
|
24354
|
-
streamUrl: exports_external.string().min(1).optional()
|
|
24423
|
+
streamUrl: exports_external.string().min(1).optional(),
|
|
24424
|
+
heldByRun: exports_external.boolean().optional()
|
|
24355
24425
|
});
|
|
24356
24426
|
var controlPointerMessageSchema = exports_external.object({
|
|
24357
24427
|
type: exports_external.literal("pointer"),
|
|
@@ -98329,6 +98399,7 @@ async function executeRun(runId) {
|
|
|
98329
98399
|
return;
|
|
98330
98400
|
registerControl(runId);
|
|
98331
98401
|
let session = null;
|
|
98402
|
+
let sharedSession = true;
|
|
98332
98403
|
try {
|
|
98333
98404
|
if (isAborted2(runId)) {
|
|
98334
98405
|
await persistCancelled(runId);
|
|
@@ -98369,14 +98440,17 @@ async function executeRun(runId) {
|
|
|
98369
98440
|
}
|
|
98370
98441
|
}
|
|
98371
98442
|
const firstCase = run.tests[0] ? await getCase(run.tests[0].caseId) : null;
|
|
98372
|
-
|
|
98373
|
-
|
|
98443
|
+
const acquired = await acquireSessionForRun({
|
|
98444
|
+
runId,
|
|
98374
98445
|
deviceId: run.deviceId,
|
|
98446
|
+
platform: run.platform,
|
|
98375
98447
|
appCaps: app.capabilities,
|
|
98376
98448
|
caseCaps: firstCase?.capabilities ?? [],
|
|
98377
98449
|
bundleId: app.iosBundleId || undefined,
|
|
98378
98450
|
appPackage: app.androidApplicationId || undefined
|
|
98379
98451
|
});
|
|
98452
|
+
session = acquired.session;
|
|
98453
|
+
sharedSession = acquired.shared;
|
|
98380
98454
|
if (isAborted2(runId)) {
|
|
98381
98455
|
await persistCancelled(runId);
|
|
98382
98456
|
return;
|
|
@@ -98451,14 +98525,15 @@ async function executeRun(runId) {
|
|
|
98451
98525
|
}
|
|
98452
98526
|
} finally {
|
|
98453
98527
|
if (session) {
|
|
98454
|
-
await
|
|
98455
|
-
return;
|
|
98456
|
-
});
|
|
98528
|
+
await releaseSessionFromRun(runId, session, sharedSession);
|
|
98457
98529
|
}
|
|
98458
98530
|
clearControl(runId);
|
|
98459
98531
|
}
|
|
98460
98532
|
}
|
|
98461
98533
|
async function createRun(input) {
|
|
98534
|
+
if (runControls.size > 0) {
|
|
98535
|
+
throw new RunValidationError("A run is already in progress. Cancel it before starting another.");
|
|
98536
|
+
}
|
|
98462
98537
|
const uniqueCaseIds = [...new Set(input.caseIds)];
|
|
98463
98538
|
if (uniqueCaseIds.length === 0) {
|
|
98464
98539
|
throw new RunValidationError("At least one case id is required");
|
|
@@ -98816,6 +98891,12 @@ import { mkdir as mkdir4, rename as rename2 } from "fs/promises";
|
|
|
98816
98891
|
import { dirname as dirname4 } from "path";
|
|
98817
98892
|
function sessionErrorResponse(error40) {
|
|
98818
98893
|
const message = error40 instanceof Error ? error40.message : String(error40);
|
|
98894
|
+
if (error40 instanceof SessionBusyError) {
|
|
98895
|
+
return {
|
|
98896
|
+
status: 409,
|
|
98897
|
+
body: { error: error40.message }
|
|
98898
|
+
};
|
|
98899
|
+
}
|
|
98819
98900
|
if (isMissingAppiumSessionError(error40)) {
|
|
98820
98901
|
abandonActiveSession();
|
|
98821
98902
|
return {
|
|
@@ -98845,6 +98926,9 @@ function createSessionRoutes() {
|
|
|
98845
98926
|
const info = await connectDevice(parsed.data);
|
|
98846
98927
|
return c.json(activeDeviceResponseSchema.parse(info));
|
|
98847
98928
|
} catch (error40) {
|
|
98929
|
+
const mapped = sessionErrorResponse(error40);
|
|
98930
|
+
if (mapped)
|
|
98931
|
+
return c.json(mapped.body, mapped.status);
|
|
98848
98932
|
const message = error40 instanceof Error ? error40.message : String(error40);
|
|
98849
98933
|
return c.json({ error: "Failed to connect device", detail: message }, 500);
|
|
98850
98934
|
}
|
|
@@ -98857,11 +98941,19 @@ function createSessionRoutes() {
|
|
|
98857
98941
|
return c.json(activeDeviceResponseSchema.parse(info));
|
|
98858
98942
|
});
|
|
98859
98943
|
app.post("/devices/disconnect", async (c) => {
|
|
98860
|
-
|
|
98861
|
-
|
|
98862
|
-
|
|
98944
|
+
try {
|
|
98945
|
+
const info = await disconnectDevice();
|
|
98946
|
+
if (!info) {
|
|
98947
|
+
return c.json({ error: "No active device session" }, 404);
|
|
98948
|
+
}
|
|
98949
|
+
return c.json(activeDeviceResponseSchema.parse(info));
|
|
98950
|
+
} catch (error40) {
|
|
98951
|
+
const mapped = sessionErrorResponse(error40);
|
|
98952
|
+
if (mapped)
|
|
98953
|
+
return c.json(mapped.body, mapped.status);
|
|
98954
|
+
const message = error40 instanceof Error ? error40.message : String(error40);
|
|
98955
|
+
return c.json({ error: "Failed to disconnect device", detail: message }, 500);
|
|
98863
98956
|
}
|
|
98864
|
-
return c.json(activeDeviceResponseSchema.parse(info));
|
|
98865
98957
|
});
|
|
98866
98958
|
app.get("/screen", async (c) => {
|
|
98867
98959
|
try {
|
|
@@ -98982,6 +99074,11 @@ function createSessionRoutes() {
|
|
|
98982
99074
|
}
|
|
98983
99075
|
try {
|
|
98984
99076
|
const { session } = requireActiveSession();
|
|
99077
|
+
if (isActiveSessionHeldByRun()) {
|
|
99078
|
+
return c.json({
|
|
99079
|
+
error: "A run is using this device session. Cancel the run to interact manually."
|
|
99080
|
+
}, 409);
|
|
99081
|
+
}
|
|
98985
99082
|
const result = await performAction(session, parsed.data);
|
|
98986
99083
|
return c.json(actionResponseSchema.parse(result));
|
|
98987
99084
|
} catch (error40) {
|
|
@@ -99110,6 +99207,13 @@ var controlWebSocket = {
|
|
|
99110
99207
|
return;
|
|
99111
99208
|
}
|
|
99112
99209
|
const msg = result.data;
|
|
99210
|
+
if (isActiveSessionHeldByRun()) {
|
|
99211
|
+
ws.send(JSON.stringify({
|
|
99212
|
+
ok: false,
|
|
99213
|
+
error: "A run is using this device session. Cancel the run to interact manually."
|
|
99214
|
+
}));
|
|
99215
|
+
return;
|
|
99216
|
+
}
|
|
99113
99217
|
try {
|
|
99114
99218
|
await active2.session.pointerEvent(msg.phase, msg.x, msg.y, msg.seq);
|
|
99115
99219
|
if (msg.phase === "end" || msg.phase === "begin") {
|
package/package.json
CHANGED