Repository: raycast/extensions
Pull request #18463: Add manage runtimes and delete unsupported runtimes commands to Xcode extension
Author: login: qeude
Labels: name: extension fix / improvement
description: Label for PRs with extension's fix improvements
name: OP is contributor
description: The OP of the PR is a contributor of the extension
name: extension: xcode
description: Issues related to the xcode extension
Created: 2025-04-08T22:23:27Z | Merged: 2025-04-15T08:02:57Z

## Description

Since Xcode runtimes are hard to find through Xcode, takes many storage and aren't automatically removed. I thought it would be a great idea to have a convenient way to manage these and to easily delete all unsupported runtimes.

## Screencast

<img width="821" alt="image" src="https://github.com/user-attachments/assets/32039e93-0967-4f51-925d-a8472b84dc6d" />
<img width="750" alt="image" src="https://github.com/user-attachments/assets/1280fe8d-5255-4b53-86a8-b4e7408e98ad" />

## Checklist

- [x] I read the [extension guidelines](https://developers.raycast.com/basics/prepare-an-extension-for-store)
- [x] I read the [documentation about publishing](https://developers.raycast.com/basics/publish-an-extension)
- [x] I ran `npm run build` and [tested this distribution build in Raycast](https://developers.raycast.com/basics/prepare-an-extension-for-store#metadata-and-configuration)
- [x] I checked that files in the `assets` folder are used by the extension itself
- [x] I checked that assets used by the `README` are placed outside of the `metadata` folder



Diff:
diff --git a/extensions/xcode/CHANGELOG.md b/extensions/xcode/CHANGELOG.md
index edc52d22b10..568e172040d 100644
--- a/extensions/xcode/CHANGELOG.md
+++ b/extensions/xcode/CHANGELOG.md
@@ -1,5 +1,10 @@
 # Xcode Changelog
 
+## [Add Xcode Runtimes Management] - 2025-04-15
+
+- Add Xcode runtimes listing and management command
+- Add a delete unsupported Xcode runtimes command
+
 ## [Added Delete App Data] - 2024-09-30
 
 - Added delete app data command to remove the data of an app installed on a simulator without uninstalling.
diff --git a/extensions/xcode/package.json b/extensions/xcode/package.json
index 54cadc931e8..cbff01c7ff2 100644
--- a/extensions/xcode/package.json
+++ b/extensions/xcode/package.json
@@ -161,6 +161,13 @@
       "description": "List Apple devices with their product name and device code name.",
       "mode": "view"
     },
+    {
+      "name": "manage-runtimes.command",
+      "title": "Manage Runtimes",
+      "subtitle": "Xcode",
+      "description": "Manage your Xcode Runtimes.",
+      "mode": "view"
+    },
     {
       "name": "manage-simulators.command",
       "title": "Manage Simulators",
@@ -277,6 +284,13 @@
       "subtitle": "Xcode",
       "description": "Deletes devices that are not supported by the current Xcode SDK.",
       "mode": "no-view"
+    },
+    {
+      "name": "delete-unsupported-runtimes.command",
+      "title": "Delete Unsupported Runtimes",
+      "subtitle": "Xcode",
+      "description": "Deletes runtimes that are not supported by the current Xcode SDK.",
+      "mode": "no-view"
     }
   ],
   "dependencies": {
diff --git a/extensions/xcode/src/components/xcode-runtime-list/xcode-runtime-list-dropdown.component.tsx b/extensions/xcode/src/components/xcode-runtime-list/xcode-runtime-list-dropdown.component.tsx
new file mode 100644
index 00000000000..53c6afd014c
--- /dev/null
+++ b/extensions/xcode/src/components/xcode-runtime-list/xcode-runtime-list-dropdown.component.tsx
@@ -0,0 +1,21 @@
+import { List } from "@raycast/api";
+import { XcodeRuntimePlatformFilter } from "../../models/xcode-runtime/xcode-runtime-platform-filter.model";
+
+export function XcodeRuntimePlatformDropdown(props: {
+  onRuntimePlatformFilterChange: (newValue: XcodeRuntimePlatformFilter) => void;
+}) {
+  const { onRuntimePlatformFilterChange } = props;
+  return (
+    <List.Dropdown
+      tooltip="Select a runtime platform"
+      storeValue={true}
+      onChange={(newValue) => {
+        onRuntimePlatformFilterChange(newValue as XcodeRuntimePlatformFilter);
+      }}
+    >
+      {Object.values(XcodeRuntimePlatformFilter).map((filter) => {
+        return <List.Dropdown.Item key={filter} title={filter} value={filter} />;
+      })}
+    </List.Dropdown>
+  );
+}
diff --git a/extensions/xcode/src/components/xcode-runtime-list/xcode-runtime-list-item.component.tsx b/extensions/xcode/src/components/xcode-runtime-list/xcode-runtime-list-item.component.tsx
new file mode 100644
index 00000000000..fd119a3232c
--- /dev/null
+++ b/extensions/xcode/src/components/xcode-runtime-list/xcode-runtime-list-item.component.tsx
@@ -0,0 +1,100 @@
+import { Action, ActionPanel, Alert, Color, confirmAlert, Icon, List } from "@raycast/api";
+import { XcodeRuntime } from "../../models/xcode-runtime/xcode-runtime.model";
+import { XcodeRuntimeService } from "../../services/xcode-runtime.service";
+import { operationWithUserFeedback } from "../../shared/operation-with-user-feedback";
+
+export function XcodeRuntimeListItem(props: { runtime: XcodeRuntime; revalidate: () => void }) {
+  return (
+    <List.Item
+      title={{
+        tooltip:
+          props.runtime.lastUsageDate !== undefined
+            ? "Last used on " + props.runtime.lastUsageDate.toLocaleDateString()
+            : null,
+        value: props.runtime.name,
+      }}
+      subtitle={{ tooltip: "Build version", value: props.runtime.buildVersion }}
+      keywords={[props.runtime.platform, props.runtime.name, props.runtime.version]}
+      accessories={[
+        ...(!props.runtime.isSupported
+          ? [
+              {
+                icon: {
+                  source: Icon.Warning,
+                  tintColor: Color.Red,
+                },
+                tooltip: "Not supported (may be deleted)",
+              },
+            ]
+          : []),
+      ]}
+      actions={
+        <ActionPanel>
+          <ActionPanel.Section title={props.runtime.name}>
+            <Action.CopyToClipboard content={props.runtime.buildVersion} />
+            <Action
+              title="Delete Runtime"
+              icon={Icon.Trash}
+              style={Action.Style.Destructive}
+              shortcut={{ modifiers: ["ctrl"], key: "x" }}
+              onAction={async () => {
+                const alertOptions: Alert.Options = {
+                  icon: Icon.Trash,
+                  title: "Delete Runtime",
+                  message: `Are you sure you want to delete the ${props.runtime.name} runtime?`,
+                  primaryAction: {
+                    title: "Delete",
+                    style: Alert.ActionStyle.Destructive,
+                  },
+                };
+                if (!(await confirmAlert(alertOptions))) {
+                  return;
+                }
+                operationWithUserFeedback(
+                  "Deleting Runtime...",
+                  `${props.runtime.name} runtime has been deleted`,
+                  "Error Deleting runtime",
+                  async () => {
+                    await XcodeRuntimeService.deleteXcodeRuntime(props.runtime);
+                    props.revalidate();
+                  }
+                );
+              }}
+            />
+          </ActionPanel.Section>
+          <ActionPanel.Section title="All">
+            <Action
+              title="Delete Unsupported Runtimes"
+              icon={Icon.Trash}
+              style={Action.Style.Destructive}
+              shortcut={{ modifiers: ["ctrl", "shift"], key: "x" }}
+              onAction={async () => {
+                const alertOptions: Alert.Options = {
+                  icon: Icon.Trash,
+                  title: "Delete unsupported runtimes",
+                  message: "Are you sure you want to delete all unsupported runtimes?",
+                  primaryAction: {
+                    title: "Delete",
+                    style: Alert.ActionStyle.Destructive,
+                  },
+                };
+                if (!(await confirmAlert(alertOptions))) {
+                  return;
+                }
+                await operationWithUserFeedback(
+                  "Deleting unsupported runtimes",
+                  "Successfully deleted unsupported runtimes",
+                  "An error occurred while trying to delete unsupported runtimes",
+                  async () => {
+                    await XcodeRuntimeService.deleteUnsupportedXcodeRuntimes();
+                    props.revalidate();
+                  }
+                );
+              }}
+            />
+          </ActionPanel.Section>
+        </ActionPanel>
+      }
+    />
+  );
+}
diff --git a/extensions/xcode/src/components/xcode-runtime-list/xcode-runtime-list.component.tsx b/extensions/xcode/src/components/xcode-runtime-list/xcode-runtime-list.component.tsx
new file mode 100644
index 00000000000..05077dd6ee9
--- /dev/null
+++ b/extensions/xcode/src/components/xcode-runtime-list/xcode-runtime-list.component.tsx
@@ -0,0 +1,40 @@
+import { useCachedPromise } from "@raycast/utils";
+import { List } from "@raycast/api";
+import { useState } from "react";
+import { XcodeRuntimeService } from "../../services/xcode-runtime.service";
+import { XcodeRuntimePlatformFilter } from "../../models/xcode-runtime/xcode-runtime-platform-filter.model";
+import { XcodeRuntimePlatformDropdown } from "./xcode-runtime-list-dropdown.component";
+import { XcodeRuntimeListItem } from "./xcode-runtime-list-item.component";
+
+export function XcodeRuntimeList() {
+  const [runtimePlatformFilter, setRuntimePlatformFilter] = useState<XcodeRuntimePlatformFilter>(
+    XcodeRuntimePlatformFilter.all
+  );
+  const runtimeGroups = useCachedPromise(XcodeRuntimeService.xcodeRuntimeGroups, [runtimePlatformFilter]);
+
+  const onRuntimePlatformFilterChange = (newValue: XcodeRuntimePlatformFilter) => {
+    setRuntimePlatformFilter(newValue);
+  };
+  return (
+    <List
+      isLoading={runtimeGroups.isLoading}
+      searchBarAccessory={
+        <XcodeRuntimePlatformDropdown onRuntimePlatformFilterChange={onRuntimePlatformFilterChange} />
+      }
+    >
+      {runtimeGroups.data?.map((runtimeGroup) => {
+        return (
+          <List.Section key={runtimeGroup.platform} title={runtimeGroup.platform}>
+            {runtimeGroup.runtimes.map((runtime) => (
+              <XcodeRuntimeListItem
+                runtime={runtime}
+                revalidate={runtimeGroups.revalidate}
+                key={runtime.buildVersion}
+              />
+            ))}
+          </List.Section>
+        );
+      })}
+    </List>
+  );
+}
diff --git a/extensions/xcode/src/delete-unsupported-runtimes.command.tsx b/extensions/xcode/src/delete-unsupported-runtimes.command.tsx
new file mode 100644
index 00000000000..457649a16cf
--- /dev/null
+++ b/extensions/xcode/src/delete-unsupported-runtimes.command.tsx
@@ -0,0 +1,24 @@
+import { operationWithUserFeedback } from "./shared/operation-with-user-feedback";
+import { Alert, confirmAlert, Icon } from "@raycast/api";
+import { XcodeRuntimeService } from "./services/xcode-runtime.service";
+
+export default async () => {
+  const alertOptions: Alert.Options = {
+    icon: Icon.Trash,
+    title: "Delete unsupported runtimes",
+    message: "Are you sure you want to delete all unsupported runtimes?",
+    primaryAction: {
+      title: "Delete",
+      style: Alert.ActionStyle.Destructive,
+    },
+  };
+  if (!(await confirmAlert(alertOptions))) {
+    return;
+  }
+  await operationWithUserFeedback(
+    "Deleting unsupported runtimes",
+    "Successfully deleted unsupported runtimes",
+    "An error occurred while trying to delete unsupported runtimes",
+    XcodeRuntimeService.deleteUnsupportedXcodeRuntimes
+  );
+};
diff --git a/extensions/xcode/src/manage-runtimes.command.tsx b/extensions/xcode/src/manage-runtimes.command.tsx
new file mode 100644
index 00000000000..45a3a454c5f
--- /dev/null
+++ b/extensions/xcode/src/manage-runtimes.command.tsx
@@ -0,0 +1,8 @@
+import { XcodeInstallationVerifier } from "./components/xcode-installation-verifier/xcode-installation-verifier.component";
+import { XcodeRuntimeList } from "./components/xcode-runtime-list/xcode-runtime-list.component";
+
+export default () => (
+  <XcodeInstallationVerifier>
+    <XcodeRuntimeList />
+  </XcodeInstallationVerifier>
+);
diff --git a/extensions/xcode/src/models/xcode-runtime/xcode-runtime-group.model.ts b/extensions/xcode/src/models/xcode-runtime/xcode-runtime-group.model.ts
new file mode 100644
index 00000000000..06612c9f5b6
--- /dev/null
+++ b/extensions/xcode/src/models/xcode-runtime/xcode-runtime-group.model.ts
@@ -0,0 +1,16 @@
+import { XcodeRuntimePlatform } from "./xcode-runtime-platform.model";
+import { XcodeRuntime } from "./xcode-runtime.model";
+
+/**
+ * XcodeRuntimeGroup
+ */
+export interface XcodeRuntimeGroup {
+  /**
+   * The platform
+   */
+  platform: XcodeRuntimePlatform;
+  /**
+   * The XcodeRuntimes
+   */
+  runtimes: XcodeRuntime[];
+}
diff --git a/extensions/xcode/src/models/xcode-runtime/xcode-runtime-platform-filter.model.ts b/extensions/xcode/src/models/xcode-runtime/xcode-runtime-platform-filter.model.ts
new file mode 100644
index 00000000000..f4198027a10
--- /dev/null
+++ b/extensions/xcode/src/models/xcode-runtime/xcode-runtime-platform-filter.model.ts
@@ -0,0 +1,9 @@
+export enum XcodeRuntimePlatformFilter {
+  all = "All",
+  iOS = "iOS",
+  iPadOS = "iPadOS",
+  tvOS = "tvOS",
+  watchOS = "watchOS",
+  macOS = "macOS",
+  visionOS = "visionOS",
+}
diff --git a/extensions/xcode/src/models/xcode-runtime/xcode-runtime-platform.model.ts b/extensions/xcode/src/models/xcode-runtime/xcode-runtime-platform.model.ts
new file mode 100644
index 00000000000..38f65cf05b4
--- /dev/null
+++ b/extensions/xcode/src/models/xcode-runtime/xcode-runtime-platform.model.ts
@@ -0,0 +1,8 @@
+export enum XcodeRuntimePlatform {
+  iOS = "iOS",
+  iPadOS = "iPadOS",
+  tvOS = "tvOS",
+  watchOS = "watchOS",
+  macOS = "macOS",
+  visionOS = "visionOS",
+}
diff --git a/extensions/xcode/src/models/xcode-runtime/xcode-runtime.model.ts b/extensions/xcode/src/models/xcode-runtime/xcode-runtime.model.ts
new file mode 100644
index 00000000000..9a8fb4b108d
--- /dev/null
+++ b/extensions/xcode/src/models/xcode-runtime/xcode-runtime.model.ts
@@ -0,0 +1,34 @@
+import { XcodeRuntimePlatform } from "./xcode-runtime-platform.model";
+
+/**
+ * A Xcode Runtime
+ */
+export interface XcodeRuntime {
+  /**
+   * The name
+   */
+  name: string;
+  /**
+   * The platform
+   */
+  platform: XcodeRuntimePlatform;
+  /**
+   * The version
+   */
+  version: string;
+
+  /**
+   * The build version
+   */
+  buildVersion: string;
+
+  /**
+   * The last time this runtime was used
+   */
+  lastUsageDate?: Date;
+
+  /**
+   * The supported status of this runtime
+   */
+  isSupported: boolean;
+}
diff --git a/extensions/xcode/src/services/xcode-runtime.service.ts b/extensions/xcode/src/services/xcode-runtime.service.ts
new file mode 100644
index 00000000000..9a6ef43ca6d
--- /dev/null
+++ b/extensions/xcode/src/services/xcode-runtime.service.ts
@@ -0,0 +1,81 @@
+import { XcodeRuntimeGroup } from "../models/xcode-runtime/xcode-runtime-group.model";
+import { XcodeRuntimePlatformFilter } from "../models/xcode-runtime/xcode-runtime-platform-filter.model";
+import { XcodeRuntimePlatform } from "../models/xcode-runtime/xcode-runtime-platform.model";
+import { XcodeRuntime } from "../models/xcode-runtime/xcode-runtime.model";
+import { execAsync } from "../shared/exec-async";
+import { groupBy } from "../shared/group-by";
+
+/**
+ * XcodeRuntimeService
+ */
+export class XcodeRuntimeService {
+  static async xcodeRuntimeGroups(filter: XcodeRuntimePlatformFilter): Promise<XcodeRuntimeGroup[]> {
+    const runtimes = await XcodeRuntimeService.xcodeRuntimes();
+    return groupBy(
+      runtimes.filter(
+        (value) =>
+          filter === XcodeRuntimePlatformFilter.all || value.platform === (filter as unknown as XcodeRuntimePlatform)
+      ),
+      (runtime) => runtime.platform
+    )
+      .map((group) => {
+        return {
+          platform: group.key,
+          runtimes: group.values,
+        };
+      })
+      .sort((lhs, rhs) => lhs.platform.localeCompare(rhs.platform));
+  }
+
+  /**
+   * Retrieve all installed XcodeRuntimes
+   */
+  static async xcodeRuntimes(): Promise<XcodeRuntime[]> {
+    const output = await execAsync("xcrun simctl list -j -v runtimes");
+    const runtimesResponseJSON = JSON.parse(output.stdout);
+    if (!runtimesResponseJSON || !runtimesResponseJSON.runtimes) {
+      throw [];
+    }
+
+    const result: XcodeRuntime[] = runtimesResponseJSON.runtimes.map(
+      (runtime: {
+        name: string;
+        isAvailable: boolean;
+        platform: string;
+        version: string;
+        buildversion: string;
+        lastUsage: Record<string, string>;
+      }): XcodeRuntime => {
+        const lastUsageValues = Object.values(runtime.lastUsage || {});
+        const lastUsageDate =
+          lastUsageValues.length > 0
+            ? new Date(Math.max(...lastUsageValues.map((timestamp) => new Date(timestamp).getTime())))
+            : undefined;
+
+        const isDefaultDate = lastUsageDate && lastUsageDate.getFullYear() === 1;
+        const lastUsage = isDefaultDate ? undefined : lastUsageDate;
+        return {
+          name: runtime.name,
+          isSupported: runtime.isAvailable,
+          platform: runtime.platform as unknown as XcodeRuntimePlatform,
+          version: runtime.version,
+          buildVersion: runtime.buildversion,
+          lastUsageDate: lastUsage,
+        };
+      }
+    );
+    return result.sort((lhs, rhs) => rhs.buildVersion.localeCompare(lhs.buildVersion));
+  }
+
+  static async deleteXcodeRuntime(runtime: XcodeRuntime): Promise<void> {
+    await execAsync(`xcrun simctl runtime delete ${runtime.buildVersion}`);
+  }
+
+  static async deleteUnsupportedXcodeRuntimes(): Promise<void> {
+    const runtimes = await XcodeRuntimeService.xcodeRuntimes();
+    const unsupportedRuntimes = runtimes.filter((runtime) => runtime.isSupported === false);
+    for (const runtime of unsupportedRuntimes) {
+      await XcodeRuntimeService.deleteXcodeRuntime(runtime);
+    }
+  }
+}


Reviews:
user: login: greptile-apps[bot]
body: 
## PR Summary

This PR adds new TypeScript components and commands to enhance the management of Xcode runtimes, including listing, filtering, and deletion functionality.

- Updated `/extensions/xcode/CHANGELOG.md` with a new entry using `{PR_MERGE_DATE}` at the top.
- Added runtime list, list item, and dropdown components in `/extensions/xcode/src/components/xcode-runtime-list/` for filtering and display.
- Implemented deletion commands in `/extensions/xcode/src/delete-unsupported-runtimes.command.tsx` and `/extensions/xcode/src/manage-runtimes.command.tsx`.
- Introduced new models and service in `/extensions/xcode/src/models/xcode-runtime/*` and `/extensions/xcode/src/services/xcode-runtime.service.ts` for runtime grouping.
- Noted that `/extensions/xcode/package.json` would benefit from a trailing newline for POSIX compliance.

<sub>💡 (1/5) You can manually trigger the bot by mentioning @greptileai in a comment!</sub>

<sub>12 file(s) reviewed, 4 comment(s)</sub>
<sub>[Edit PR Review Bot Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews&utm_content=extensions_18463)</sub>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T22:30:57Z
user: login: qeude
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T22:34:10Z
user: login: qeude
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T22:34:40Z
user: login: SvenTiigi
body: LGTM 👍 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T17:50:09Z
user: login: pernielsentikaer
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-15T08:01:49Z

Review comments:
diff_hunk: @@ -0,0 +1,100 @@
+import { Action, ActionPanel, Alert, Color, confirmAlert, Icon, List } from "@raycast/api";
+import { XcodeRuntime } from "../../models/xcode-runtime/xcode-runtime.model";
+import { XcodeRuntimeService } from "../../services/xcode-runtime.service";
+import { operationWithUserFeedback } from "../../shared/operation-with-user-feedback";
+
+export function XcodeRuntimeListItem(props: { runtime: XcodeRuntime; revalidate: () => void }) {
+  return (
+    <List.Item
+      title={{
+        tooltip:
+          props.runtime.lastUsageDate !== undefined
+            ? "Last used on " + props.runtime.lastUsageDate.toLocaleDateString()
+            : null,
+        value: props.runtime.name,
+      }}
+      subtitle={{ tooltip: "Build version", value: props.runtime.buildVersion }}
+      keywords={[props.runtime.platform, props.runtime.name, props.runtime.version]}
+      accessories={[
+        ...(props.runtime.isSupported == false
+          ? [
+              {
+                icon: {
+                  source: Icon.Warning,
+                  tintColor: Color.Red,
+                },
+                tooltip: "Not supported (may be deleted)",
+              },
+            ]
+          : []),
path: extensions/xcode/src/components/xcode-runtime-list/xcode-runtime-list-item.component.tsx
user: login: greptile-apps[bot]
body: style: Consider using '!props.runtime.isSupported' instead of 'props.runtime.isSupported == false' for clarity.
created_at: 2025-04-08T22:25:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -308,4 +322,4 @@
     "lint": "ray lint",
     "publish": "ray publish"
   }
path: extensions/xcode/package.json
user: login: greptile-apps[bot]
body: style: Add newline at end-of-file.
created_at: 2025-04-08T22:27:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,81 @@
+import { XcodeRuntimeGroup } from "../models/xcode-runtime/xcode-runtime-group.model";
+import { XcodeRuntimePlatformFilter } from "../models/xcode-runtime/xcode-runtime-platform-filter.model";
+import { XcodeRuntimePlatform } from "../models/xcode-runtime/xcode-runtime-platform.model";
+import { XcodeRuntime } from "../models/xcode-runtime/xcode-runtime.model";
+import { execAsync } from "../shared/exec-async";
+import { groupBy } from "../shared/group-by";
+
+/**
+ * XcodeRuntimeService
+ */
+export class XcodeRuntimeService {
+  static async xcodeRuntimeGroups(filter: XcodeRuntimePlatformFilter): Promise<XcodeRuntimeGroup[]> {
+    const runtimes = await XcodeRuntimeService.xcodeRuntimes();
+    return groupBy(
+      runtimes.filter(
+        (value) =>
+          filter === XcodeRuntimePlatformFilter.all || value.platform === (filter as unknown as XcodeRuntimePlatform)
+      ),
+      (runtime) => runtime.platform
+    )
+      .map((group) => {
+        return {
+          platform: group.key,
+          runtimes: group.values,
+        };
+      })
+      .sort((lhs, rhs) => lhs.platform.localeCompare(rhs.platform));
+  }
+
+  /**
+   * Retrieve all installed XcodeRuntimes
+   */
+  static async xcodeRuntimes(): Promise<XcodeRuntime[]> {
+    const output = await execAsync("xcrun simctl list -j -v runtimes");
+    const runtimesResponseJSON = JSON.parse(output.stdout);
+    if (!runtimesResponseJSON || !runtimesResponseJSON.runtimes) {
+      throw [];
path: extensions/xcode/src/services/xcode-runtime.service.ts
user: login: greptile-apps[bot]
body: logic: Throwing an empty array is ambiguous. Consider throwing an Error instance for clearer error handling (e.g., throw new Error('Invalid runtime response')).

```suggestion
    if (!runtimesResponseJSON || !runtimesResponseJSON.runtimes) {
      throw new Error('Invalid runtime response: missing runtimes data');
```
created_at: 2025-04-08T22:29:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,81 @@
+import { XcodeRuntimeGroup } from "../models/xcode-runtime/xcode-runtime-group.model";
+import { XcodeRuntimePlatformFilter } from "../models/xcode-runtime/xcode-runtime-platform-filter.model";
+import { XcodeRuntimePlatform } from "../models/xcode-runtime/xcode-runtime-platform.model";
+import { XcodeRuntime } from "../models/xcode-runtime/xcode-runtime.model";
+import { execAsync } from "../shared/exec-async";
+import { groupBy } from "../shared/group-by";
+
+/**
+ * XcodeRuntimeService
+ */
+export class XcodeRuntimeService {
+  static async xcodeRuntimeGroups(filter: XcodeRuntimePlatformFilter): Promise<XcodeRuntimeGroup[]> {
+    const runtimes = await XcodeRuntimeService.xcodeRuntimes();
+    return groupBy(
+      runtimes.filter(
+        (value) =>
+          filter === XcodeRuntimePlatformFilter.all || value.platform === (filter as unknown as XcodeRuntimePlatform)
+      ),
path: extensions/xcode/src/services/xcode-runtime.service.ts
user: login: greptile-apps[bot]
body: style: Avoid using a double cast ('as unknown as'). Consider refining the filter logic with proper types.
created_at: 2025-04-08T22:29:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,81 @@
+import { XcodeRuntimeGroup } from "../models/xcode-runtime/xcode-runtime-group.model";
+import { XcodeRuntimePlatformFilter } from "../models/xcode-runtime/xcode-runtime-platform-filter.model";
+import { XcodeRuntimePlatform } from "../models/xcode-runtime/xcode-runtime-platform.model";
+import { XcodeRuntime } from "../models/xcode-runtime/xcode-runtime.model";
+import { execAsync } from "../shared/exec-async";
+import { groupBy } from "../shared/group-by";
+
+/**
+ * XcodeRuntimeService
+ */
+export class XcodeRuntimeService {
+  static async xcodeRuntimeGroups(filter: XcodeRuntimePlatformFilter): Promise<XcodeRuntimeGroup[]> {
+    const runtimes = await XcodeRuntimeService.xcodeRuntimes();
+    return groupBy(
+      runtimes.filter(
+        (value) =>
+          filter === XcodeRuntimePlatformFilter.all || value.platform === (filter as unknown as XcodeRuntimePlatform)
+      ),
+      (runtime) => runtime.platform
+    )
+      .map((group) => {
+        return {
+          platform: group.key,
+          runtimes: group.values,
+        };
+      })
+      .sort((lhs, rhs) => lhs.platform.localeCompare(rhs.platform));
+  }
+
+  /**
+   * Retrieve all installed XcodeRuntimes
+   */
+  static async xcodeRuntimes(): Promise<XcodeRuntime[]> {
+    const output = await execAsync("xcrun simctl list -j -v runtimes");
+    const runtimesResponseJSON = JSON.parse(output.stdout);
+    if (!runtimesResponseJSON || !runtimesResponseJSON.runtimes) {
+      throw [];
path: extensions/xcode/src/services/xcode-runtime.service.ts
user: login: qeude
body: We don't want to do this
created_at: 2025-04-08T22:34:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,81 @@
+import { XcodeRuntimeGroup } from "../models/xcode-runtime/xcode-runtime-group.model";
+import { XcodeRuntimePlatformFilter } from "../models/xcode-runtime/xcode-runtime-platform-filter.model";
+import { XcodeRuntimePlatform } from "../models/xcode-runtime/xcode-runtime-platform.model";
+import { XcodeRuntime } from "../models/xcode-runtime/xcode-runtime.model";
+import { execAsync } from "../shared/exec-async";
+import { groupBy } from "../shared/group-by";
+
+/**
+ * XcodeRuntimeService
+ */
+export class XcodeRuntimeService {
+  static async xcodeRuntimeGroups(filter: XcodeRuntimePlatformFilter): Promise<XcodeRuntimeGroup[]> {
+    const runtimes = await XcodeRuntimeService.xcodeRuntimes();
+    return groupBy(
+      runtimes.filter(
+        (value) =>
+          filter === XcodeRuntimePlatformFilter.all || value.platform === (filter as unknown as XcodeRuntimePlatform)
+      ),
path: extensions/xcode/src/services/xcode-runtime.service.ts
user: login: qeude
body: I think in that case we want to keep it like this
created_at: 2025-04-08T22:34:40Z
author_association: CONTRIBUTOR

PR comments:
user: login: raycastbot
created_at: 2025-04-08T22:23:51Z
body: Thank you for your  contribution! :tada:

🔔 @SvenTiigi @oliverepper @kiliankoe @3llomi @ridemountainpig you might want to have a look.

You can use [this guide](https://developers.raycast.com/basics/review-pullrequest) to learn how to check out the Pull Request locally in order to test it.

You can expect an initial review within five business days.
author_association: COLLABORATOR
user: login: pernielsentikaer
created_at: 2025-04-11T06:48:17Z
body: Hi @qeude 👋 

Thanks for your contribution 🔥

@SvenTiigi do you want to check this?
author_association: COLLABORATOR
user: login: github-actions[bot]
created_at: 2025-04-15T08:04:38Z
body: <!-- add-pr-comment:add-pr-comment -->

**Published to the Raycast Store:**
https://raycast.com/SvenTiigi/xcode
author_association: CONTRIBUTOR
user: login: raycastbot
created_at: 2025-04-15T08:04:40Z
body: :tada: :tada: :tada:

We've rewarded your Raycast account with some credits. You will soon be able to exchange them for some swag.
author_association: COLLABORATOR