mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
6e9e45c9f0
This pull request lets Argent drive a simulator or emulator another process is already running, attaching to its simulator-server rather than spawning a second one. A provider writes a JSON descriptor to `~/.argent/providers/` listing the devices it offers and the mechanisms Argent may use on each. They appear in `list-devices` with an `ext:` id and work with the existing tools. The file is re-read on every call, so a withdrawal or a narrowed grant applies immediately. `ios.additionalDeviceSets` (#600) already makes such a simulator reachable by UDID. What changes is ownership. `boot-device` and `stop-simulator-server` refuse these devices, anything the provider did not grant is refused with a message naming it and Argent uses only the endpoints its own simulator-server build serves. A grant binds to the device rather than to one of its names, so the real udid or serial is gated exactly like the `ext:` id. Android emulators are covered too and a device visible to both a provider and `adb`/`simctl` is listed once. The contract ships as `schemas/device-provider-v1.json`, validated by `argent providers check`, and is documented in `docs/reference/device-providers.mdx`. Includes unit tests and an e2e phase where the harness acts as its own provider. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added support for discovering and connecting to externally managed devices. * Added `argent providers` commands for listing, validating, publishing, withdrawing, and pruning providers. * Added provider-aware simulator, debugger, profiler, native tools, and device listings. * Added capability controls, endpoint validation, revocation handling, and provider-specific diagnostics. * Added automatic CLI discovery for provider integrations. * **Bug Fixes** * Improved service recovery and paused-runtime error reporting. * Improved simulator keyboard and paste command reliability. * **Documentation** * Documented provider descriptors, capabilities, lifecycle rules, and CLI usage. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
127 lines
4.7 KiB
TypeScript
127 lines
4.7 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
|
// launch-app / restart-app must keep working for Apple system apps: launching
|
|
// or restarting com.apple.* is legitimate — the app just never injects. They
|
|
// stay safe from the terminal NATIVE_DEVTOOLS_NOT_INJECTABLE throw only
|
|
// because all four iOS call sites use the 2-arg precheckNativeDevtools
|
|
// overload (no bundleId). Unlike launch-restart-tvos.test.ts, the REAL
|
|
// precheck runs here — these tests pin the tool boundary so a call site
|
|
// "improved" to pass params.bundleId (3-arg) starts failing loudly instead of
|
|
// shipping a launch-app that throws on Settings/Safari. A plain udid
|
|
// dispatches the local iOS sites (platforms/ios.ts); a `remote:`-prefixed
|
|
// udid dispatches the ios-remote sites (platforms/shared.ts) — all four are
|
|
// covered.
|
|
|
|
const execFileMock = vi.fn(
|
|
(
|
|
_cmd: string,
|
|
_args: readonly string[],
|
|
opts: unknown,
|
|
cb?: (err: Error | null, out: { stdout: string; stderr: string }) => void
|
|
) => {
|
|
const callback = typeof opts === "function" ? opts : cb!;
|
|
callback(null, { stdout: "", stderr: "" });
|
|
}
|
|
);
|
|
|
|
vi.mock("node:child_process", async () => {
|
|
const actual = await vi.importActual<typeof import("node:child_process")>("node:child_process");
|
|
return { ...actual, execFile: (...a: unknown[]) => (execFileMock as any)(...a) };
|
|
});
|
|
|
|
vi.mock("../src/utils/ios-devices", () => ({
|
|
isTvOsSimulator: vi.fn(async () => false),
|
|
}));
|
|
|
|
import { createLaunchAppTool } from "../src/tools/launch-app";
|
|
import { createRestartAppTool } from "../src/tools/restart-app";
|
|
import type { NativeDevtoolsApi } from "../src/blueprints/native-devtools";
|
|
import { __primeDepCacheForTests, __resetDepCacheForTests } from "../src/utils/check-deps";
|
|
|
|
const IOS_UDID = "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA";
|
|
const REMOTE_UDID = `remote:${IOS_UDID}`;
|
|
const SYSTEM_APP = "com.apple.Preferences";
|
|
|
|
// Minimal api satisfying what the real 2-arg precheck consults
|
|
// (getInitFailure + ensureEnvReady); the rest must stay untouched.
|
|
function makeNativeApi(): NativeDevtoolsApi {
|
|
return {
|
|
isEnvSetup: () => true,
|
|
socketPath: "/tmp/test.sock",
|
|
ensureEnvReady: async () => {},
|
|
reverifyEnv: async () => {},
|
|
armsEnv: true,
|
|
withdrawEnv: async () => {},
|
|
getInitFailure: () => null,
|
|
isConnected: () => false,
|
|
isAppRunning: async () => false,
|
|
listConnectedBundleIds: () => [],
|
|
appConnectionState: async () => "connected",
|
|
activateNetworkInspection: () => {},
|
|
getNetworkLog: () => [],
|
|
clearNetworkLog: () => {},
|
|
getAppState: async () => {
|
|
throw new Error("not implemented");
|
|
},
|
|
detectFrontmostBundleId: async () => null,
|
|
queryViewHierarchy: async () => ({}),
|
|
} as NativeDevtoolsApi;
|
|
}
|
|
|
|
function makeRegistry() {
|
|
return { resolveService: vi.fn(async () => makeNativeApi() as unknown) } as any;
|
|
}
|
|
|
|
beforeEach(() => {
|
|
execFileMock.mockClear();
|
|
// The local branch requires xcrun, the remote branch sim-remote; prime both
|
|
// so neither probes the host toolchain (Linux CI has neither).
|
|
__resetDepCacheForTests();
|
|
__primeDepCacheForTests(["xcrun", "sim-remote"]);
|
|
});
|
|
|
|
describe("launch-app / restart-app — Apple system apps stay launchable (real precheck)", () => {
|
|
it("launch-app launches com.apple.* without the non-injectable terminal throw", async () => {
|
|
const tool = createLaunchAppTool(makeRegistry());
|
|
|
|
await expect(tool.execute!({}, { udid: IOS_UDID, bundleId: SYSTEM_APP })).resolves.toEqual({
|
|
launched: true,
|
|
bundleId: SYSTEM_APP,
|
|
});
|
|
});
|
|
|
|
it("restart-app restarts com.apple.* without the non-injectable terminal throw", async () => {
|
|
const tool = createRestartAppTool(makeRegistry());
|
|
|
|
await expect(tool.execute!({}, { udid: IOS_UDID, bundleId: SYSTEM_APP })).resolves.toEqual({
|
|
restarted: true,
|
|
bundleId: SYSTEM_APP,
|
|
});
|
|
});
|
|
|
|
it("launch-app (ios-remote) launches com.apple.* without the terminal throw", async () => {
|
|
// The remote branch reads services.nativeDevtools instead of resolving
|
|
// lazily; the shared handler (platforms/shared.ts) runs the same real
|
|
// 2-arg precheck.
|
|
const tool = createLaunchAppTool(makeRegistry());
|
|
|
|
await expect(
|
|
tool.execute!(
|
|
{ nativeDevtools: makeNativeApi() },
|
|
{ udid: REMOTE_UDID, bundleId: SYSTEM_APP }
|
|
)
|
|
).resolves.toEqual({ launched: true, bundleId: SYSTEM_APP });
|
|
});
|
|
|
|
it("restart-app (ios-remote) restarts com.apple.* without the terminal throw", async () => {
|
|
const tool = createRestartAppTool(makeRegistry());
|
|
|
|
await expect(
|
|
tool.execute!(
|
|
{ nativeDevtools: makeNativeApi() },
|
|
{ udid: REMOTE_UDID, bundleId: SYSTEM_APP }
|
|
)
|
|
).resolves.toEqual({ restarted: true, bundleId: SYSTEM_APP });
|
|
});
|
|
});
|