mobile-automator 0.28.1 → 0.28.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobile-automator",
3
- "version": "0.28.1",
3
+ "version": "0.28.2",
4
4
  "description": "Host-agnostic mauto CLI for AI-driven mobile QA automation — analyzes a mobile project and drives devices through mobile-mcp.",
5
5
  "homepage": "https://github.com/sh3lan93/mobile-automator#readme",
6
6
  "repository": {
package/src/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const fs = require('fs');
4
+ const path = require('path');
4
5
  const { Command } = require('commander');
5
6
 
6
7
  const { version: PKG_VERSION } = require('../package.json');
@@ -190,9 +191,12 @@ async function handleElements({ deviceBridge }) {
190
191
  }
191
192
  }
192
193
 
193
- async function handleScreenshot({ deviceBridge }, destPath) {
194
+ // Relative paths resolve against the caller's cwd HERE, not in the daemon: the
195
+ // daemon runs from the project root, which is not where a verb run from a
196
+ // subdirectory meant `./shot.png` (#214).
197
+ async function handleScreenshot({ deviceBridge, cwd = process.cwd() }, destPath) {
194
198
  try {
195
- const savedPath = await deviceBridge.screenshot(destPath);
199
+ const savedPath = await deviceBridge.screenshot(path.resolve(cwd, destPath));
196
200
  return { envelope: ok({ path: savedPath }), exitKind: 'ok' };
197
201
  } catch (err) {
198
202
  return {
@@ -380,7 +384,10 @@ async function handleLaunch({ deviceBridge }, appId) {
380
384
  }
381
385
  }
382
386
 
383
- async function handleInstall({ deviceBridge }, appPath) {
387
+ // Resolved against the caller's cwd for the same reason as handleScreenshot
388
+ // (#214). The empty-path guard runs first: path.resolve(cwd, '') is the cwd
389
+ // itself, which would turn a missing argument into a bogus install attempt.
390
+ async function handleInstall({ deviceBridge, cwd = process.cwd() }, appPath) {
384
391
  const p = String(appPath == null ? '' : appPath);
385
392
  if (!p) {
386
393
  return {
@@ -388,9 +395,10 @@ async function handleInstall({ deviceBridge }, appPath) {
388
395
  exitKind: 'invalid_input',
389
396
  };
390
397
  }
398
+ const abs = path.resolve(cwd, p);
391
399
  try {
392
- await deviceBridge.installApp(p);
393
- return { envelope: ok({ installed: p }), exitKind: 'ok' };
400
+ await deviceBridge.installApp(abs);
401
+ return { envelope: ok({ installed: abs }), exitKind: 'ok' };
394
402
  } catch (err) {
395
403
  return deviceFail(err);
396
404
  }
@@ -1807,7 +1815,7 @@ function buildProgram(deps = {}) {
1807
1815
  .command('install <path>')
1808
1816
  .description('Install an app from a local .apk/.app/.ipa path')
1809
1817
  .option('--device <id>', 'target device id')
1810
- .action(withEnvelope((path, opts) => withBridge(opts.device, (bridge) => handleInstall({ deviceBridge: bridge }, path))));
1818
+ .action(withEnvelope((appPath, opts) => withBridge(opts.device, (bridge) => handleInstall({ deviceBridge: bridge }, appPath))));
1811
1819
 
1812
1820
  program
1813
1821
  .command('uninstall <appId>')
@@ -66,6 +66,14 @@ function deviceNotFoundHint(id) {
66
66
  );
67
67
  }
68
68
 
69
+ // B3 (#214): mobile-mcp's validateOutputPath only allows screenshots under the
70
+ // engine's cwd or the temp dir, and its message calls the former "the current
71
+ // directory". The engine runs from the project root (the daemon is spawned
72
+ // there), which is not necessarily the directory the user ran the verb from.
73
+ const OUTSIDE_ALLOWED_DIRS_HINT =
74
+ 'Screenshots must be saved under the project root (the directory containing mobile-automator/) ' +
75
+ 'or the system temp directory. Choose a path inside one of them and retry.';
76
+
69
77
  function engineHint(message, { mobilecliVersion = resolveMobilecliVersion } = {}) {
70
78
  try {
71
79
  if (typeof message !== 'string' || !message) return null;
@@ -74,6 +82,7 @@ function engineHint(message, { mobilecliVersion = resolveMobilecliVersion } = {}
74
82
  }
75
83
  const notFound = /Device "([^"]+)" not found/.exec(message);
76
84
  if (notFound) return deviceNotFoundHint(notFound[1]);
85
+ if (/not in the list of allowed directories/.test(message)) return OUTSIDE_ALLOWED_DIRS_HINT;
77
86
  return null;
78
87
  } catch {
79
88
  return null;
@@ -54,7 +54,12 @@ async function spawnDaemon({
54
54
  try {
55
55
  // The root in argv is an identity marker only (see paths.daemonArgv): env
56
56
  // stays the daemon's source of truth, but `ps` can only see argv.
57
+ // cwd: the engine inherits the daemon's cwd, and mobile-mcp's
58
+ // validateOutputPath only allows screenshots under its own process.cwd()
59
+ // (or the temp dir). Pin it to the project root so the allow-list never
60
+ // depends on which subdirectory happened to spawn the daemon (#214).
57
61
  const child = spawn(process.execPath, [DAEMON_BIN, ...paths.daemonArgv(projectRoot)], {
62
+ cwd: projectRoot,
58
63
  detached: true,
59
64
  stdio: log.stdio,
60
65
  env,