@yoqa/runner 1.0.6 → 1.0.7

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.
Files changed (2) hide show
  1. package/dist/index.js +141 -37
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -27918,6 +27918,8 @@ function installedBrowserId(browser) {
27918
27918
  function everydayBrowserId(browser) {
27919
27919
  return `web-${browser}-everyday`;
27920
27920
  }
27921
+ var REMOTE_DEBUGGING_SETTING_URL = "chrome://inspect/#remote-debugging";
27922
+ var EVERYDAY_ALLOW_TIMEOUT_MS = 60000;
27921
27923
  function webBrowserOf(deviceId) {
27922
27924
  return WEB_BROWSERS.find(({ browser }) => deviceId === installedBrowserId(browser) || deviceId === everydayBrowserId(browser));
27923
27925
  }
@@ -28149,7 +28151,9 @@ async function launchChromium(browser, deps = {}, request = {}) {
28149
28151
  }
28150
28152
 
28151
28153
  // src/domains/devices/chromium-everyday.ts
28154
+ import { execFile } from "child_process";
28152
28155
  import { join as join3 } from "path";
28156
+ import { promisify } from "util";
28153
28157
 
28154
28158
  // src/domains/devices/cdp-client.ts
28155
28159
  class CdpClosedError extends Error {
@@ -28339,9 +28343,15 @@ async function findEverydayBrowsers(deps = discoveryDeps()) {
28339
28343
  const roots = ["/Applications", join3(deps.home, "Applications")];
28340
28344
  const found = await Promise.all(EVERYDAY_BROWSERS.map(async ({ id, name, app, userData }) => {
28341
28345
  for (const root of roots) {
28342
- if (!await deps.isExecutable(join3(root, `${app}.app`, "Contents/MacOS", app)))
28346
+ const executable = join3(root, `${app}.app`, "Contents/MacOS", app);
28347
+ if (!await deps.isExecutable(executable))
28343
28348
  continue;
28344
- return { id, name, userDataDir: join3(deps.home, "Library/Application Support", userData) };
28349
+ return {
28350
+ id,
28351
+ name,
28352
+ userDataDir: join3(deps.home, "Library/Application Support", userData),
28353
+ executable
28354
+ };
28345
28355
  }
28346
28356
  return null;
28347
28357
  }));
@@ -28363,13 +28373,26 @@ async function portAnswers(port, fetchFn) {
28363
28373
  signal: AbortSignal.timeout(PROBE_TIMEOUT_MS)
28364
28374
  }).then(() => true, () => false);
28365
28375
  }
28376
+ async function debuggingAnswers(browser, deps) {
28377
+ const active = await readActivePort2(browser, deps.readText);
28378
+ return active && await portAnswers(active.port, deps.fetch ?? fetch) ? active : null;
28379
+ }
28380
+ async function psArgs() {
28381
+ const { stdout } = await promisify(execFile)("ps", ["-axww", "-o", "args="]);
28382
+ return stdout.split(`
28383
+ `);
28384
+ }
28385
+ async function isEverydayRunning(executable, deps) {
28386
+ const lines = await (deps.processArgs ?? psArgs)().catch(() => null);
28387
+ if (lines === null)
28388
+ return true;
28389
+ return lines.some((line) => (line === executable || line.startsWith(`${executable} `)) && !line.includes(" --user-data-dir"));
28390
+ }
28366
28391
  async function listEverydayDevices(options = {}, deps = discoveryDeps()) {
28367
28392
  const includeUnavailable = options.includeUnavailable ?? true;
28368
28393
  const browsers = await findEverydayBrowsers(deps);
28369
28394
  const devices = await Promise.all(browsers.map(async (browser) => {
28370
- const active = await readActivePort2(browser, deps.readText);
28371
- const ready = active !== null && await portAnswers(active.port, deps.fetch ?? fetch);
28372
- const state = ready ? "ready" : "remote debugging off";
28395
+ const state = await debuggingAnswers(browser, deps) ? "ready" : await isEverydayRunning(browser.executable, deps) ? "remote debugging off" : "not running";
28373
28396
  return {
28374
28397
  id: browser.id,
28375
28398
  name: `${browser.name} (everyday)`,
@@ -28382,7 +28405,7 @@ async function listEverydayDevices(options = {}, deps = discoveryDeps()) {
28382
28405
  }));
28383
28406
  return devices.filter((device) => includeUnavailable || device.state === "ready");
28384
28407
  }
28385
- var ALLOW_TIMEOUT_MS = 60000;
28408
+ var ALLOW_TIMEOUT_MS = EVERYDAY_ALLOW_TIMEOUT_MS;
28386
28409
 
28387
28410
  class EverydayConnectError extends Error {
28388
28411
  constructor(message) {
@@ -28394,9 +28417,9 @@ function seconds(ms) {
28394
28417
  return `${Number((ms / 1000).toFixed(3))} s`;
28395
28418
  }
28396
28419
  async function openEverydaySocket(browser, deps) {
28397
- const active = await readActivePort2(browser, deps.readText);
28420
+ const active = await debuggingAnswers(browser, deps);
28398
28421
  if (!active) {
28399
- throw new EverydayConnectError(`Remote debugging is off in ${browser.name}. Turn it on at chrome://inspect/#remote-debugging, then connect again`);
28422
+ throw new EverydayConnectError(await isEverydayRunning(browser.executable, deps) ? `Remote debugging is off in ${browser.name}. Turn it on at ${REMOTE_DEBUGGING_SETTING_URL}, then connect again` : `${browser.name} is not running. Open it, turn on remote debugging at ${REMOTE_DEBUGGING_SETTING_URL}, then connect again`);
28400
28423
  }
28401
28424
  const allowTimeoutMs = deps.allowTimeoutMs ?? ALLOW_TIMEOUT_MS;
28402
28425
  const connect = deps.connect ?? connectCdp;
@@ -28876,6 +28899,42 @@ function parseDevicectlApps(json) {
28876
28899
  const others = apps2.filter((app) => !app.builtByDeveloper);
28877
28900
  return [...developerBuilt, ...others].map((app) => app.bundleIdentifier);
28878
28901
  }
28902
+ function parseDevicectlAppUrl(json, bundleId) {
28903
+ const items = json?.result?.apps;
28904
+ if (!Array.isArray(items))
28905
+ return;
28906
+ const app = items.find((item) => item.bundleIdentifier === bundleId);
28907
+ return typeof app?.url === "string" && app.url ? app.url : undefined;
28908
+ }
28909
+ function parseDevicectlProcesses(json) {
28910
+ const items = json?.result?.runningProcesses;
28911
+ if (!Array.isArray(items))
28912
+ return null;
28913
+ const processes = [];
28914
+ for (const item of items) {
28915
+ if (typeof item?.processIdentifier !== "number" || typeof item.executable !== "string")
28916
+ continue;
28917
+ processes.push({ pid: item.processIdentifier, executable: item.executable });
28918
+ }
28919
+ return processes;
28920
+ }
28921
+ function decoded(url) {
28922
+ try {
28923
+ return decodeURI(url);
28924
+ } catch {
28925
+ return url;
28926
+ }
28927
+ }
28928
+ function appMainProcess(processes, appUrl) {
28929
+ const bundle = decoded(appUrl.endsWith("/") ? appUrl : `${appUrl}/`);
28930
+ return processes.find((process2) => {
28931
+ const executable = decoded(process2.executable);
28932
+ if (!executable.startsWith(bundle))
28933
+ return false;
28934
+ const inside = executable.slice(bundle.length);
28935
+ return inside !== "" && !inside.includes("/");
28936
+ });
28937
+ }
28879
28938
 
28880
28939
  // src/domains/devices/application.ts
28881
28940
  async function runCommand(command, options) {
@@ -31818,8 +31877,8 @@ async function readProfiles(products, exec) {
31818
31877
  const path = join12(platform, app, "embedded.mobileprovision");
31819
31878
  if (!existsSync6(path))
31820
31879
  continue;
31821
- const decoded = await exec(["security", "cms", "-D", "-i", path]);
31822
- const profile = decoded.exitCode === 0 ? parseProfile(decoded.stdout) : null;
31880
+ const decoded2 = await exec(["security", "cms", "-D", "-i", path]);
31881
+ const profile = decoded2.exitCode === 0 ? parseProfile(decoded2.stdout) : null;
31823
31882
  if (profile)
31824
31883
  profiles.push(profile);
31825
31884
  }
@@ -32224,6 +32283,9 @@ var PHONE_KEY_SUPPORT = {
32224
32283
  };
32225
32284
  var RECORDING_PULL_MS = 400;
32226
32285
  var UNREACHABLE_GRACE_MS = 20000;
32286
+ var TERMINATE_GRACE_MS = 3000;
32287
+ var TERMINATE_POLL_MS = 250;
32288
+ var KILL_REAP_MS = 1000;
32227
32289
  function errorMessage5(error) {
32228
32290
  return error instanceof Error ? error.message : String(error);
32229
32291
  }
@@ -32371,28 +32433,28 @@ async function createIosDeviceSession(options, deps = iosDeviceDeps()) {
32371
32433
  }
32372
32434
  return { width: size.width, height: size.height };
32373
32435
  });
32436
+ const devicectl = async (args, label) => {
32437
+ const result = await deps.devicectl(args);
32438
+ if (result.exitCode !== 0) {
32439
+ throw new Error(`${label}: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.exitCode}`}`);
32440
+ }
32441
+ };
32442
+ const devicectlJson = async (args, label) => {
32443
+ const dir = await mkdtemp4(join13(tmpdir5(), "yoqa-devicectl-"));
32444
+ const jsonPath = join13(dir, "out.json");
32445
+ try {
32446
+ await devicectl([...args, "--json-output", jsonPath], label);
32447
+ return JSON.parse(await readFile5(jsonPath, "utf8"));
32448
+ } finally {
32449
+ await rm5(dir, { recursive: true, force: true });
32450
+ }
32451
+ };
32374
32452
  const installedApps = remember(async () => {
32375
- const dir = await mkdtemp4(join13(tmpdir5(), "yoqa-apps-"));
32376
- const jsonPath = join13(dir, "apps.json");
32377
32453
  try {
32378
- const result = await deps.devicectl([
32379
- "device",
32380
- "info",
32381
- "apps",
32382
- "--device",
32383
- udid,
32384
- "--json-output",
32385
- jsonPath
32386
- ]);
32387
- if (result.exitCode !== 0) {
32388
- throw new Error(result.stderr.trim() || result.stdout.trim() || `exit ${result.exitCode}`);
32389
- }
32390
- return parseDevicectlApps(JSON.parse(await readFile5(jsonPath, "utf8")));
32454
+ return parseDevicectlApps(await devicectlJson(["device", "info", "apps", "--device", udid], "devicectl apps"));
32391
32455
  } catch (error) {
32392
32456
  console.warn(`[yoqa-runner] devicectl could not list the apps on iPhone ${udid} (${errorMessage5(error)}); with no target app, the tree is SpringBoard's`);
32393
32457
  return [];
32394
- } finally {
32395
- await rm5(dir, { recursive: true, force: true });
32396
32458
  }
32397
32459
  });
32398
32460
  const readPageSource = async () => {
@@ -32503,11 +32565,52 @@ async function createIosDeviceSession(options, deps = iosDeviceDeps()) {
32503
32565
  };
32504
32566
  let pointerStart = null;
32505
32567
  let pointerActive = false;
32506
- const devicectl = async (args, label) => {
32507
- const result = await deps.devicectl(args);
32508
- if (result.exitCode !== 0) {
32509
- throw new Error(`${label}: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.exitCode}`}`);
32510
- }
32568
+ const installedAt = async (id) => {
32569
+ const appUrl = parseDevicectlAppUrl(await devicectlJson(["device", "info", "apps", "--device", udid, "--bundle-id", id, "--include-all-apps"], "devicectl apps"), id);
32570
+ if (!appUrl)
32571
+ throw new Error(`${id} is not installed on the iPhone`);
32572
+ return appUrl;
32573
+ };
32574
+ const appProcess = async (appUrl) => {
32575
+ const processes = parseDevicectlProcesses(await devicectlJson(["device", "info", "processes", "--device", udid], "devicectl processes"));
32576
+ if (!processes)
32577
+ throw new Error("devicectl processes: the process list was unreadable");
32578
+ return appMainProcess(processes, appUrl);
32579
+ };
32580
+ const terminateGraceMs = deps.terminateGraceMs ?? TERMINATE_GRACE_MS;
32581
+ const terminate = async (id) => {
32582
+ const appUrl = await installedAt(id);
32583
+ const running2 = await appProcess(appUrl);
32584
+ if (!running2)
32585
+ return;
32586
+ const signal = (force) => devicectl([
32587
+ "device",
32588
+ "process",
32589
+ "terminate",
32590
+ "--device",
32591
+ udid,
32592
+ "--pid",
32593
+ String(running2.pid),
32594
+ ...force ? ["--kill"] : []
32595
+ ], "devicectl terminate");
32596
+ const exited = async (waitMs) => {
32597
+ const deadline = performance.now() + waitMs;
32598
+ for (;; ) {
32599
+ if ((await appProcess(appUrl))?.pid !== running2.pid)
32600
+ return true;
32601
+ const left = deadline - performance.now();
32602
+ if (left <= 0)
32603
+ return false;
32604
+ await Bun.sleep(Math.min(TERMINATE_POLL_MS, left));
32605
+ }
32606
+ };
32607
+ await signal(false);
32608
+ if (await exited(terminateGraceMs))
32609
+ return;
32610
+ await signal(true);
32611
+ if (await exited(Math.min(KILL_REAP_MS, terminateGraceMs)))
32612
+ return;
32613
+ throw new Error(`${id} is still running on the iPhone after a force-kill (pid ${running2.pid}); close it on the phone`);
32511
32614
  };
32512
32615
  return {
32513
32616
  lane: "direct",
@@ -32547,7 +32650,7 @@ async function createIosDeviceSession(options, deps = iosDeviceDeps()) {
32547
32650
  appId = id;
32548
32651
  },
32549
32652
  targetApp: () => appId,
32550
- terminateApp: notYet("Terminating an app"),
32653
+ terminateApp: (id) => withActionLock(() => terminate(id)),
32551
32654
  backgroundApp: (seconds2 = 3) => withActionLock(async () => {
32552
32655
  await call("button", { name: "home" }, true);
32553
32656
  if (!appId)
@@ -107960,6 +108063,7 @@ async function performAction(session, body, options) {
107960
108063
  } else {
107961
108064
  await session.terminateApp(body.appId);
107962
108065
  await session.activateApp(body.appId);
108066
+ storage = "kept";
107963
108067
  }
107964
108068
  break;
107965
108069
  }
@@ -110241,15 +110345,15 @@ var HonoRequest = class {
110241
110345
  return param && tryDecodeURIComponent(param);
110242
110346
  }
110243
110347
  #getAllDecodedParams() {
110244
- const decoded = {};
110348
+ const decoded2 = {};
110245
110349
  const keys = Object.keys(this.#matchResult[0][this.routeIndex][1]);
110246
110350
  for (const key of keys) {
110247
110351
  const value = this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key]);
110248
110352
  if (value !== undefined) {
110249
- decoded[key] = tryDecodeURIComponent(value);
110353
+ decoded2[key] = tryDecodeURIComponent(value);
110250
110354
  }
110251
110355
  }
110252
- return decoded;
110356
+ return decoded2;
110253
110357
  }
110254
110358
  #getParamValue(paramKey) {
110255
110359
  return this.#matchResult[1] ? this.#matchResult[1][paramKey] : paramKey;
@@ -111859,7 +111963,7 @@ import { join as join23 } from "path";
111859
111963
  // package.json
111860
111964
  var package_default = {
111861
111965
  name: "@yoqa/runner",
111862
- version: "1.0.6",
111966
+ version: "1.0.7",
111863
111967
  description: "Yoqa local runner \u2014 device control + catalog HTTP API (Bun). Start with yoqa serve or the yoqa-runner bin.",
111864
111968
  license: "UNLICENSED",
111865
111969
  type: "module",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yoqa/runner",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "Yoqa local runner — device control + catalog HTTP API (Bun). Start with yoqa serve or the yoqa-runner bin.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",