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 -->
103 lines
3.2 KiB
TypeScript
103 lines
3.2 KiB
TypeScript
/**
|
|
* `bootIos` shuts a simulator down and boots it again, so `boot-device` is the
|
|
* one tool that could take a provider's device away. The promise ("Argent will
|
|
* never kill, boot, reboot or shut down your device") is about the device, so
|
|
* the refusal has to hold for its real udid as well as its `ext:` id;
|
|
* `ios.additionalDeviceSets` surfaces such a simulator by raw udid.
|
|
*/
|
|
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
import * as fs from "node:fs";
|
|
import * as os from "node:os";
|
|
import * as path from "node:path";
|
|
import { getFailureSignal, type Registry } from "@argent/registry";
|
|
import { createBootDeviceTool } from "../src/tools/devices/boot-device";
|
|
import { makeExternalId } from "../src/utils/external-devices";
|
|
|
|
const registry = {} as Registry;
|
|
const IOS_UDID = "1A2B3C4D-5E6F-7081-92A3-B4C5D6E7F809";
|
|
|
|
let temporaryDirectory: string;
|
|
|
|
function publishDescriptor(): void {
|
|
const descriptorPath = path.join(temporaryDirectory, "acme.json");
|
|
|
|
fs.writeFileSync(
|
|
descriptorPath,
|
|
JSON.stringify({
|
|
devices: [
|
|
{
|
|
capabilities: ["simctl", "ax-service"],
|
|
kind: "simulator",
|
|
name: "iPhone 16 Pro",
|
|
nativeId: IOS_UDID,
|
|
platform: "ios",
|
|
state: "Booted",
|
|
},
|
|
],
|
|
id: "acme-3f2a9c",
|
|
name: "Acme IDE",
|
|
pid: process.pid,
|
|
schemaVersion: 1,
|
|
})
|
|
);
|
|
|
|
process.env.ARGENT_DEVICE_PROVIDERS = descriptorPath;
|
|
delete process.env.ARGENT_DISABLE_DEVICE_PROVIDERS;
|
|
}
|
|
|
|
/** The refusal's message and failure code or a test failure if it booted. */
|
|
async function bootFailure(params: Record<string, unknown>): Promise<{
|
|
code: string | undefined;
|
|
message: string;
|
|
}> {
|
|
try {
|
|
await createBootDeviceTool(registry).execute!({}, params);
|
|
} catch (error) {
|
|
return {
|
|
code: getFailureSignal(error)?.error_code,
|
|
message: (error as Error).message,
|
|
};
|
|
}
|
|
|
|
throw new Error(`boot-device did not refuse ${JSON.stringify(params)}`);
|
|
}
|
|
|
|
beforeEach(() => {
|
|
temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "argent-boot-refused-"));
|
|
});
|
|
|
|
afterEach(() => {
|
|
delete process.env.ARGENT_DEVICE_PROVIDERS;
|
|
process.env.ARGENT_DISABLE_DEVICE_PROVIDERS = "1";
|
|
fs.rmSync(temporaryDirectory, { recursive: true, force: true });
|
|
});
|
|
|
|
describe("boot-device on a provider's device", () => {
|
|
it("refuses the ext: id", async () => {
|
|
publishDescriptor();
|
|
|
|
const failure = await bootFailure({ udid: makeExternalId("acme-3f2a9c", IOS_UDID) });
|
|
|
|
expect(failure.code).toBe("EXTERNAL_DEVICE_LIFECYCLE_REFUSED");
|
|
expect(failure.message).toMatch(/owns its lifecycle/);
|
|
});
|
|
|
|
/** The reboot is what the promise forbids; the spelling is incidental. */
|
|
it("refuses the raw udid of the same device, naming the provider", async () => {
|
|
publishDescriptor();
|
|
|
|
const failure = await bootFailure({ udid: IOS_UDID });
|
|
|
|
expect(failure.code).toBe("EXTERNAL_DEVICE_LIFECYCLE_REFUSED");
|
|
expect(failure.message).toMatch(/supplied by Acme IDE/);
|
|
});
|
|
|
|
it("refuses it with force, which is the flag that would reboot it", async () => {
|
|
publishDescriptor();
|
|
|
|
const failure = await bootFailure({ udid: IOS_UDID, force: true });
|
|
|
|
expect(failure.code).toBe("EXTERNAL_DEVICE_LIFECYCLE_REFUSED");
|
|
});
|
|
});
|