mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
630dc7c99b
* feat(examples): add runnable Node.js SDK examples under examples/sdk/ examples/test-app is a fixture and the repo's only prior examples/ content; the real SDK usage patterns lived only in website/docs/docs/client-api.md with no runnable script anywhere. Adds four standalone, typechecked examples covering the minimum surface from #1463: root client session (create -> open -> snapshot/tap -> close with typed error handling), agent-device/metro (normalizeBaseUrl/resolveRuntimeTransport), agent-device/contracts (centerOfRect on a snapshot node), and agent-device/batch (runBatch for a custom transport). Each imports the published `agent-device/...` subpaths rather than relative src/ paths. examples/sdk/tsconfig.json path-maps those subpaths to src/sdk/ so `pnpm typecheck` (now also run against this tsconfig) checks the examples in CI without a prior build, workspace link, or publish step. Running an example for real still resolves `agent-device` as a self-referencing package after `pnpm build`. src/__tests__/client-api-examples-drift.test.ts guards the examples against drifting from client-api.md's subpath API manifest in both directions, picked up automatically by the existing unit-core vitest project (no new script or workflow needed). examples/README.md indexes the new examples and notes that test-app/ remains a fixture, not an example; it is not renamed or moved. Refs #1463 * fix: address Fallow findings on the new SDK examples Fallow flagged the four examples/sdk/*.ts files as unused files (not reachable from any entry point) and three functions as high complexity. - Register the examples as manual entry points in .fallowrc.json, matching how other standalone scripts (scripts/patch-xcuitest-runner-icon.ts, scripts/runner-request-count/run.ts) are already declared. - Reduce complexity in client-session.ts and contracts-result.ts by extracting device-resolution/error-reporting and rect-assertion helpers out of main(). - Reduce complexity in the drift guard's parseSubpathManifest by splitting bullet-matching and backtick-name extraction into their own functions. Verified: pnpm check:fallow --base <PR base sha> now reports no issues, and pnpm check:tooling / pnpm test:unit stay green. Refs #1463 * fix: compile client-api.md's actual code snippets, not just its symbol manifest Addresses review feedback on #1463's drift guard: the existing guard only parsed the doc's "Public subpath API" bullet manifest and compared imported symbol names, so a fenced ```ts snippet could drift or stop compiling without the guard noticing. Added test/integration/client-api-doc-snippets.test.ts, which extracts every fenced ```ts block from client-api.md and typechecks it against the real agent-device/* sources (reusing examples/sdk/tsconfig.json's existing paths mapping, read via `tsc --showConfig` so there's one source of truth). Free identifiers that continue a `client`/`snapshot` from an earlier snippet are stubbed — typed against the real SDK return type, not `any`, so continuation snippets still get real checking. Lives in the Node integration lane (test/integration/*.test.ts), not vitest's unit-core: it spawns a real tsc Program, well past the unit suite's 2.5s budget. Running this check against the existing doc surfaced real, pre-existing snippet bugs (unrelated to the new examples), fixed here: - "sessions.artifacts": `result.cloudArtifacts` accessed without narrowing the `CloudArtifactsResult | DaemonArtifactsResult` union first. - "Device cloud sessions": `platform`/`device` were passed into the client constructor config, which doesn't accept them; moved to the `apps.open()` call where those fields actually belong. - "Android ADB providers": the inline `exec` handler had no parameter types, so it failed under strict/noImplicitAny; annotated with the real `AndroidAdbExecutorOptions` type. Two further gaps the check surfaced are pre-existing product/API-surface questions out of scope for this PR (not the new examples), so they're allowlisted in KNOWN_DOC_GAPS with comments rather than silently patched: - "Remote Metro helpers" documents prepareRemoteMetro/reloadRemoteMetro/ stopMetroTunnel/resolveRemoteConfigProfile as public, but none of them are exported from agent-device/metro or agent-device/remote-config today. - "Web sessions"/audio probe pass `platform` to `observability.network()`/ `.audio()`, but NetworkOptions/AudioOptions have no `platform` field even though the CLI's network/audio commands accept `--platform`. Refs #1463 * fix: close the doc-snippet compiler's stubbing hole and the two suppressed gaps Addresses the second round of review feedback on #1463's drift guard: 1. stubFreeNamesAndRecompile auto-stubbed every "Cannot find name" as `any`, so a typo like `cliet.apps.open()` would silently pass on the second compile. It now only stubs identifiers in an explicit allowlist (KNOWN_FREE_NAME_STUB_TYPES) — the real SDK-derived continuations (`client`, `androidClient`, `snapshot`) plus the doc's own invented host-glue names, each typed precisely rather than loosely. Anything else is left as a real compile failure. Added a regression test that feeds a `cliet` typo through the guard and asserts it fails. 2. KNOWN_DOC_GAPS filtered six real compiler errors out of the final assertion while the test claimed every snippet compiles. Investigated both and fixed the actual contracts instead of suppressing them: - `prepareMetroRuntime`/`reloadMetro` (src/metro/client-metro.ts) and `stopMetroTunnel` (src/metro/metro.ts) already existed and matched the doc's described workflow almost exactly (same result shape) but were never re-exported from `agent-device/metro`; same for `resolveRemoteConfigProfile` and `agent-device/remote-config`. Added the four exports and fixed the doc's stale function names (`prepareRemoteMetro`/`reloadRemoteMetro`) and one stale field name (`profileKey` -> `companionProfileKey` on the prepare call) to match. - `NetworkOptions`/`AudioOptions` (src/contracts/client-observability.ts) had no `platform` field even though the CLI's `network`/`audio` commands accept `--platform` for the same use case, and the client methods already forward the options object to the daemon generically (`executeCommand('network'|'audio', options)`) — so this was a type gap, not a runtime one. Switched both from AgentDeviceRequestOverrides to DeviceCommandBaseOptions (matching PerfOptions' existing pattern), closing the gap for real instead of stripping `platform` from the doc. - The "Android installFromSource()" snippet was missing its `createAgentDeviceClient` import outright; added it. KNOWN_DOC_GAPS is gone — every fenced snippet now compiles for real, and the test's assertion matches what it claims. 3. Switched the raw `execFileSync` calls to `runCmdSync` from src/utils/exec.ts, per AGENTS.md's process-execution invariant (this is a .ts integration test, not a packaging fixture that needs to stay dependency-free). Refs #1463 * docs: fix stale reloadRemoteMetro() prose reference to reloadMetro() The prose right after the Remote Metro helpers snippet still named the old function; the compile guard only checks the fenced snippet, not surrounding prose, so it didn't catch this leftover from the prior rename. Refs #1463 --------- Co-authored-by: Claude <noreply@anthropic.com>
76 lines
2.3 KiB
TypeScript
76 lines
2.3 KiB
TypeScript
/**
|
|
* Root client session: create a client, open an app, capture a snapshot, tap
|
|
* a node, then close the session — with typed error handling via the
|
|
* exported error helpers.
|
|
*
|
|
* Demonstrates: `createAgentDeviceClient`, `AppError`, `isAgentDeviceError`,
|
|
* and `normalizeAgentDeviceError` from the `agent-device` root export.
|
|
*
|
|
* Prerequisites: an `agent-device` daemon target (a booted iOS simulator).
|
|
* This file typechecks without one; running it for real also requires
|
|
* `pnpm build` first, so the package resolves at runtime.
|
|
*
|
|
* Run: node --experimental-strip-types examples/sdk/client-session.ts
|
|
*/
|
|
import {
|
|
AppError,
|
|
createAgentDeviceClient,
|
|
isAgentDeviceError,
|
|
normalizeAgentDeviceError,
|
|
} from 'agent-device';
|
|
|
|
async function resolveSnapshotCapableIosDevice(client: ReturnType<typeof createAgentDeviceClient>) {
|
|
const devices = await client.devices.list({ platform: 'ios' });
|
|
const device = devices[0];
|
|
if (!device) {
|
|
throw new AppError('DEVICE_NOT_FOUND', 'No iOS device available');
|
|
}
|
|
|
|
const capabilities = await client.devices.capabilities({ platform: 'ios' });
|
|
if (!capabilities.availableCommands.includes('snapshot')) {
|
|
throw new AppError('UNSUPPORTED_OPERATION', 'Selected target does not support snapshots');
|
|
}
|
|
|
|
return device;
|
|
}
|
|
|
|
function reportAgentDeviceError(error: unknown): void {
|
|
const normalized = normalizeAgentDeviceError(error);
|
|
console.error(`agent-device error [${normalized.code}]: ${normalized.message}`);
|
|
if (normalized.hint) {
|
|
console.error(`hint: ${normalized.hint}`);
|
|
}
|
|
process.exitCode = 1;
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const client = createAgentDeviceClient({
|
|
session: 'sdk-example',
|
|
lockPolicy: 'reject',
|
|
lockPlatform: 'ios',
|
|
});
|
|
|
|
try {
|
|
const device = await resolveSnapshotCapableIosDevice(client);
|
|
|
|
await client.apps.open({
|
|
app: 'com.apple.Preferences',
|
|
platform: 'ios',
|
|
udid: device.id,
|
|
});
|
|
|
|
const snapshot = await client.capture.snapshot({ interactiveOnly: true });
|
|
const target = snapshot.nodes.find((node) => node.role === 'button');
|
|
if (target) {
|
|
await client.interactions.press({ ref: target.ref });
|
|
}
|
|
} catch (error) {
|
|
if (!isAgentDeviceError(error)) throw error;
|
|
reportAgentDeviceError(error);
|
|
} finally {
|
|
await client.sessions.close();
|
|
}
|
|
}
|
|
|
|
await main();
|