mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
013d5b60c5
## Summary
Three fixes that together get `describe` (and the other ax-service /
native-devtools tools) working against **ios-remote** (`sim-remote`)
devices — including from a **Linux** host. Reproduced end-to-end against
a live remote iPhone 17 simulator over the QUIC relay.
Before: `argent run describe --udid remote:<UDID>` returned an empty
`ROOT AXGroup` with a misleading "reboot the simulator" hint.
### 1. `sim-remote spawn` output was never valid JSON
(`fix(tool-server)`)
`simctlSpawn` parses the CLI's stdout as one `{exit_code,stdout,stderr}`
(or `{pid}`) JSON object, but `sim-remote spawn` only emits that when
passed `--json`. Without it a **non-detached** spawn streams the child's
raw output live, so `JSON.parse` throws `Unexpected end of JSON input`.
This broke ios-remote describe at the first step:
`remoteIosHost.bootstrapAx` runs `defaults write com.apple.Accessibility
…` through a non-detached `simctlSpawn`, which failed before the
ax-service daemon ever started — surfacing only as an empty AX tree.
Fix: pass `--json` unconditionally (no-op for the already-JSON
`--detach` path).
### 2. The tcp ax-service was resolved/packaged under a host-platform
dir (`fix(native-devtools-ios)`)
The tcp `ax-service` is a **darwin/iOS-simulator** binary that is
uploaded to and `simctl spawn`d on the **remote** macOS orchestrator, so
it's needed on any *host* platform. But it was resolved (and packaged)
under `bin/<hostPlatform>/tcp` via `platformBinDir()`, so a **Linux host
looked at `bin/linux/tcp` and never found the darwin-built binary** —
the ios-remote path was effectively macOS-host-only. (The tcp dylibs
were already neutral at `dylibs/tcp`.)
Moved it to a platform-neutral `bin/tcp`, mirroring `dylibs/tcp`, and
rewired every producer/consumer:
- resolver: `platformTcpBinDir()` → `tcpBinDir()` = `BIN_DIR/tcp`
- `native-devtools-ios/scripts/build.sh`: `bin/darwin/tcp` → `bin/tcp`
- `scripts/download-native-binaries.sh`: `TCP_BIN_DIR` → `bin/tcp`
- `packages/argent/scripts/bundle-tools.cjs`: copy from/to `bin/tcp`
- tests assert the neutral location on both linux and darwin
### 3. Missing tcp artifacts were undiagnosable
(`fix(native-devtools-ios)`)
When a tcp artifact is absent (local/dev build, older package), the bare
`... not found` error was thrown deep in the ax-service factory, where
`describeIos` swallowed it and returned an empty tree plus
`DEGRADED_HINT` — telling the agent to `boot-device --force`, a dead
end. The only escape was discovering `ARGENT_SIMULATOR_SERVER_TCP_DIR`
by reading source. Fix: `requireTcpArtifact` throws an actionable error
naming the exact path, the override env var, and how to build it;
`describeIos` surfaces that instead of the reboot hint.
## Testing
- New `sim-remote-spawn-json.test.ts` — asserts `--json` on both plain
and `--bin/--detach` argv, before the `--` separator.
- `native-devtools-ios/test/index.test.ts` — the tcp binary resolves
from the neutral `bin/tcp` on **both** linux and darwin; the not-found
errors name their override env var.
- `native-devtools-ios` (16) + ios/describe tool-server suites pass;
typecheck, eslint, prettier clean.
- Manual end-to-end against `remote:<UDID>` (darwin host): binary at
`bin/tcp` → full tree from `source: "ax-service"`, no hint; binary
removed → actionable `ARGENT_SIMULATOR_SERVER_TCP_DIR` hint.
66 lines
2.4 KiB
TypeScript
66 lines
2.4 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
|
// `simctlSpawn` parses the CLI's stdout as one `{exit_code,stdout,stderr}` (or
|
|
// `{pid}`) JSON object. `sim-remote spawn` only emits that object when passed
|
|
// `--json`; without it a non-detached spawn streams the child's raw output live,
|
|
// which then fails `JSON.parse` ("Unexpected end of JSON input"). These tests
|
|
// pin `--json` onto every spawn argv so the regression that broke every
|
|
// ios-remote describe (via bootstrapAx's `defaults write`) can't come back.
|
|
const calls: Array<{ cmd: string; args: readonly string[] }> = [];
|
|
|
|
vi.mock("node:child_process", async () => {
|
|
const actual = await vi.importActual<typeof import("node:child_process")>("node:child_process");
|
|
return {
|
|
...actual,
|
|
execFile: (
|
|
cmd: string,
|
|
args: readonly string[],
|
|
_opts: unknown,
|
|
cb?: (err: Error | null, out: { stdout: string; stderr: string }) => void
|
|
) => {
|
|
const callback = typeof _opts === "function" ? _opts : cb!;
|
|
calls.push({ cmd, args });
|
|
callback(null, { stdout: '{"exit_code":0,"pid":null,"stdout":"","stderr":""}', stderr: "" });
|
|
},
|
|
};
|
|
});
|
|
|
|
import { simctlSpawn } from "../src/utils/sim-remote";
|
|
|
|
beforeEach(() => {
|
|
calls.length = 0;
|
|
});
|
|
|
|
describe("simctlSpawn --json", () => {
|
|
it("passes --json for a non-detached in-simulator argv", async () => {
|
|
await simctlSpawn("remote:ABCD1234", {
|
|
args: ["defaults", "write", "com.apple.Accessibility", "X"],
|
|
});
|
|
expect(calls).toHaveLength(1);
|
|
const { cmd, args } = calls[0];
|
|
expect(cmd).toBe("sim-remote");
|
|
expect(args).toContain("--json");
|
|
// `--json` must precede the `--` argv separator so it is parsed as an option.
|
|
expect(args.indexOf("--json")).toBeLessThan(args.indexOf("--"));
|
|
// Everything after `--` is the in-simulator argv, untouched.
|
|
expect(args.slice(args.indexOf("--") + 1)).toEqual([
|
|
"defaults",
|
|
"write",
|
|
"com.apple.Accessibility",
|
|
"X",
|
|
]);
|
|
});
|
|
|
|
it("passes --json alongside --bin and --detach for the ax-service daemon spawn", async () => {
|
|
await simctlSpawn("remote:ABCD1234", {
|
|
binPath: "/path/to/tcp/ax-service",
|
|
args: ["--port", "5000", "--timeout", "3600"],
|
|
detach: true,
|
|
});
|
|
const { args } = calls[0];
|
|
expect(args).toContain("--json");
|
|
expect(args).toContain("--detach");
|
|
expect(args).toContain("--bin");
|
|
});
|
|
});
|