Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
import { deriveCapabilityForPlatform } from '../platform-descriptor/derive.ts';
import { platformDescriptors } from '../platform-descriptor/registry.ts';
import { getPlugin } from '../platform-plugin/plugin.ts';
import { registerBuiltinPlatformPlugins } from '../platform-plugin/register-builtins.ts';
import { registerBuiltinPlatformPlugins } from '../interactors/register-builtins.ts';

// Phase 3 step (b) parity gate. Independent oracles pin that the migration is
// byte-for-byte behaviorless:
Expand Down
2 changes: 1 addition & 1 deletion src/core/capabilities.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { deriveCapabilityMatrix } from './command-descriptor/derive.ts';
import { commandDescriptors } from './command-descriptor/registry.ts';
import { tryGetPlugin } from './platform-plugin/plugin.ts';
import { registerBuiltinPlatformPlugins } from './platform-plugin/register-builtins.ts';
import { registerBuiltinPlatformPlugins } from './interactors/register-builtins.ts';
import type { DeviceInfo } from '../kernel/device.ts';

// Populate the PlatformPlugin registry once at module load (idempotent; registers
Expand Down
6 changes: 4 additions & 2 deletions src/core/command-descriptor/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ const isShardedTestRequest = (req: DaemonRequest): boolean =>
// The per-command `supports()` / `unsupportedHint()` device closures that used to
// live here were RELOCATED VERBATIM onto the owning PlatformPlugin's
// `capability.supportsByDefault` / `unsupportedHintByDefault` in Phase 3 step b.2
// (src/core/platform-plugin/register-builtins.ts). The capability facet now carries
// platform/kind buckets only; admission reads the closure off the plugin.
// (the Apple family's closures live on the Apple plugin, src/platforms/apple/plugin.ts;
// android/linux/web plugins are wired in src/core/interactors/register-builtins.ts). The
// capability facet now carries platform/kind buckets only; admission reads the closure
// off the plugin.
// ---------------------------------------------------------------------------

const APPLE_SIM_AND_DEVICE = { simulator: true, device: true };
Expand Down
2 changes: 1 addition & 1 deletion src/core/interactors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AppError } from '../kernel/errors.ts';
import { getProviderDeviceInteractor, isActiveProviderDevice } from '../provider-device-runtime.ts';
import type { Interactor, RunnerContext } from './interactor-types.ts';
import { getPlugin } from './platform-plugin/plugin.ts';
import { registerBuiltinPlatformPlugins } from './platform-plugin/register-builtins.ts';
import { registerBuiltinPlatformPlugins } from './interactors/register-builtins.ts';

// Populate the platform-plugin registry once, at module load (only registers
// lazy closures — no leaf code is imported here, so CLI cold-start is unaffected).
Expand Down
117 changes: 117 additions & 0 deletions src/core/interactors/register-builtins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { registerPlatformPlugin, type PlatformPlugin } from '../platform-plugin/plugin.ts';
import { applePlugin } from '../../platforms/apple/plugin.ts';
import { PUBLIC_COMMANDS } from '../../command-catalog.ts';
import { isAudioProbeSupportedDevice } from '../../kernel/audio-probe-support.ts';
import { WEB_DESKTOP_DEVICE } from '../platform-inventory.ts';
import type { Platform, DeviceInfo } from '../../kernel/device.ts';
import type { DeviceInventoryRequest } from '../platform-inventory.ts';

// The builtin-plugin wiring lives at the interactor seam (src/core/interactors/) —
// the one place R3 (see scripts/layering/check.ts) permits a STATIC value import of
// `platforms/`, so this module can pull the relocated `applePlugin`
// (src/platforms/apple/plugin.ts) into the registry while the generic registry + type
// stay in `core/` (src/core/platform-plugin/plugin.ts) where non-interactor core code
// like `core/capabilities.ts` may import them. The Apple plugin instance and its
// capability closures now live under `platforms/apple/`; the android/linux/web wiring
// stays here. Each plugin WRAPS today's existing factories (src/core/interactors/*) and
// the inventory if-chain (src/core/platform-inventory.ts) as LAZY methods: the dynamic
// `import()`s and per-platform list calls are byte-for-byte the same as the
// hand-authored `getInteractor` switch arms and `listLocalDeviceInventory` branches.
// `as const satisfies PlatformPlugin` preserves each plugin's literal `platforms` tuple
// so the totality assertion below is a real compile-time check.

const androidPlugin = {
id: 'android',
platforms: ['android'],
capability: {
bucket: 'android',
supportsByDefault: { [PUBLIC_COMMANDS.audio]: isAudioProbeSupportedDevice },
},
// Wraps the Android arm of `resolveLogBackend`: every Android device -> 'android'.
appLog: { resolveBackend: () => 'android' },
// Wraps the Android arm of `supportsPlatformPerfMetrics`: every Android device
// reports perf-metrics support.
perf: { supportsMetrics: () => true },
createInteractor: async (device: DeviceInfo) => {
const { createAndroidInteractor } = await import('./android.ts');
return createAndroidInteractor(device);
},
discoverDevices: async (request: DeviceInventoryRequest) => {
const { listAndroidDevices } = await import('../../platforms/android/devices.ts');
return await listAndroidDevices({
serialAllowlist: request.androidSerialAllowlist
? new Set(request.androidSerialAllowlist)
: undefined,
});
},
} as const satisfies PlatformPlugin;

const linuxPlugin = {
id: 'linux',
platforms: ['linux'],
capability: { bucket: 'linux' },
createInteractor: async () => {
const { createLinuxInteractor } = await import('./linux.ts');
return createLinuxInteractor();
},
discoverDevices: async () => {
const { listLinuxDevices } = await import('../../platforms/linux/devices.ts');
return await listLinuxDevices();
},
} as const satisfies PlatformPlugin;

const webPlugin = {
id: 'web',
platforms: ['web'],
capability: { bucket: 'web' },
createInteractor: async () => {
const { createWebInteractor } = await import('./web.ts');
return createWebInteractor();
},
// Mirrors the `request.platform === 'web'` branch (the single static device).
discoverDevices: async () => [WEB_DESKTOP_DEVICE],
} as const satisfies PlatformPlugin;

/**
* The builtin plugins, in `PLATFORMS` order so `registeredPlatforms()` derives
* the canonical tuple's order (asserted by the parity test).
*/
export const BUILTIN_PLATFORM_PLUGINS = [
applePlugin,
androidPlugin,
linuxPlugin,
webPlugin,
] as const satisfies readonly PlatformPlugin[];

// The leaf platforms covered by at least one builtin plugin, recovered from the
// preserved literal `platforms` tuples.
type CoveredPlatform = (typeof BUILTIN_PLATFORM_PLUGINS)[number]['platforms'][number];

/**
* Compile-time EXHAUSTIVENESS: a new `Platform` literal added to `PLATFORMS`
* without a plugin makes `Platform` no longer extend `CoveredPlatform`, so this
* alias resolves to `false`, violating the `extends true` constraint and failing
* the build. This is the registry counterpart of the deleted `getInteractor`
* switch's exhaustive `never` default. (Equivalent in spirit to the §5.1
* `Object.fromEntries(registeredPlatforms()...) satisfies Record<Platform, true>`
* sketch, but type-level so it cannot be satisfied vacuously by a runtime map.)
*/
type AssertTrue<T extends true> = T;
export type BuiltinPluginsCoverAllPlatforms = AssertTrue<
[Platform] extends [CoveredPlatform] ? true : false
>;

let registered = false;

/**
* Registers every builtin plugin into the shared registry exactly once
* (idempotent). Called at the top of `core/interactors.ts` so the registry is
* populated before any `getPlugin` lookup; safe to call again from tests.
*/
export function registerBuiltinPlatformPlugins(): void {
if (registered) return;
for (const plugin of BUILTIN_PLATFORM_PLUGINS) {
registerPlatformPlugin(plugin);
}
registered = true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
} from '../../../__tests__/test-utils/index.ts';
import { APPLE_OS_CAPABILITIES, resolveDeviceAppleOs } from '../apple-os-capabilities.ts';
import { getPlugin } from '../plugin.ts';
import { registerBuiltinPlatformPlugins } from '../register-builtins.ts';
import { registerBuiltinPlatformPlugins } from '../../interactors/register-builtins.ts';

// Phase 3 step d.5 table-equivalence gate. The AppleOS-axis predicates
// (`target !== 'tv'` / `platform !== 'macos'` / `isTvOsDevice`) that used to be
Expand Down
5 changes: 4 additions & 1 deletion src/core/platform-plugin/__tests__/parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { PLATFORMS, type Platform } from '../../../kernel/device.ts';
import { AppError } from '../../../kernel/errors.ts';
import { platformDescriptors } from '../../platform-descriptor/registry.ts';
import { getPlugin, registeredPlatforms, registerPlatformPlugin, tryGetPlugin } from '../plugin.ts';
import { BUILTIN_PLATFORM_PLUGINS, registerBuiltinPlatformPlugins } from '../register-builtins.ts';
import {
BUILTIN_PLATFORM_PLUGINS,
registerBuiltinPlatformPlugins,
} from '../../interactors/register-builtins.ts';

// Idempotently populate the registry for this test module.
registerBuiltinPlatformPlugins();
Expand Down
2 changes: 1 addition & 1 deletion src/daemon/__tests__/applog-plugin-routing-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
WEB_DESKTOP_DEVICE,
} from '../../__tests__/test-utils/index.ts';
import { getPlugin } from '../../core/platform-plugin/plugin.ts';
import { registerBuiltinPlatformPlugins } from '../../core/platform-plugin/register-builtins.ts';
import { registerBuiltinPlatformPlugins } from '../../core/interactors/register-builtins.ts';
import { resolveLogBackend } from '../app-log.ts';
import type { LogBackend } from '../network-log.ts';

Expand Down
2 changes: 1 addition & 1 deletion src/daemon/__tests__/perf-plugin-routing-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
WEB_DESKTOP_DEVICE,
} from '../../__tests__/test-utils/index.ts';
import { getPlugin } from '../../core/platform-plugin/plugin.ts';
import { registerBuiltinPlatformPlugins } from '../../core/platform-plugin/register-builtins.ts';
import { registerBuiltinPlatformPlugins } from '../../core/interactors/register-builtins.ts';
import { buildPerfResponseData } from '../handlers/session-perf.ts';
import { PERF_UNAVAILABLE_REASON } from '../handlers/session-startup-metrics.ts';

Expand Down
2 changes: 1 addition & 1 deletion src/daemon/app-log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import path from 'node:path';
import type { DeviceInfo } from '../kernel/device.ts';
import { AppError } from '../kernel/errors.ts';
import { tryGetPlugin } from '../core/platform-plugin/plugin.ts';
import { registerBuiltinPlatformPlugins } from '../core/platform-plugin/register-builtins.ts';
import { registerBuiltinPlatformPlugins } from '../core/interactors/register-builtins.ts';
import { runCmd } from '../utils/exec.ts';
import { runXcrun } from '../platforms/apple/core/tool-provider.ts';
import { runAndroidAdb } from '../platforms/android/adb.ts';
Expand Down
2 changes: 1 addition & 1 deletion src/daemon/handlers/session-perf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { SessionAction, SessionState } from '../types.ts';
import { AppError, normalizeError } from '../../kernel/errors.ts';
import { isApplePlatform } from '../../kernel/device.ts';
import { tryGetPlugin } from '../../core/platform-plugin/plugin.ts';
import { registerBuiltinPlatformPlugins } from '../../core/platform-plugin/register-builtins.ts';
import { registerBuiltinPlatformPlugins } from '../../core/interactors/register-builtins.ts';
import type { AndroidAdbExecutor } from '../../platforms/android/adb-executor.ts';
import {
ANDROID_HPROF_SNAPSHOT_DESCRIPTION,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { test, expect } from 'vitest';
import { createAppleInteractor } from '../apple.ts';
import { createAppleInteractor } from '../interactor.ts';
import type { DeviceInfo } from '../../../kernel/device.ts';
import type { RunnerContext } from '../../interactor-types.ts';
import type { RunnerContext } from '../../../core/interactor-types.ts';
import { AppError } from '../../../kernel/errors.ts';

// watchOS is an explicit unsupported sentinel: XCUITest cannot drive watchOS UI,
Expand Down
2 changes: 1 addition & 1 deletion src/platforms/apple/core/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ import {
prepareSimulatorStatusBarForScreenshot as prepareStatusBarForScreenshot,
} from '../screenshot-status-bar.ts';
import { runAppleRunnerCommand } from '../runner/runner-client.ts';
import { iosRunnerOverrides } from '../../../ios/interactions.ts';
import { iosRunnerOverrides } from '../../interactions.ts';
import { IOS_DEVICE_INSTALL_TIMEOUT_MS, IOS_SIMULATOR_TERMINATE_TIMEOUT_MS } from '../config.ts';
import type { DeviceInfo } from '../../../../kernel/device.ts';
import { withDiagnosticsScope } from '../../../../utils/diagnostics.ts';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { isTvOsDevice, type DeviceInfo } from '../../kernel/device.ts';
import { assertScrollGestureInput, type ScrollDirection } from '../../core/scroll-gesture.ts';
import { normalizeScrollDurationMs, SCROLL_DURATION_MAX_MS } from '../../core/scroll-command.ts';
import { runAppleRunnerCommand } from '../apple/core/runner/runner-client.ts';
import { runAppleRunnerCommand } from './core/runner/runner-client.ts';
import {
buildRunnerSequenceCommand,
parseRunnerSequenceResult,
} from '../apple/core/runner/runner-sequence.ts';
import type { RunnerCommand } from '../apple/core/runner/runner-contract.ts';
import { appleRemotePressCommand } from '../apple/os/tvos/remote.ts';
import { runMacosDesktopScroll } from '../apple/os/macos/desktop-scroll.ts';
} from './core/runner/runner-sequence.ts';
import type { RunnerCommand } from './core/runner/runner-contract.ts';
import { appleRemotePressCommand } from './os/tvos/remote.ts';
import { runMacosDesktopScroll } from './os/macos/desktop-scroll.ts';
import {
normalizeAppleScrollResult,
normalizeAppleScrollResultWithResolvedFrame,
scrollRunnerFields,
type AppleScrollOptions,
} from '../apple/core/scroll.ts';
} from './core/scroll.ts';
import type {
BackMode,
Interactor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,16 @@ import {
screenshotIos,
setIosSetting,
writeIosClipboardText,
} from '../../platforms/apple/core/apps.ts';
import {
iosRunnerOverrides,
resolveAppleBackRunnerCommand,
} from '../../platforms/ios/interactions.ts';
import { appleRemotePressCommand } from '../../platforms/apple/os/tvos/remote.ts';
import { runMacOsScreenshotAction } from '../../platforms/apple/os/macos/helper.ts';
import { runAppleRunnerCommand } from '../../platforms/apple/core/runner/runner-client.ts';
} from './core/apps.ts';
import { iosRunnerOverrides, resolveAppleBackRunnerCommand } from './interactions.ts';
import { appleRemotePressCommand } from './os/tvos/remote.ts';
import { runMacOsScreenshotAction } from './os/macos/helper.ts';
import { runAppleRunnerCommand } from './core/runner/runner-client.ts';
import { withDiagnosticTimer } from '../../utils/diagnostics.ts';
import { isTvOsDevice, type DeviceInfo } from '../../kernel/device.ts';
import { AppError } from '../../kernel/errors.ts';
import type { RawSnapshotNode } from '../../kernel/snapshot.ts';
import type { Interactor, RunnerContext } from '../interactor-types.ts';
import type { Interactor, RunnerContext } from '../../core/interactor-types.ts';
import {
readSnapshotQualityVerdict,
type SnapshotQualityVerdict,
Expand Down
Loading
Loading