Skip to content
Open
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
62 changes: 62 additions & 0 deletions docs/resources/(resources)/ios-simulator.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
title: ios-simulator
description: A reference page for the ios-simulator resource
---

The ios-simulator resource manages iOS (and iPadOS/watchOS/tvOS/visionOS) simulator instances on macOS
using `xcrun simctl`. Each resource declaration represents one simulator. You can declare multiple
`ios-simulator` resources to create a full testing matrix across device types and OS versions.

Simulators are created with the specified device type and runtime, and optionally booted. Removing a
resource deletes the simulator from the system. Xcode Command Line Tools must be installed — add an
`xcode-tools` resource as a dependency if you are not sure they are present.

## Parameters:

- **name** *(string, required)* — Human-readable name for the simulator instance (e.g. `"iPhone 15 Dev"`). Must be unique across your declared simulators.

- **deviceType** *(string, required)* — CoreSimulator device type identifier. Use the format `com.apple.CoreSimulator.SimDeviceType.<Device>`. Run `xcrun simctl list devicetypes` to see identifiers available on your machine.

- **runtime** *(string, required)* — CoreSimulator runtime identifier. Use the format `com.apple.CoreSimulator.SimRuntime.<Platform>-<Version>`. Run `xcrun simctl list runtimes` to see installed runtimes.

- **state** *(string, optional)* — Desired runtime state of the simulator. One of `"Booted"` or `"Shutdown"`. Defaults to `"Shutdown"`. Can be modified after creation.

## Example usage:

```json title="codify.jsonc"
[
{
"type": "ios-simulator",
"name": "iPhone 15 Dev",
"deviceType": "com.apple.CoreSimulator.SimDeviceType.iPhone-15",
"runtime": "com.apple.CoreSimulator.SimRuntime.iOS-18-0",
"state": "Shutdown",
"os": ["macOS"]
}
]
```

```json title="codify.jsonc"
[
{
"type": "xcode-tools",
"os": ["macOS"]
},
{
"type": "ios-simulator",
"name": "iPhone 15 Pro",
"deviceType": "com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro",
"runtime": "com.apple.CoreSimulator.SimRuntime.iOS-18-0",
"state": "Shutdown",
"os": ["macOS"]
},
{
"type": "ios-simulator",
"name": "iPad Pro 11-inch",
"deviceType": "com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M4",
"runtime": "com.apple.CoreSimulator.SimRuntime.iOS-18-0",
"state": "Shutdown",
"os": ["macOS"]
}
]
```
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import { TerraformResource } from './resources/terraform/terraform.js';
import { CursorResource } from './resources/cursor/cursor.js';
import { VscodeResource } from './resources/vscode/vscode.js';
import { WebStormResource } from './resources/webstorm/webstorm.js';
import { IosSimulatorResource } from './resources/ios/ios-simulator/ios-simulator.js';
import { XcodeToolsResource } from './resources/xcode-tools/xcode-tools.js';
import { YumResource } from './resources/yum/yum.js';
import { PyCharmResource } from './resources/jetbrains/pycharm/pycharm.js';
Expand All @@ -82,6 +83,7 @@ runPlugin(Plugin.create(
[
new GitResource(),
new XcodeToolsResource(),
new IosSimulatorResource(),
new PathResource(),
new AliasResource(),
new AliasesResource(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Known CoreSimulator device type identifiers shipped with Xcode.
// These identifiers are stable across Xcode versions for each device family.
export default async function loadIosSimulatorDeviceTypes(): Promise<string[]> {
return [
// iPhone SE
'com.apple.CoreSimulator.SimDeviceType.iPhone-SE-3rd-generation',

// iPhone 14 family
'com.apple.CoreSimulator.SimDeviceType.iPhone-14',
'com.apple.CoreSimulator.SimDeviceType.iPhone-14-Plus',
'com.apple.CoreSimulator.SimDeviceType.iPhone-14-Pro',
'com.apple.CoreSimulator.SimDeviceType.iPhone-14-Pro-Max',

// iPhone 15 family
'com.apple.CoreSimulator.SimDeviceType.iPhone-15',
'com.apple.CoreSimulator.SimDeviceType.iPhone-15-Plus',
'com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro',
'com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro-Max',

// iPhone 16 family
'com.apple.CoreSimulator.SimDeviceType.iPhone-16',
'com.apple.CoreSimulator.SimDeviceType.iPhone-16-Plus',
'com.apple.CoreSimulator.SimDeviceType.iPhone-16-Pro',
'com.apple.CoreSimulator.SimDeviceType.iPhone-16-Pro-Max',

// iPad mini
'com.apple.CoreSimulator.SimDeviceType.iPad-mini-6th-generation',
'com.apple.CoreSimulator.SimDeviceType.iPad-mini-A17-Pro',

// iPad Air
'com.apple.CoreSimulator.SimDeviceType.iPad-Air-5th-generation',
'com.apple.CoreSimulator.SimDeviceType.iPad-Air-11-inch-M2',
'com.apple.CoreSimulator.SimDeviceType.iPad-Air-13-inch-M2',

// iPad Pro 11-inch
'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-4th-generation',
'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M4',

// iPad Pro 12.9 / 13-inch
'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-12-9-inch-6th-generation',
'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-13-inch-M4',

// Apple Watch
'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-9-41mm',
'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-9-45mm',
'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Ultra-2-49mm',

// Apple TV
'com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-3rd-generation-4K',
'com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-3rd-generation-1080p',

// Apple Vision Pro
'com.apple.CoreSimulator.SimDeviceType.Apple-Vision-Pro',
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Known CoreSimulator runtime identifiers. Each entry corresponds to an iOS
// (or watchOS/tvOS/visionOS) runtime that can be installed via Xcode.
export default async function loadIosSimulatorRuntimes(): Promise<string[]> {
return [
// iOS
'com.apple.CoreSimulator.SimRuntime.iOS-16-0',
'com.apple.CoreSimulator.SimRuntime.iOS-16-1',
'com.apple.CoreSimulator.SimRuntime.iOS-16-2',
'com.apple.CoreSimulator.SimRuntime.iOS-16-4',
'com.apple.CoreSimulator.SimRuntime.iOS-17-0',
'com.apple.CoreSimulator.SimRuntime.iOS-17-2',
'com.apple.CoreSimulator.SimRuntime.iOS-17-4',
'com.apple.CoreSimulator.SimRuntime.iOS-17-5',
'com.apple.CoreSimulator.SimRuntime.iOS-18-0',
'com.apple.CoreSimulator.SimRuntime.iOS-18-1',
'com.apple.CoreSimulator.SimRuntime.iOS-18-2',
'com.apple.CoreSimulator.SimRuntime.iOS-18-3',
'com.apple.CoreSimulator.SimRuntime.iOS-18-4',

// watchOS
'com.apple.CoreSimulator.SimRuntime.watchOS-10-0',
'com.apple.CoreSimulator.SimRuntime.watchOS-10-4',
'com.apple.CoreSimulator.SimRuntime.watchOS-11-0',
'com.apple.CoreSimulator.SimRuntime.watchOS-11-2',

// tvOS
'com.apple.CoreSimulator.SimRuntime.tvOS-17-0',
'com.apple.CoreSimulator.SimRuntime.tvOS-17-4',
'com.apple.CoreSimulator.SimRuntime.tvOS-18-0',
'com.apple.CoreSimulator.SimRuntime.tvOS-18-2',

// visionOS
'com.apple.CoreSimulator.SimRuntime.xrOS-1-0',
'com.apple.CoreSimulator.SimRuntime.xrOS-1-2',
'com.apple.CoreSimulator.SimRuntime.xrOS-2-0',
'com.apple.CoreSimulator.SimRuntime.xrOS-2-2',
];
}
196 changes: 196 additions & 0 deletions src/resources/ios/ios-simulator/ios-simulator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import {
CreatePlan,
DestroyPlan,
ExampleConfig,
ModifyPlan,
ParameterChange,
Resource,
ResourceSettings,
SpawnStatus,
getPty,
z,
} from '@codifycli/plugin-core';
import { OS } from '@codifycli/schemas';

const schema = z.object({
name: z
.string()
.describe('Name for the iOS simulator instance (e.g. "iPhone 15 Dev")'),
deviceType: z
.string()
.describe('Device type identifier (e.g. "com.apple.CoreSimulator.SimDeviceType.iPhone-15")'),
runtime: z
.string()
.describe('Runtime identifier (e.g. "com.apple.CoreSimulator.SimRuntime.iOS-18-0")'),
state: z
.enum(['Booted', 'Shutdown'])
.optional()
.describe('Desired runtime state of the simulator. Defaults to Shutdown.'),
});

export type IosSimulatorConfig = z.infer<typeof schema>;

interface SimDevice {
udid: string;
name: string;
state: string;
deviceTypeIdentifier: string;
}

interface SimctlDevicesOutput {
devices: Record<string, SimDevice[]>;
}

const defaultConfig: Partial<IosSimulatorConfig> & { os: any } = {
name: '<Replace me here!>',
deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15',
runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0',
state: 'Shutdown',
os: ['macOS'],
};

const exampleBasic: ExampleConfig = {
title: 'iPhone 15 simulator for development',
description: 'Create an iPhone 15 simulator running iOS 18 for use in development and UI testing.',
configs: [{
type: 'ios-simulator',
name: 'iPhone 15 Dev',
deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15',
runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0',
state: 'Shutdown',
os: ['macOS'],
}],
};

const exampleMultiDevice: ExampleConfig = {
title: 'iPhone and iPad simulator setup',
description: 'Install Xcode Command Line Tools and create an iPhone and iPad simulator for cross-device testing.',
configs: [
{ type: 'xcode-tools', os: ['macOS'] },
{
type: 'ios-simulator',
name: 'iPhone 15 Pro',
deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro',
runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0',
state: 'Shutdown',
os: ['macOS'],
},
{
type: 'ios-simulator',
name: 'iPad Pro 11-inch',
deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M4',
runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0',
state: 'Shutdown',
os: ['macOS'],
},
],
};

export class IosSimulatorResource extends Resource<IosSimulatorConfig> {
getSettings(): ResourceSettings<IosSimulatorConfig> {
return {
id: 'ios-simulator',
defaultConfig,
exampleConfigs: {
example1: exampleBasic,
example2: exampleMultiDevice,
},
operatingSystems: [OS.Darwin],
dependencies: ['xcode-tools'],
schema,
parameterSettings: {
state: { type: 'string', canModify: true },
},
allowMultiple: {
identifyingParameters: ['name'],
},
};
}

async refresh(parameters: Partial<IosSimulatorConfig>): Promise<Partial<IosSimulatorConfig> | null> {
const $ = getPty();

const { status, data } = await $.spawnSafe('xcrun simctl list devices --json');
if (status !== SpawnStatus.SUCCESS) {
return null;
}

let parsed: SimctlDevicesOutput;
try {
parsed = JSON.parse(data);
} catch {
return null;
}

for (const [runtimeId, devices] of Object.entries(parsed.devices)) {
const match = devices.find((d) => d.name === parameters.name);
if (match) {
return {
name: match.name,
deviceType: match.deviceTypeIdentifier,
runtime: runtimeId,
state: match.state === 'Booted' ? 'Booted' : 'Shutdown',
};
}
}

return null;
}

async create(plan: CreatePlan<IosSimulatorConfig>): Promise<void> {
const $ = getPty();
const { name, deviceType, runtime, state } = plan.desiredConfig;

// xcrun simctl create prints the new simulator's UDID to stdout
const { data: udid } = await $.spawn(
`xcrun simctl create "${name}" "${deviceType}" "${runtime}"`,
{ interactive: true }
);

if (state === 'Booted') {
await $.spawn(`xcrun simctl boot "${udid.trim()}"`, { interactive: true });
}
}

async modify(pc: ParameterChange<IosSimulatorConfig>, plan: ModifyPlan<IosSimulatorConfig>): Promise<void> {
if (pc.name !== 'state') return;

const $ = getPty();
const udid = await this.getUdidByName(plan.desiredConfig.name);
if (!udid) return;

if (plan.desiredConfig.state === 'Booted') {
await $.spawn(`xcrun simctl boot "${udid}"`, { interactive: true });
} else {
await $.spawn(`xcrun simctl shutdown "${udid}"`, { interactive: true });
}
}

async destroy(plan: DestroyPlan<IosSimulatorConfig>): Promise<void> {
const $ = getPty();
const udid = await this.getUdidByName(plan.currentConfig.name);
if (!udid) return;

await $.spawn(`xcrun simctl delete "${udid}"`, { interactive: true });
}

private async getUdidByName(name: string | undefined): Promise<string | null> {
if (!name) return null;

const $ = getPty();
const { status, data } = await $.spawnSafe('xcrun simctl list devices --json');
if (status !== SpawnStatus.SUCCESS) return null;

try {
const parsed: SimctlDevicesOutput = JSON.parse(data);
for (const devices of Object.values(parsed.devices)) {
const match = devices.find((d) => d.name === name);
if (match) return match.udid;
}
} catch {
// ignore parse errors
}

return null;
}
}
Loading