From 05f0e1aea0b954944baa57de781a1f41ef7547b1 Mon Sep 17 00:00:00 2001 From: kevinwang5658 <20214115+kevinwang5658@users.noreply.github.com> Date: Mon, 29 Jun 2026 15:24:25 +0000 Subject: [PATCH 01/18] feat: Add ios-emulators resource (auto-generated from issue #72) --- docs/resources/(resources)/ios-simulator.mdx | 62 ++++++ src/index.ts | 2 + .../completions/ios-simulator.deviceType.ts | 55 +++++ .../completions/ios-simulator.runtime.ts | 38 ++++ .../ios/ios-simulator/ios-simulator.ts | 196 ++++++++++++++++++ test/ios/ios-simulator.test.ts | 54 +++++ 6 files changed, 407 insertions(+) create mode 100644 docs/resources/(resources)/ios-simulator.mdx create mode 100644 src/resources/ios/ios-simulator/completions/ios-simulator.deviceType.ts create mode 100644 src/resources/ios/ios-simulator/completions/ios-simulator.runtime.ts create mode 100644 src/resources/ios/ios-simulator/ios-simulator.ts create mode 100644 test/ios/ios-simulator.test.ts diff --git a/docs/resources/(resources)/ios-simulator.mdx b/docs/resources/(resources)/ios-simulator.mdx new file mode 100644 index 00000000..2161805a --- /dev/null +++ b/docs/resources/(resources)/ios-simulator.mdx @@ -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.`. 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.-`. 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"] + } +] +``` diff --git a/src/index.ts b/src/index.ts index a9106dd0..e2e6a0aa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -67,6 +67,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 { XcodesResource } from './resources/xcodes/xcodes-resource.js'; import { YumResource } from './resources/yum/yum.js'; @@ -87,6 +88,7 @@ runPlugin(Plugin.create( [ new GitResource(), new XcodeToolsResource(), + new IosSimulatorResource(), new XcodesResource(), new PathResource(), new AliasResource(), diff --git a/src/resources/ios/ios-simulator/completions/ios-simulator.deviceType.ts b/src/resources/ios/ios-simulator/completions/ios-simulator.deviceType.ts new file mode 100644 index 00000000..8635f10c --- /dev/null +++ b/src/resources/ios/ios-simulator/completions/ios-simulator.deviceType.ts @@ -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 { + 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', + ]; +} diff --git a/src/resources/ios/ios-simulator/completions/ios-simulator.runtime.ts b/src/resources/ios/ios-simulator/completions/ios-simulator.runtime.ts new file mode 100644 index 00000000..c781be4c --- /dev/null +++ b/src/resources/ios/ios-simulator/completions/ios-simulator.runtime.ts @@ -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 { + 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', + ]; +} diff --git a/src/resources/ios/ios-simulator/ios-simulator.ts b/src/resources/ios/ios-simulator/ios-simulator.ts new file mode 100644 index 00000000..13533ad9 --- /dev/null +++ b/src/resources/ios/ios-simulator/ios-simulator.ts @@ -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; + +interface SimDevice { + udid: string; + name: string; + state: string; + deviceTypeIdentifier: string; +} + +interface SimctlDevicesOutput { + devices: Record; +} + +const defaultConfig: Partial & { os: any } = { + name: '', + 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 { + getSettings(): ResourceSettings { + 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): Promise | 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): Promise { + 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, plan: ModifyPlan): Promise { + 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): Promise { + 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 { + 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; + } +} diff --git a/test/ios/ios-simulator.test.ts b/test/ios/ios-simulator.test.ts new file mode 100644 index 00000000..77d6e528 --- /dev/null +++ b/test/ios/ios-simulator.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, it } from 'vitest'; +import { PluginTester, testSpawn } from '@codifycli/plugin-test'; +import * as path from 'node:path'; +import { SpawnStatus, Utils } from '@codifycli/plugin-core'; + +describe('iOS Simulator tests', { skip: !Utils.isMacOS() }, async () => { + const pluginPath = path.resolve('./src/index.ts'); + + it('Can create, modify state, and destroy an iOS simulator', { timeout: 300000 }, async () => { + await PluginTester.fullTest(pluginPath, [ + { + type: 'ios-simulator', + name: 'codify-test-iphone', + deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15', + runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', + state: 'Shutdown', + }, + ], { + validateApply: async () => { + const { data, status } = await testSpawn('xcrun simctl list devices --json'); + expect(status).toBe(SpawnStatus.SUCCESS); + const parsed = JSON.parse(data); + const allDevices: any[] = Object.values(parsed.devices).flat(); + const sim = allDevices.find((d: any) => d.name === 'codify-test-iphone'); + expect(sim).toBeDefined(); + expect(sim.state).toBe('Shutdown'); + }, + testModify: { + modifiedConfigs: [{ + type: 'ios-simulator', + name: 'codify-test-iphone', + deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15', + runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', + state: 'Booted', + }], + validateModify: async () => { + const { data } = await testSpawn('xcrun simctl list devices --json'); + const parsed = JSON.parse(data); + const allDevices: any[] = Object.values(parsed.devices).flat(); + const sim = allDevices.find((d: any) => d.name === 'codify-test-iphone'); + expect(sim).toBeDefined(); + expect(sim.state).toBe('Booted'); + }, + }, + validateDestroy: async () => { + const { data } = await testSpawn('xcrun simctl list devices --json'); + const parsed = JSON.parse(data); + const allDevices: any[] = Object.values(parsed.devices).flat(); + const sim = allDevices.find((d: any) => d.name === 'codify-test-iphone'); + expect(sim).toBeUndefined(); + }, + }); + }); +}); From 807c3443b665db17557e3fcdb114766275c3c776 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Thu, 2 Jul 2026 20:47:41 -0400 Subject: [PATCH 02/18] fix: naming clash --- docs/resources/(resources)/ios-simulator.mdx | 8 +++---- .../ios/ios-simulator/ios-simulator.ts | 24 +++++++++---------- test/ios/ios-simulator.test.ts | 4 ++-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/resources/(resources)/ios-simulator.mdx b/docs/resources/(resources)/ios-simulator.mdx index 2161805a..228d176d 100644 --- a/docs/resources/(resources)/ios-simulator.mdx +++ b/docs/resources/(resources)/ios-simulator.mdx @@ -13,7 +13,7 @@ resource deletes the simulator from the system. Xcode Command Line Tools must be ## Parameters: -- **name** *(string, required)* — Human-readable name for the simulator instance (e.g. `"iPhone 15 Dev"`). Must be unique across your declared simulators. +- **simulatorName** *(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.`. Run `xcrun simctl list devicetypes` to see identifiers available on your machine. @@ -27,7 +27,7 @@ resource deletes the simulator from the system. Xcode Command Line Tools must be [ { "type": "ios-simulator", - "name": "iPhone 15 Dev", + "simulatorName": "iPhone 15 Dev", "deviceType": "com.apple.CoreSimulator.SimDeviceType.iPhone-15", "runtime": "com.apple.CoreSimulator.SimRuntime.iOS-18-0", "state": "Shutdown", @@ -44,7 +44,7 @@ resource deletes the simulator from the system. Xcode Command Line Tools must be }, { "type": "ios-simulator", - "name": "iPhone 15 Pro", + "simulatorName": "iPhone 15 Pro", "deviceType": "com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro", "runtime": "com.apple.CoreSimulator.SimRuntime.iOS-18-0", "state": "Shutdown", @@ -52,7 +52,7 @@ resource deletes the simulator from the system. Xcode Command Line Tools must be }, { "type": "ios-simulator", - "name": "iPad Pro 11-inch", + "simulatorName": "iPad Pro 11-inch", "deviceType": "com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M4", "runtime": "com.apple.CoreSimulator.SimRuntime.iOS-18-0", "state": "Shutdown", diff --git a/src/resources/ios/ios-simulator/ios-simulator.ts b/src/resources/ios/ios-simulator/ios-simulator.ts index 13533ad9..3ae738e6 100644 --- a/src/resources/ios/ios-simulator/ios-simulator.ts +++ b/src/resources/ios/ios-simulator/ios-simulator.ts @@ -13,7 +13,7 @@ import { import { OS } from '@codifycli/schemas'; const schema = z.object({ - name: z + simulatorName: z .string() .describe('Name for the iOS simulator instance (e.g. "iPhone 15 Dev")'), deviceType: z @@ -42,7 +42,7 @@ interface SimctlDevicesOutput { } const defaultConfig: Partial & { os: any } = { - name: '', + simulatorName: '', deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15', runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', state: 'Shutdown', @@ -54,7 +54,7 @@ const exampleBasic: ExampleConfig = { description: 'Create an iPhone 15 simulator running iOS 18 for use in development and UI testing.', configs: [{ type: 'ios-simulator', - name: 'iPhone 15 Dev', + simulatorName: 'iPhone 15 Dev', deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15', runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', state: 'Shutdown', @@ -69,7 +69,7 @@ const exampleMultiDevice: ExampleConfig = { { type: 'xcode-tools', os: ['macOS'] }, { type: 'ios-simulator', - name: 'iPhone 15 Pro', + simulatorName: 'iPhone 15 Pro', deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro', runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', state: 'Shutdown', @@ -77,7 +77,7 @@ const exampleMultiDevice: ExampleConfig = { }, { type: 'ios-simulator', - name: 'iPad Pro 11-inch', + simulatorName: 'iPad Pro 11-inch', deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M4', runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', state: 'Shutdown', @@ -102,7 +102,7 @@ export class IosSimulatorResource extends Resource { state: { type: 'string', canModify: true }, }, allowMultiple: { - identifyingParameters: ['name'], + identifyingParameters: ['simulatorName'], }, }; } @@ -123,10 +123,10 @@ export class IosSimulatorResource extends Resource { } for (const [runtimeId, devices] of Object.entries(parsed.devices)) { - const match = devices.find((d) => d.name === parameters.name); + const match = devices.find((d) => d.name === parameters.simulatorName); if (match) { return { - name: match.name, + simulatorName: match.name, deviceType: match.deviceTypeIdentifier, runtime: runtimeId, state: match.state === 'Booted' ? 'Booted' : 'Shutdown', @@ -139,11 +139,11 @@ export class IosSimulatorResource extends Resource { async create(plan: CreatePlan): Promise { const $ = getPty(); - const { name, deviceType, runtime, state } = plan.desiredConfig; + const { simulatorName, 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}"`, + `xcrun simctl create "${simulatorName}" "${deviceType}" "${runtime}"`, { interactive: true } ); @@ -156,7 +156,7 @@ export class IosSimulatorResource extends Resource { if (pc.name !== 'state') return; const $ = getPty(); - const udid = await this.getUdidByName(plan.desiredConfig.name); + const udid = await this.getUdidByName(plan.desiredConfig.simulatorName); if (!udid) return; if (plan.desiredConfig.state === 'Booted') { @@ -168,7 +168,7 @@ export class IosSimulatorResource extends Resource { async destroy(plan: DestroyPlan): Promise { const $ = getPty(); - const udid = await this.getUdidByName(plan.currentConfig.name); + const udid = await this.getUdidByName(plan.currentConfig.simulatorName); if (!udid) return; await $.spawn(`xcrun simctl delete "${udid}"`, { interactive: true }); diff --git a/test/ios/ios-simulator.test.ts b/test/ios/ios-simulator.test.ts index 77d6e528..203795d6 100644 --- a/test/ios/ios-simulator.test.ts +++ b/test/ios/ios-simulator.test.ts @@ -10,7 +10,7 @@ describe('iOS Simulator tests', { skip: !Utils.isMacOS() }, async () => { await PluginTester.fullTest(pluginPath, [ { type: 'ios-simulator', - name: 'codify-test-iphone', + simulatorName: 'codify-test-iphone', deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15', runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', state: 'Shutdown', @@ -28,7 +28,7 @@ describe('iOS Simulator tests', { skip: !Utils.isMacOS() }, async () => { testModify: { modifiedConfigs: [{ type: 'ios-simulator', - name: 'codify-test-iphone', + simulatorName: 'codify-test-iphone', deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15', runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', state: 'Booted', From 8bae0a087b46b97b992e0234f7485cdf968b3a27 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Thu, 2 Jul 2026 22:39:04 -0400 Subject: [PATCH 03/18] feat: changed simulators into an array, skip tests if xcode isn't already installed and add check if xcode is installed inside the create --- docs/resources/(resources)/ios-simulator.mdx | 59 ++--- .../ios/ios-simulator/ios-simulator.ts | 231 +++++++++++------- test/ios/ios-simulator.test.ts | 36 ++- 3 files changed, 198 insertions(+), 128 deletions(-) diff --git a/docs/resources/(resources)/ios-simulator.mdx b/docs/resources/(resources)/ios-simulator.mdx index 228d176d..8b5c944a 100644 --- a/docs/resources/(resources)/ios-simulator.mdx +++ b/docs/resources/(resources)/ios-simulator.mdx @@ -4,22 +4,19 @@ 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 +using `xcrun simctl`. A single resource declaration manages a list of simulators, making it easy to +define a full testing matrix across device types and OS versions in one place. Simulators are created +with the specified device type and runtime, and optionally booted. Removing the resource deletes all +declared simulators 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: -- **simulatorName** *(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.`. 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.-`. 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. +- **simulators** *(object[], optional)* — List of simulators to create and manage. Each entry has: + - **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.`. 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.-`. 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: @@ -27,10 +24,14 @@ resource deletes the simulator from the system. Xcode Command Line Tools must be [ { "type": "ios-simulator", - "simulatorName": "iPhone 15 Dev", - "deviceType": "com.apple.CoreSimulator.SimDeviceType.iPhone-15", - "runtime": "com.apple.CoreSimulator.SimRuntime.iOS-18-0", - "state": "Shutdown", + "simulators": [ + { + "name": "iPhone 15 Dev", + "deviceType": "com.apple.CoreSimulator.SimDeviceType.iPhone-15", + "runtime": "com.apple.CoreSimulator.SimRuntime.iOS-18-0", + "state": "Shutdown" + } + ], "os": ["macOS"] } ] @@ -44,18 +45,20 @@ resource deletes the simulator from the system. Xcode Command Line Tools must be }, { "type": "ios-simulator", - "simulatorName": "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", - "simulatorName": "iPad Pro 11-inch", - "deviceType": "com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M4", - "runtime": "com.apple.CoreSimulator.SimRuntime.iOS-18-0", - "state": "Shutdown", + "simulators": [ + { + "name": "iPhone 15 Pro", + "deviceType": "com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro", + "runtime": "com.apple.CoreSimulator.SimRuntime.iOS-18-0", + "state": "Shutdown" + }, + { + "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"] } ] diff --git a/src/resources/ios/ios-simulator/ios-simulator.ts b/src/resources/ios/ios-simulator/ios-simulator.ts index 3ae738e6..b61c0049 100644 --- a/src/resources/ios/ios-simulator/ios-simulator.ts +++ b/src/resources/ios/ios-simulator/ios-simulator.ts @@ -12,20 +12,23 @@ import { } from '@codifycli/plugin-core'; import { OS } from '@codifycli/schemas'; -const schema = z.object({ - simulatorName: 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")'), +const simulatorSchema = z.object({ + name: z.string().describe('Name for the 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.'), + .describe('Desired runtime state. Defaults to Shutdown.'), +}); + +export type SimulatorDeclaration = z.infer; + +const schema = z.object({ + simulators: z + .array(simulatorSchema) + .optional() + .describe('List of iOS simulators to create and manage.'), }); export type IosSimulatorConfig = z.infer; @@ -42,10 +45,7 @@ interface SimctlDevicesOutput { } const defaultConfig: Partial & { os: any } = { - simulatorName: '', - deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15', - runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', - state: 'Shutdown', + simulators: [], os: ['macOS'], }; @@ -54,10 +54,14 @@ const exampleBasic: ExampleConfig = { description: 'Create an iPhone 15 simulator running iOS 18 for use in development and UI testing.', configs: [{ type: 'ios-simulator', - simulatorName: 'iPhone 15 Dev', - deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15', - runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', - state: 'Shutdown', + simulators: [ + { + name: 'iPhone 15 Dev', + deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15', + runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', + state: 'Shutdown', + }, + ], os: ['macOS'], }], }; @@ -69,18 +73,20 @@ const exampleMultiDevice: ExampleConfig = { { type: 'xcode-tools', os: ['macOS'] }, { type: 'ios-simulator', - simulatorName: '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', - simulatorName: 'iPad Pro 11-inch', - deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M4', - runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', - state: 'Shutdown', + simulators: [ + { + name: 'iPhone 15 Pro', + deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro', + runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', + state: 'Shutdown', + }, + { + 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'], }, ], @@ -99,98 +105,143 @@ export class IosSimulatorResource extends Resource { dependencies: ['xcode-tools'], schema, parameterSettings: { - state: { type: 'string', canModify: true }, - }, - allowMultiple: { - identifyingParameters: ['simulatorName'], + simulators: { + type: 'array', + itemType: 'object', + isElementEqual: (a, b) => + a.name === b.name && + a.deviceType === b.deviceType && + a.runtime === b.runtime && + a.state === b.state, + filterInStatelessMode: (desired, current) => + current.filter((c) => desired.some((d) => d.name === c.name)), + canModify: true, + }, }, }; } - async refresh(parameters: Partial): Promise | null> { - const $ = getPty(); - - const { status, data } = await $.spawnSafe('xcrun simctl list devices --json'); - if (status !== SpawnStatus.SUCCESS) { - return null; - } + async refresh(): Promise | null> { + const allDevices = await this.listAllDevices(); + if (!allDevices) 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.simulatorName); - if (match) { - return { - simulatorName: match.name, - deviceType: match.deviceTypeIdentifier, + const simulators: SimulatorDeclaration[] = []; + for (const [runtimeId, devices] of Object.entries(allDevices)) { + for (const device of devices) { + simulators.push({ + name: device.name, + deviceType: device.deviceTypeIdentifier, runtime: runtimeId, - state: match.state === 'Booted' ? 'Booted' : 'Shutdown', - }; + state: device.state === 'Booted' ? 'Booted' : 'Shutdown', + }); } } - return null; + return simulators.length > 0 ? { simulators } : null; } async create(plan: CreatePlan): Promise { + await this.assertSimctlAvailable(); const $ = getPty(); - const { simulatorName, deviceType, runtime, state } = plan.desiredConfig; - - // xcrun simctl create prints the new simulator's UDID to stdout - const { data: udid } = await $.spawn( - `xcrun simctl create "${simulatorName}" "${deviceType}" "${runtime}"`, - { interactive: true } - ); - - if (state === 'Booted') { - await $.spawn(`xcrun simctl boot "${udid.trim()}"`, { interactive: true }); + for (const sim of plan.desiredConfig.simulators ?? []) { + const { data: udid } = await $.spawn( + `xcrun simctl create "${sim.name}" "${sim.deviceType}" "${sim.runtime}"`, + { interactive: true }, + ); + if (sim.state === 'Booted') { + await $.spawn(`xcrun simctl boot "${udid.trim()}"`, { interactive: true }); + } } } - async modify(pc: ParameterChange, plan: ModifyPlan): Promise { - if (pc.name !== 'state') return; + async modify(pc: ParameterChange, _plan: ModifyPlan): Promise { + if (pc.name !== 'simulators') return; const $ = getPty(); - const udid = await this.getUdidByName(plan.desiredConfig.simulatorName); - if (!udid) return; + const allDevices = await this.listAllDevices(); + if (!allDevices) return; - if (plan.desiredConfig.state === 'Booted') { - await $.spawn(`xcrun simctl boot "${udid}"`, { interactive: true }); - } else { - await $.spawn(`xcrun simctl shutdown "${udid}"`, { interactive: true }); + const findUdid = (name: string): string | undefined => { + for (const devices of Object.values(allDevices)) { + const match = devices.find((d) => d.name === name); + if (match) return match.udid; + } + }; + + const previous: SimulatorDeclaration[] = pc.previousValue ?? []; + const desired: SimulatorDeclaration[] = pc.newValue ?? []; + + // Remove simulators no longer declared + const toRemove = previous.filter((p) => !desired.some((d) => d.name === p.name)); + for (const sim of toRemove) { + const udid = findUdid(sim.name); + if (udid) await $.spawn(`xcrun simctl delete "${udid}"`, { interactive: true }); + } + + // Add newly declared simulators + const toAdd = desired.filter((d) => !previous.some((p) => p.name === d.name)); + for (const sim of toAdd) { + const { data: udid } = await $.spawn( + `xcrun simctl create "${sim.name}" "${sim.deviceType}" "${sim.runtime}"`, + { interactive: true }, + ); + if (sim.state === 'Booted') { + await $.spawn(`xcrun simctl boot "${udid.trim()}"`, { interactive: true }); + } + } + + // Update state for simulators that changed only their state + const stateChanged = desired.filter((d) => { + const prev = previous.find((p) => p.name === d.name); + return prev && prev.state !== d.state; + }); + for (const sim of stateChanged) { + const udid = findUdid(sim.name); + if (!udid) continue; + if (sim.state === 'Booted') { + await $.spawn(`xcrun simctl boot "${udid}"`, { interactive: true }); + } else { + await $.spawn(`xcrun simctl shutdown "${udid}"`, { interactive: true }); + } } } async destroy(plan: DestroyPlan): Promise { const $ = getPty(); - const udid = await this.getUdidByName(plan.currentConfig.simulatorName); - if (!udid) return; - - await $.spawn(`xcrun simctl delete "${udid}"`, { interactive: true }); + const allDevices = await this.listAllDevices(); + if (!allDevices) return; + + for (const sim of plan.currentConfig.simulators ?? []) { + for (const devices of Object.values(allDevices)) { + const match = devices.find((d) => d.name === sim.name); + if (match) { + await $.spawn(`xcrun simctl delete "${match.udid}"`, { interactive: true }); + break; + } + } + } } - private async getUdidByName(name: string | undefined): Promise { - if (!name) return null; + private async assertSimctlAvailable(): Promise { + const $ = getPty(); + const { status } = await $.spawnSafe('xcrun simctl help'); + if (status !== SpawnStatus.SUCCESS) { + throw new Error( + 'xcrun simctl is not available. Xcode must be installed to manage iOS simulators. ' + + 'Install it manually from the Mac App Store or use the xcodes resource to manage Xcode versions.', + ); + } + } + private async listAllDevices(): Promise | 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; - } + return parsed.devices; } catch { - // ignore parse errors + return null; } - - return null; } } diff --git a/test/ios/ios-simulator.test.ts b/test/ios/ios-simulator.test.ts index 203795d6..5b0f7941 100644 --- a/test/ios/ios-simulator.test.ts +++ b/test/ios/ios-simulator.test.ts @@ -1,19 +1,31 @@ import { describe, expect, it } from 'vitest'; import { PluginTester, testSpawn } from '@codifycli/plugin-test'; import * as path from 'node:path'; +import { spawnSync } from 'node:child_process'; import { SpawnStatus, Utils } from '@codifycli/plugin-core'; -describe('iOS Simulator tests', { skip: !Utils.isMacOS() }, async () => { +function isXcodeInstalled(): boolean { + const result = spawnSync('xcrun', ['simctl', 'help'], { stdio: 'ignore' }); + return result.status === 0; +} + +const skip = !Utils.isMacOS() || !isXcodeInstalled(); + +describe('iOS Simulator tests', { skip }, async () => { const pluginPath = path.resolve('./src/index.ts'); - it('Can create, modify state, and destroy an iOS simulator', { timeout: 300000 }, async () => { + it('Can create, modify state, and destroy iOS simulators', { timeout: 300000 }, async () => { await PluginTester.fullTest(pluginPath, [ { type: 'ios-simulator', - simulatorName: 'codify-test-iphone', - deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15', - runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', - state: 'Shutdown', + simulators: [ + { + name: 'codify-test-iphone', + deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15', + runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', + state: 'Shutdown', + }, + ], }, ], { validateApply: async () => { @@ -28,10 +40,14 @@ describe('iOS Simulator tests', { skip: !Utils.isMacOS() }, async () => { testModify: { modifiedConfigs: [{ type: 'ios-simulator', - simulatorName: 'codify-test-iphone', - deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15', - runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', - state: 'Booted', + simulators: [ + { + name: 'codify-test-iphone', + deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15', + runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', + state: 'Booted', + }, + ], }], validateModify: async () => { const { data } = await testSpawn('xcrun simctl list devices --json'); From ddb837e2c5cee5e8f671e6d62ecde70e685c3556 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Fri, 3 Jul 2026 09:57:16 -0400 Subject: [PATCH 04/18] feat: increase max turns for test jobs for generate resource action --- .github/workflows/resource-request.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/resource-request.yml b/.github/workflows/resource-request.yml index 7005a40a..5bc56933 100644 --- a/.github/workflows/resource-request.yml +++ b/.github/workflows/resource-request.yml @@ -240,7 +240,7 @@ jobs: uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - claude_args: "--allowedTools Bash,Read,Edit,Write --max-turns 30" + claude_args: "--allowedTools Bash,Read,Edit,Write --max-turns 120" prompt: | The integration tests for the newly generated resource failed on Linux (ubuntu-latest). @@ -343,7 +343,7 @@ jobs: uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - claude_args: "--allowedTools Bash,Read,Edit,Write --max-turns 30" + claude_args: "--allowedTools Bash,Read,Edit,Write --max-turns 120" prompt: | The integration tests for the newly generated resource failed on macOS (macos-latest). From 1f2b05877d9114b22f11b905c999ce22ee71fc6f Mon Sep 17 00:00:00 2001 From: kevinwang Date: Fri, 3 Jul 2026 10:02:46 -0400 Subject: [PATCH 05/18] feat: changed name to ios-simulators to reflect plural form. Removed state parameter --- .../src/__generated__/completions-index.ts | 44 ++++++++++--------- .../{ios-simulator.mdx => ios-simulators.mdx} | 15 +++---- package.json | 2 +- ...> ios-simulators.simulators.deviceType.ts} | 0 ...s => ios-simulators.simulators.runtime.ts} | 0 .../ios/ios-simulator/ios-simulator.ts | 44 +++---------------- test/ios/ios-simulator.test.ts | 26 +++++------ 7 files changed, 51 insertions(+), 80 deletions(-) rename docs/resources/(resources)/{ios-simulator.mdx => ios-simulators.mdx} (82%) rename src/resources/ios/ios-simulator/completions/{ios-simulator.deviceType.ts => ios-simulators.simulators.deviceType.ts} (100%) rename src/resources/ios/ios-simulator/completions/{ios-simulator.runtime.ts => ios-simulators.simulators.runtime.ts} (100%) diff --git a/completions-cron/src/__generated__/completions-index.ts b/completions-cron/src/__generated__/completions-index.ts index e79ceb68..65c1e593 100644 --- a/completions-cron/src/__generated__/completions-index.ts +++ b/completions-cron/src/__generated__/completions-index.ts @@ -22,16 +22,18 @@ import mod17 from '../../../src/resources/jetbrains/clion/completions/clion.plug import mod18 from '../../../src/resources/javascript/pnpm/completions/pnpm.globalEnvNodeVersion.js'; import mod19 from '../../../src/resources/javascript/nvm/completions/nvm.nodeVersions.js'; import mod20 from '../../../src/resources/javascript/npm/completions/npm.install.js'; -import mod21 from '../../../src/resources/homebrew/completions/homebrew.formulae.js'; -import mod22 from '../../../src/resources/homebrew/completions/homebrew.casks.js'; -import mod23 from '../../../src/resources/cursor/completions/cursor.extensions.js'; -import mod24 from '../../../src/resources/codex/completions/codex.config.model.js'; -import mod25 from '../../../src/resources/asdf/completions/asdf.plugins.js'; -import mod26 from '../../../src/resources/asdf/completions/asdf-plugin.plugin.js'; -import mod27 from '../../../src/resources/apt/completions/apt.install.js'; -import mod28 from '../../../src/resources/android/android-studios/completions/android-studio.version.js'; -import mod29 from '../../../src/resources/android/android-cli/completions/android-cli.sdkPackages.js'; -import mod30 from '../../../src/resources/android/android-cli/completions/android-cli.emulators.js'; +import mod21 from '../../../src/resources/ios/ios-simulator/completions/ios-simulators.simulators.runtime.js'; +import mod22 from '../../../src/resources/ios/ios-simulator/completions/ios-simulators.simulators.deviceType.js'; +import mod23 from '../../../src/resources/homebrew/completions/homebrew.formulae.js'; +import mod24 from '../../../src/resources/homebrew/completions/homebrew.casks.js'; +import mod25 from '../../../src/resources/cursor/completions/cursor.extensions.js'; +import mod26 from '../../../src/resources/codex/completions/codex.config.model.js'; +import mod27 from '../../../src/resources/asdf/completions/asdf.plugins.js'; +import mod28 from '../../../src/resources/asdf/completions/asdf-plugin.plugin.js'; +import mod29 from '../../../src/resources/apt/completions/apt.install.js'; +import mod30 from '../../../src/resources/android/android-studios/completions/android-studio.version.js'; +import mod31 from '../../../src/resources/android/android-cli/completions/android-cli.sdkPackages.js'; +import mod32 from '../../../src/resources/android/android-cli/completions/android-cli.emulators.js'; export interface CompletionModule { resourceType: string @@ -61,14 +63,16 @@ export const completionModules: CompletionModule[] = [ { resourceType: 'pnpm', parameterPath: '/globalEnvNodeVersion', fetch: mod18 }, { resourceType: 'nvm', parameterPath: '/nodeVersions', fetch: mod19 }, { resourceType: 'npm', parameterPath: '/install', fetch: mod20 }, - { resourceType: 'homebrew', parameterPath: '/formulae', fetch: mod21 }, - { resourceType: 'homebrew', parameterPath: '/casks', fetch: mod22 }, - { resourceType: 'cursor', parameterPath: '/extensions', fetch: mod23 }, - { resourceType: 'codex', parameterPath: '/config/model', fetch: mod24 }, - { resourceType: 'asdf', parameterPath: '/plugins', fetch: mod25 }, - { resourceType: 'asdf-plugin', parameterPath: '/plugin', fetch: mod26 }, - { resourceType: 'apt', parameterPath: '/install', fetch: mod27 }, - { resourceType: 'android-studio', parameterPath: '/version', fetch: mod28 }, - { resourceType: 'android-cli', parameterPath: '/sdkPackages', fetch: mod29 }, - { resourceType: 'android-cli', parameterPath: '/emulators', fetch: mod30 }, + { resourceType: 'ios-simulators', parameterPath: '/simulators/runtime', fetch: mod21 }, + { resourceType: 'ios-simulators', parameterPath: '/simulators/deviceType', fetch: mod22 }, + { resourceType: 'homebrew', parameterPath: '/formulae', fetch: mod23 }, + { resourceType: 'homebrew', parameterPath: '/casks', fetch: mod24 }, + { resourceType: 'cursor', parameterPath: '/extensions', fetch: mod25 }, + { resourceType: 'codex', parameterPath: '/config/model', fetch: mod26 }, + { resourceType: 'asdf', parameterPath: '/plugins', fetch: mod27 }, + { resourceType: 'asdf-plugin', parameterPath: '/plugin', fetch: mod28 }, + { resourceType: 'apt', parameterPath: '/install', fetch: mod29 }, + { resourceType: 'android-studio', parameterPath: '/version', fetch: mod30 }, + { resourceType: 'android-cli', parameterPath: '/sdkPackages', fetch: mod31 }, + { resourceType: 'android-cli', parameterPath: '/emulators', fetch: mod32 }, ] diff --git a/docs/resources/(resources)/ios-simulator.mdx b/docs/resources/(resources)/ios-simulators.mdx similarity index 82% rename from docs/resources/(resources)/ios-simulator.mdx rename to docs/resources/(resources)/ios-simulators.mdx index 8b5c944a..3032ffcc 100644 --- a/docs/resources/(resources)/ios-simulator.mdx +++ b/docs/resources/(resources)/ios-simulators.mdx @@ -5,8 +5,7 @@ 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`. A single resource declaration manages a list of simulators, making it easy to -define a full testing matrix across device types and OS versions in one place. Simulators are created -with the specified device type and runtime, and optionally booted. Removing the resource deletes all +define a full testing matrix across device types and OS versions in one place. Simulators are created with the specified device type and runtime. Removing the resource deletes all declared simulators 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. @@ -16,20 +15,20 @@ declared simulators from the system. Xcode Command Line Tools must be installed - **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.`. 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.-`. 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", + "type": "ios-simulators", "simulators": [ { "name": "iPhone 15 Dev", "deviceType": "com.apple.CoreSimulator.SimDeviceType.iPhone-15", "runtime": "com.apple.CoreSimulator.SimRuntime.iOS-18-0", - "state": "Shutdown" + } ], "os": ["macOS"] @@ -44,19 +43,19 @@ declared simulators from the system. Xcode Command Line Tools must be installed "os": ["macOS"] }, { - "type": "ios-simulator", + "type": "ios-simulators", "simulators": [ { "name": "iPhone 15 Pro", "deviceType": "com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro", "runtime": "com.apple.CoreSimulator.SimRuntime.iOS-18-0", - "state": "Shutdown" + }, { "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"] diff --git a/package.json b/package.json index aa3a8119..2adc4542 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "default", - "version": "1.14.0", + "version": "1.15.0-beta.2", "description": "Default plugin for Codify - provides 50+ declarative resources for managing development tools and system configuration across macOS and Linux", "main": "dist/index.js", "scripts": { diff --git a/src/resources/ios/ios-simulator/completions/ios-simulator.deviceType.ts b/src/resources/ios/ios-simulator/completions/ios-simulators.simulators.deviceType.ts similarity index 100% rename from src/resources/ios/ios-simulator/completions/ios-simulator.deviceType.ts rename to src/resources/ios/ios-simulator/completions/ios-simulators.simulators.deviceType.ts diff --git a/src/resources/ios/ios-simulator/completions/ios-simulator.runtime.ts b/src/resources/ios/ios-simulator/completions/ios-simulators.simulators.runtime.ts similarity index 100% rename from src/resources/ios/ios-simulator/completions/ios-simulator.runtime.ts rename to src/resources/ios/ios-simulator/completions/ios-simulators.simulators.runtime.ts diff --git a/src/resources/ios/ios-simulator/ios-simulator.ts b/src/resources/ios/ios-simulator/ios-simulator.ts index b61c0049..a8a68a48 100644 --- a/src/resources/ios/ios-simulator/ios-simulator.ts +++ b/src/resources/ios/ios-simulator/ios-simulator.ts @@ -16,10 +16,6 @@ const simulatorSchema = z.object({ name: z.string().describe('Name for the 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. Defaults to Shutdown.'), }); export type SimulatorDeclaration = z.infer; @@ -53,13 +49,12 @@ 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', + type: 'ios-simulators', simulators: [ { name: 'iPhone 15 Dev', deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15', runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', - state: 'Shutdown', }, ], os: ['macOS'], @@ -72,19 +67,17 @@ const exampleMultiDevice: ExampleConfig = { configs: [ { type: 'xcode-tools', os: ['macOS'] }, { - type: 'ios-simulator', + type: 'ios-simulators', simulators: [ { name: 'iPhone 15 Pro', deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro', runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', - state: 'Shutdown', }, { 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'], @@ -95,7 +88,7 @@ const exampleMultiDevice: ExampleConfig = { export class IosSimulatorResource extends Resource { getSettings(): ResourceSettings { return { - id: 'ios-simulator', + id: 'ios-simulators', defaultConfig, exampleConfigs: { example1: exampleBasic, @@ -111,8 +104,7 @@ export class IosSimulatorResource extends Resource { isElementEqual: (a, b) => a.name === b.name && a.deviceType === b.deviceType && - a.runtime === b.runtime && - a.state === b.state, + a.runtime === b.runtime, filterInStatelessMode: (desired, current) => current.filter((c) => desired.some((d) => d.name === c.name)), canModify: true, @@ -132,7 +124,6 @@ export class IosSimulatorResource extends Resource { name: device.name, deviceType: device.deviceTypeIdentifier, runtime: runtimeId, - state: device.state === 'Booted' ? 'Booted' : 'Shutdown', }); } } @@ -144,13 +135,10 @@ export class IosSimulatorResource extends Resource { await this.assertSimctlAvailable(); const $ = getPty(); for (const sim of plan.desiredConfig.simulators ?? []) { - const { data: udid } = await $.spawn( + await $.spawn( `xcrun simctl create "${sim.name}" "${sim.deviceType}" "${sim.runtime}"`, { interactive: true }, ); - if (sim.state === 'Booted') { - await $.spawn(`xcrun simctl boot "${udid.trim()}"`, { interactive: true }); - } } } @@ -171,38 +159,18 @@ export class IosSimulatorResource extends Resource { const previous: SimulatorDeclaration[] = pc.previousValue ?? []; const desired: SimulatorDeclaration[] = pc.newValue ?? []; - // Remove simulators no longer declared const toRemove = previous.filter((p) => !desired.some((d) => d.name === p.name)); for (const sim of toRemove) { const udid = findUdid(sim.name); if (udid) await $.spawn(`xcrun simctl delete "${udid}"`, { interactive: true }); } - // Add newly declared simulators const toAdd = desired.filter((d) => !previous.some((p) => p.name === d.name)); for (const sim of toAdd) { - const { data: udid } = await $.spawn( + await $.spawn( `xcrun simctl create "${sim.name}" "${sim.deviceType}" "${sim.runtime}"`, { interactive: true }, ); - if (sim.state === 'Booted') { - await $.spawn(`xcrun simctl boot "${udid.trim()}"`, { interactive: true }); - } - } - - // Update state for simulators that changed only their state - const stateChanged = desired.filter((d) => { - const prev = previous.find((p) => p.name === d.name); - return prev && prev.state !== d.state; - }); - for (const sim of stateChanged) { - const udid = findUdid(sim.name); - if (!udid) continue; - if (sim.state === 'Booted') { - await $.spawn(`xcrun simctl boot "${udid}"`, { interactive: true }); - } else { - await $.spawn(`xcrun simctl shutdown "${udid}"`, { interactive: true }); - } } } diff --git a/test/ios/ios-simulator.test.ts b/test/ios/ios-simulator.test.ts index 5b0f7941..eb3ad4be 100644 --- a/test/ios/ios-simulator.test.ts +++ b/test/ios/ios-simulator.test.ts @@ -14,16 +14,15 @@ const skip = !Utils.isMacOS() || !isXcodeInstalled(); describe('iOS Simulator tests', { skip }, async () => { const pluginPath = path.resolve('./src/index.ts'); - it('Can create, modify state, and destroy iOS simulators', { timeout: 300000 }, async () => { + it('Can create, add a simulator, and destroy iOS simulators', { timeout: 300000 }, async () => { await PluginTester.fullTest(pluginPath, [ { - type: 'ios-simulator', + type: 'ios-simulators', simulators: [ { name: 'codify-test-iphone', deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15', runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', - state: 'Shutdown', }, ], }, @@ -33,19 +32,21 @@ describe('iOS Simulator tests', { skip }, async () => { expect(status).toBe(SpawnStatus.SUCCESS); const parsed = JSON.parse(data); const allDevices: any[] = Object.values(parsed.devices).flat(); - const sim = allDevices.find((d: any) => d.name === 'codify-test-iphone'); - expect(sim).toBeDefined(); - expect(sim.state).toBe('Shutdown'); + expect(allDevices.find((d: any) => d.name === 'codify-test-iphone')).toBeDefined(); }, testModify: { modifiedConfigs: [{ - type: 'ios-simulator', + type: 'ios-simulators', simulators: [ { name: 'codify-test-iphone', deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15', runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', - state: 'Booted', + }, + { + name: 'codify-test-ipad', + deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPad-mini-6th-generation', + runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', }, ], }], @@ -53,17 +54,16 @@ describe('iOS Simulator tests', { skip }, async () => { const { data } = await testSpawn('xcrun simctl list devices --json'); const parsed = JSON.parse(data); const allDevices: any[] = Object.values(parsed.devices).flat(); - const sim = allDevices.find((d: any) => d.name === 'codify-test-iphone'); - expect(sim).toBeDefined(); - expect(sim.state).toBe('Booted'); + expect(allDevices.find((d: any) => d.name === 'codify-test-iphone')).toBeDefined(); + expect(allDevices.find((d: any) => d.name === 'codify-test-ipad')).toBeDefined(); }, }, validateDestroy: async () => { const { data } = await testSpawn('xcrun simctl list devices --json'); const parsed = JSON.parse(data); const allDevices: any[] = Object.values(parsed.devices).flat(); - const sim = allDevices.find((d: any) => d.name === 'codify-test-iphone'); - expect(sim).toBeUndefined(); + expect(allDevices.find((d: any) => d.name === 'codify-test-iphone')).toBeUndefined(); + expect(allDevices.find((d: any) => d.name === 'codify-test-ipad')).toBeUndefined(); }, }); }); From dbbed5a6ddb06ad9e2bbb97f7183e3d55c9bb7f4 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Fri, 3 Jul 2026 10:49:53 -0400 Subject: [PATCH 06/18] feat: updated completions path to JSONPath to handle more complicated matching cases (such as matching objects inside array elements) --- CLAUDE.md | 15 +- .../src/__generated__/completions-index.ts | 132 +++++++++--------- package.json | 2 +- scripts/generate-completions-index.ts | 7 +- ...mulators.ts => android-cli.$.emulators.ts} | 0 ...ckages.ts => android-cli.$.sdkPackages.ts} | 0 ...version.ts => android-studio.$.version.ts} | 0 .../{apt.install.ts => apt.$.install.ts} | 0 ...ugin.plugin.ts => asdf-plugin.$.plugin.ts} | 0 .../{asdf.plugins.ts => asdf.$.plugins.ts} | 0 ...onfig.model.ts => codex.$.config.model.ts} | 0 ...r.extensions.ts => cursor.$.extensions.ts} | 0 ...{homebrew.casks.ts => homebrew.$.casks.ts} | 0 ...rew.formulae.ts => homebrew.$.formulae.ts} | 0 ...-simulators.$.simulators[x].deviceType.ts} | 0 ...ios-simulators.$.simulators[x].runtime.ts} | 0 .../{npm.install.ts => npm.$.install.ts} | 0 ....nodeVersions.ts => nvm.$.nodeVersions.ts} | 0 ...sion.ts => pnpm.$.globalEnvNodeVersion.ts} | 0 .../{clion.plugins.ts => clion.$.plugins.ts} | 0 ...{goland.plugins.ts => goland.$.plugins.ts} | 0 ....plugins.ts => intellij-idea.$.plugins.ts} | 0 ...storm.plugins.ts => phpstorm.$.plugins.ts} | 0 ...ycharm.plugins.ts => pycharm.$.plugins.ts} | 0 .../{rider.plugins.ts => rider.$.plugins.ts} | 0 ...ymine.plugins.ts => rubymine.$.plugins.ts} | 0 ...over.plugins.ts => rustrover.$.plugins.ts} | 0 .../{ollama.models.ts => ollama.$.models.ts} | 0 .../{pip.install.ts => pip.$.install.ts} | 0 ...nVersions.ts => pyenv.$.pythonVersions.ts} | 0 ...thonVersions.ts => uv.$.pythonVersions.ts} | 0 .../{uv.tools.ts => uv.$.tools.ts} | 0 ...ubyVersions.ts => rbenv.$.rubyVersions.ts} | 0 .../{snap.install.ts => snap.$.install.ts} | 0 ...e.extensions.ts => vscode.$.extensions.ts} | 0 ...storm.plugins.ts => webstorm.$.plugins.ts} | 0 ...eVersions.ts => xcodes.$.xcodeVersions.ts} | 0 37 files changed, 82 insertions(+), 74 deletions(-) rename src/resources/android/android-cli/completions/{android-cli.emulators.ts => android-cli.$.emulators.ts} (100%) rename src/resources/android/android-cli/completions/{android-cli.sdkPackages.ts => android-cli.$.sdkPackages.ts} (100%) rename src/resources/android/android-studios/completions/{android-studio.version.ts => android-studio.$.version.ts} (100%) rename src/resources/apt/completions/{apt.install.ts => apt.$.install.ts} (100%) rename src/resources/asdf/completions/{asdf-plugin.plugin.ts => asdf-plugin.$.plugin.ts} (100%) rename src/resources/asdf/completions/{asdf.plugins.ts => asdf.$.plugins.ts} (100%) rename src/resources/codex/completions/{codex.config.model.ts => codex.$.config.model.ts} (100%) rename src/resources/cursor/completions/{cursor.extensions.ts => cursor.$.extensions.ts} (100%) rename src/resources/homebrew/completions/{homebrew.casks.ts => homebrew.$.casks.ts} (100%) rename src/resources/homebrew/completions/{homebrew.formulae.ts => homebrew.$.formulae.ts} (100%) rename src/resources/ios/ios-simulator/completions/{ios-simulators.simulators.deviceType.ts => ios-simulators.$.simulators[x].deviceType.ts} (100%) rename src/resources/ios/ios-simulator/completions/{ios-simulators.simulators.runtime.ts => ios-simulators.$.simulators[x].runtime.ts} (100%) rename src/resources/javascript/npm/completions/{npm.install.ts => npm.$.install.ts} (100%) rename src/resources/javascript/nvm/completions/{nvm.nodeVersions.ts => nvm.$.nodeVersions.ts} (100%) rename src/resources/javascript/pnpm/completions/{pnpm.globalEnvNodeVersion.ts => pnpm.$.globalEnvNodeVersion.ts} (100%) rename src/resources/jetbrains/clion/completions/{clion.plugins.ts => clion.$.plugins.ts} (100%) rename src/resources/jetbrains/goland/completions/{goland.plugins.ts => goland.$.plugins.ts} (100%) rename src/resources/jetbrains/intellij-idea/completions/{intellij-idea.plugins.ts => intellij-idea.$.plugins.ts} (100%) rename src/resources/jetbrains/phpstorm/completions/{phpstorm.plugins.ts => phpstorm.$.plugins.ts} (100%) rename src/resources/jetbrains/pycharm/completions/{pycharm.plugins.ts => pycharm.$.plugins.ts} (100%) rename src/resources/jetbrains/rider/completions/{rider.plugins.ts => rider.$.plugins.ts} (100%) rename src/resources/jetbrains/rubymine/completions/{rubymine.plugins.ts => rubymine.$.plugins.ts} (100%) rename src/resources/jetbrains/rustrover/completions/{rustrover.plugins.ts => rustrover.$.plugins.ts} (100%) rename src/resources/ollama/completions/{ollama.models.ts => ollama.$.models.ts} (100%) rename src/resources/python/pip/completions/{pip.install.ts => pip.$.install.ts} (100%) rename src/resources/python/pyenv/completions/{pyenv.pythonVersions.ts => pyenv.$.pythonVersions.ts} (100%) rename src/resources/python/uv/completions/{uv.pythonVersions.ts => uv.$.pythonVersions.ts} (100%) rename src/resources/python/uv/completions/{uv.tools.ts => uv.$.tools.ts} (100%) rename src/resources/ruby/rbenv/completions/{rbenv.rubyVersions.ts => rbenv.$.rubyVersions.ts} (100%) rename src/resources/snap/completions/{snap.install.ts => snap.$.install.ts} (100%) rename src/resources/vscode/completions/{vscode.extensions.ts => vscode.$.extensions.ts} (100%) rename src/resources/webstorm/completions/{webstorm.plugins.ts => webstorm.$.plugins.ts} (100%) rename src/resources/xcodes/completions/{xcodes.xcodeVersions.ts => xcodes.$.xcodeVersions.ts} (100%) diff --git a/CLAUDE.md b/CLAUDE.md index d4494a7f..e31892ac 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -369,11 +369,18 @@ The Codify Editor supports auto-complete for certain resource parameters (e.g. H ### Adding completions for a parameter -1. Create `src/resources///completions/..ts` +1. Create `src/resources///completions/..ts` 2. Export a default async function returning `Promise` — fetch the values, return them, nothing else -3. The filename determines the Supabase metadata automatically: - - `homebrew.formulae.ts` → `resource_type=homebrew`, `parameter_path=/formulae` -4. Run `npm run build:completions` to regenerate the index +3. The filename encodes both the resource type and the JSONPath of the parameter: + - Everything **before the first dot** = `resource_type` (e.g. `homebrew`) + - Everything **after the first dot** = JSONPath expression (e.g. `$.formulae`) + - Examples: + - `homebrew.$.formulae.ts` → `resource_type=homebrew`, `parameter_path=$.formulae` + - `nvm.$.nodeVersions.ts` → `resource_type=nvm`, `parameter_path=$.nodeVersions` + - `codex.$.config.model.ts` → `resource_type=codex`, `parameter_path=$.config.model` +4. For parameters **nested inside array items** (e.g. a property on each object in an array), use `[x]` in the filename to encode the `[*]` array wildcard — bundlers treat `[*]` as a glob pattern in import paths, so `[x]` is used as the safe filename equivalent and is translated to `[*]` by the codegen script: + - `ios-simulators.$.simulators[x].deviceType.ts` → `parameter_path=$.simulators[*].deviceType` +5. Run `npm run build:completions` to regenerate the index ```bash npm run build:completions # regenerate completions-cron/src/__generated__/completions-index.ts diff --git a/completions-cron/src/__generated__/completions-index.ts b/completions-cron/src/__generated__/completions-index.ts index 65c1e593..9ed4a4a3 100644 --- a/completions-cron/src/__generated__/completions-index.ts +++ b/completions-cron/src/__generated__/completions-index.ts @@ -1,39 +1,39 @@ // AUTO-GENERATED by scripts/generate-completions-index.ts - DO NOT EDIT // Re-run `npm run build:completions` to regenerate -import mod0 from '../../../src/resources/xcodes/completions/xcodes.xcodeVersions.js'; -import mod1 from '../../../src/resources/webstorm/completions/webstorm.plugins.js'; -import mod2 from '../../../src/resources/vscode/completions/vscode.extensions.js'; -import mod3 from '../../../src/resources/snap/completions/snap.install.js'; -import mod4 from '../../../src/resources/ruby/rbenv/completions/rbenv.rubyVersions.js'; -import mod5 from '../../../src/resources/python/uv/completions/uv.tools.js'; -import mod6 from '../../../src/resources/python/uv/completions/uv.pythonVersions.js'; -import mod7 from '../../../src/resources/python/pyenv/completions/pyenv.pythonVersions.js'; -import mod8 from '../../../src/resources/python/pip/completions/pip.install.js'; -import mod9 from '../../../src/resources/ollama/completions/ollama.models.js'; -import mod10 from '../../../src/resources/jetbrains/rustrover/completions/rustrover.plugins.js'; -import mod11 from '../../../src/resources/jetbrains/rubymine/completions/rubymine.plugins.js'; -import mod12 from '../../../src/resources/jetbrains/rider/completions/rider.plugins.js'; -import mod13 from '../../../src/resources/jetbrains/pycharm/completions/pycharm.plugins.js'; -import mod14 from '../../../src/resources/jetbrains/phpstorm/completions/phpstorm.plugins.js'; -import mod15 from '../../../src/resources/jetbrains/intellij-idea/completions/intellij-idea.plugins.js'; -import mod16 from '../../../src/resources/jetbrains/goland/completions/goland.plugins.js'; -import mod17 from '../../../src/resources/jetbrains/clion/completions/clion.plugins.js'; -import mod18 from '../../../src/resources/javascript/pnpm/completions/pnpm.globalEnvNodeVersion.js'; -import mod19 from '../../../src/resources/javascript/nvm/completions/nvm.nodeVersions.js'; -import mod20 from '../../../src/resources/javascript/npm/completions/npm.install.js'; -import mod21 from '../../../src/resources/ios/ios-simulator/completions/ios-simulators.simulators.runtime.js'; -import mod22 from '../../../src/resources/ios/ios-simulator/completions/ios-simulators.simulators.deviceType.js'; -import mod23 from '../../../src/resources/homebrew/completions/homebrew.formulae.js'; -import mod24 from '../../../src/resources/homebrew/completions/homebrew.casks.js'; -import mod25 from '../../../src/resources/cursor/completions/cursor.extensions.js'; -import mod26 from '../../../src/resources/codex/completions/codex.config.model.js'; -import mod27 from '../../../src/resources/asdf/completions/asdf.plugins.js'; -import mod28 from '../../../src/resources/asdf/completions/asdf-plugin.plugin.js'; -import mod29 from '../../../src/resources/apt/completions/apt.install.js'; -import mod30 from '../../../src/resources/android/android-studios/completions/android-studio.version.js'; -import mod31 from '../../../src/resources/android/android-cli/completions/android-cli.sdkPackages.js'; -import mod32 from '../../../src/resources/android/android-cli/completions/android-cli.emulators.js'; +import mod0 from '../../../src/resources/xcodes/completions/xcodes.$.xcodeVersions.js'; +import mod1 from '../../../src/resources/webstorm/completions/webstorm.$.plugins.js'; +import mod2 from '../../../src/resources/vscode/completions/vscode.$.extensions.js'; +import mod3 from '../../../src/resources/snap/completions/snap.$.install.js'; +import mod4 from '../../../src/resources/ruby/rbenv/completions/rbenv.$.rubyVersions.js'; +import mod5 from '../../../src/resources/python/uv/completions/uv.$.tools.js'; +import mod6 from '../../../src/resources/python/uv/completions/uv.$.pythonVersions.js'; +import mod7 from '../../../src/resources/python/pyenv/completions/pyenv.$.pythonVersions.js'; +import mod8 from '../../../src/resources/python/pip/completions/pip.$.install.js'; +import mod9 from '../../../src/resources/ollama/completions/ollama.$.models.js'; +import mod10 from '../../../src/resources/jetbrains/rustrover/completions/rustrover.$.plugins.js'; +import mod11 from '../../../src/resources/jetbrains/rubymine/completions/rubymine.$.plugins.js'; +import mod12 from '../../../src/resources/jetbrains/rider/completions/rider.$.plugins.js'; +import mod13 from '../../../src/resources/jetbrains/pycharm/completions/pycharm.$.plugins.js'; +import mod14 from '../../../src/resources/jetbrains/phpstorm/completions/phpstorm.$.plugins.js'; +import mod15 from '../../../src/resources/jetbrains/intellij-idea/completions/intellij-idea.$.plugins.js'; +import mod16 from '../../../src/resources/jetbrains/goland/completions/goland.$.plugins.js'; +import mod17 from '../../../src/resources/jetbrains/clion/completions/clion.$.plugins.js'; +import mod18 from '../../../src/resources/javascript/pnpm/completions/pnpm.$.globalEnvNodeVersion.js'; +import mod19 from '../../../src/resources/javascript/nvm/completions/nvm.$.nodeVersions.js'; +import mod20 from '../../../src/resources/javascript/npm/completions/npm.$.install.js'; +import mod21 from '../../../src/resources/ios/ios-simulator/completions/ios-simulators.$.simulators[x].runtime.js'; +import mod22 from '../../../src/resources/ios/ios-simulator/completions/ios-simulators.$.simulators[x].deviceType.js'; +import mod23 from '../../../src/resources/homebrew/completions/homebrew.$.formulae.js'; +import mod24 from '../../../src/resources/homebrew/completions/homebrew.$.casks.js'; +import mod25 from '../../../src/resources/cursor/completions/cursor.$.extensions.js'; +import mod26 from '../../../src/resources/codex/completions/codex.$.config.model.js'; +import mod27 from '../../../src/resources/asdf/completions/asdf.$.plugins.js'; +import mod28 from '../../../src/resources/asdf/completions/asdf-plugin.$.plugin.js'; +import mod29 from '../../../src/resources/apt/completions/apt.$.install.js'; +import mod30 from '../../../src/resources/android/android-studios/completions/android-studio.$.version.js'; +import mod31 from '../../../src/resources/android/android-cli/completions/android-cli.$.sdkPackages.js'; +import mod32 from '../../../src/resources/android/android-cli/completions/android-cli.$.emulators.js'; export interface CompletionModule { resourceType: string @@ -42,37 +42,37 @@ export interface CompletionModule { } export const completionModules: CompletionModule[] = [ - { resourceType: 'xcodes', parameterPath: '/xcodeVersions', fetch: mod0 }, - { resourceType: 'webstorm', parameterPath: '/plugins', fetch: mod1 }, - { resourceType: 'vscode', parameterPath: '/extensions', fetch: mod2 }, - { resourceType: 'snap', parameterPath: '/install', fetch: mod3 }, - { resourceType: 'rbenv', parameterPath: '/rubyVersions', fetch: mod4 }, - { resourceType: 'uv', parameterPath: '/tools', fetch: mod5 }, - { resourceType: 'uv', parameterPath: '/pythonVersions', fetch: mod6 }, - { resourceType: 'pyenv', parameterPath: '/pythonVersions', fetch: mod7 }, - { resourceType: 'pip', parameterPath: '/install', fetch: mod8 }, - { resourceType: 'ollama', parameterPath: '/models', fetch: mod9 }, - { resourceType: 'rustrover', parameterPath: '/plugins', fetch: mod10 }, - { resourceType: 'rubymine', parameterPath: '/plugins', fetch: mod11 }, - { resourceType: 'rider', parameterPath: '/plugins', fetch: mod12 }, - { resourceType: 'pycharm', parameterPath: '/plugins', fetch: mod13 }, - { resourceType: 'phpstorm', parameterPath: '/plugins', fetch: mod14 }, - { resourceType: 'intellij-idea', parameterPath: '/plugins', fetch: mod15 }, - { resourceType: 'goland', parameterPath: '/plugins', fetch: mod16 }, - { resourceType: 'clion', parameterPath: '/plugins', fetch: mod17 }, - { resourceType: 'pnpm', parameterPath: '/globalEnvNodeVersion', fetch: mod18 }, - { resourceType: 'nvm', parameterPath: '/nodeVersions', fetch: mod19 }, - { resourceType: 'npm', parameterPath: '/install', fetch: mod20 }, - { resourceType: 'ios-simulators', parameterPath: '/simulators/runtime', fetch: mod21 }, - { resourceType: 'ios-simulators', parameterPath: '/simulators/deviceType', fetch: mod22 }, - { resourceType: 'homebrew', parameterPath: '/formulae', fetch: mod23 }, - { resourceType: 'homebrew', parameterPath: '/casks', fetch: mod24 }, - { resourceType: 'cursor', parameterPath: '/extensions', fetch: mod25 }, - { resourceType: 'codex', parameterPath: '/config/model', fetch: mod26 }, - { resourceType: 'asdf', parameterPath: '/plugins', fetch: mod27 }, - { resourceType: 'asdf-plugin', parameterPath: '/plugin', fetch: mod28 }, - { resourceType: 'apt', parameterPath: '/install', fetch: mod29 }, - { resourceType: 'android-studio', parameterPath: '/version', fetch: mod30 }, - { resourceType: 'android-cli', parameterPath: '/sdkPackages', fetch: mod31 }, - { resourceType: 'android-cli', parameterPath: '/emulators', fetch: mod32 }, + { resourceType: 'xcodes', parameterPath: '$.xcodeVersions', fetch: mod0 }, + { resourceType: 'webstorm', parameterPath: '$.plugins', fetch: mod1 }, + { resourceType: 'vscode', parameterPath: '$.extensions', fetch: mod2 }, + { resourceType: 'snap', parameterPath: '$.install', fetch: mod3 }, + { resourceType: 'rbenv', parameterPath: '$.rubyVersions', fetch: mod4 }, + { resourceType: 'uv', parameterPath: '$.tools', fetch: mod5 }, + { resourceType: 'uv', parameterPath: '$.pythonVersions', fetch: mod6 }, + { resourceType: 'pyenv', parameterPath: '$.pythonVersions', fetch: mod7 }, + { resourceType: 'pip', parameterPath: '$.install', fetch: mod8 }, + { resourceType: 'ollama', parameterPath: '$.models', fetch: mod9 }, + { resourceType: 'rustrover', parameterPath: '$.plugins', fetch: mod10 }, + { resourceType: 'rubymine', parameterPath: '$.plugins', fetch: mod11 }, + { resourceType: 'rider', parameterPath: '$.plugins', fetch: mod12 }, + { resourceType: 'pycharm', parameterPath: '$.plugins', fetch: mod13 }, + { resourceType: 'phpstorm', parameterPath: '$.plugins', fetch: mod14 }, + { resourceType: 'intellij-idea', parameterPath: '$.plugins', fetch: mod15 }, + { resourceType: 'goland', parameterPath: '$.plugins', fetch: mod16 }, + { resourceType: 'clion', parameterPath: '$.plugins', fetch: mod17 }, + { resourceType: 'pnpm', parameterPath: '$.globalEnvNodeVersion', fetch: mod18 }, + { resourceType: 'nvm', parameterPath: '$.nodeVersions', fetch: mod19 }, + { resourceType: 'npm', parameterPath: '$.install', fetch: mod20 }, + { resourceType: 'ios-simulators', parameterPath: '$.simulators[*].runtime', fetch: mod21 }, + { resourceType: 'ios-simulators', parameterPath: '$.simulators[*].deviceType', fetch: mod22 }, + { resourceType: 'homebrew', parameterPath: '$.formulae', fetch: mod23 }, + { resourceType: 'homebrew', parameterPath: '$.casks', fetch: mod24 }, + { resourceType: 'cursor', parameterPath: '$.extensions', fetch: mod25 }, + { resourceType: 'codex', parameterPath: '$.config.model', fetch: mod26 }, + { resourceType: 'asdf', parameterPath: '$.plugins', fetch: mod27 }, + { resourceType: 'asdf-plugin', parameterPath: '$.plugin', fetch: mod28 }, + { resourceType: 'apt', parameterPath: '$.install', fetch: mod29 }, + { resourceType: 'android-studio', parameterPath: '$.version', fetch: mod30 }, + { resourceType: 'android-cli', parameterPath: '$.sdkPackages', fetch: mod31 }, + { resourceType: 'android-cli', parameterPath: '$.emulators', fetch: mod32 }, ] diff --git a/package.json b/package.json index 2adc4542..adcbc486 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "default", - "version": "1.15.0-beta.2", + "version": "1.15.0-beta.4", "description": "Default plugin for Codify - provides 50+ declarative resources for managing development tools and system configuration across macOS and Linux", "main": "dist/index.js", "scripts": { diff --git a/scripts/generate-completions-index.ts b/scripts/generate-completions-index.ts index 14630f5f..29736355 100644 --- a/scripts/generate-completions-index.ts +++ b/scripts/generate-completions-index.ts @@ -24,11 +24,12 @@ const modules = completionFiles.map((relPath, i) => { const dotIndex = filename.indexOf('.') if (dotIndex === -1) { throw new Error( - `Completion file must be named ..ts, got: ${filename}` + `Completion file must be named ..ts (e.g. homebrew.$.formulae.ts, ios-simulators.$.simulators[x].deviceType.ts), got: ${filename}` ) } const resourceType = filename.substring(0, dotIndex) - const parameterPath = '/' + filename.substring(dotIndex + 1).replaceAll('.', '/') + // [x] in filenames encodes [*] (glob-safe); restore to proper JSONPath wildcard + const parameterPath = filename.substring(dotIndex + 1).replaceAll('[x]', '[*]') // Path from completions-cron/src/__generated__/ back to plugin src/resources/ const importPath = '../../../src/' + relPath.replace(/\.ts$/, '.js') @@ -59,5 +60,5 @@ fs.writeFileSync(outputFile, lines.join('\n'), 'utf-8') console.log(`Generated ${outputFile} with ${modules.length} completion module(s):`) for (const { resourceType, parameterPath } of modules) { - console.log(` ${resourceType}${parameterPath}`) + console.log(` ${resourceType} → ${parameterPath}`) } diff --git a/src/resources/android/android-cli/completions/android-cli.emulators.ts b/src/resources/android/android-cli/completions/android-cli.$.emulators.ts similarity index 100% rename from src/resources/android/android-cli/completions/android-cli.emulators.ts rename to src/resources/android/android-cli/completions/android-cli.$.emulators.ts diff --git a/src/resources/android/android-cli/completions/android-cli.sdkPackages.ts b/src/resources/android/android-cli/completions/android-cli.$.sdkPackages.ts similarity index 100% rename from src/resources/android/android-cli/completions/android-cli.sdkPackages.ts rename to src/resources/android/android-cli/completions/android-cli.$.sdkPackages.ts diff --git a/src/resources/android/android-studios/completions/android-studio.version.ts b/src/resources/android/android-studios/completions/android-studio.$.version.ts similarity index 100% rename from src/resources/android/android-studios/completions/android-studio.version.ts rename to src/resources/android/android-studios/completions/android-studio.$.version.ts diff --git a/src/resources/apt/completions/apt.install.ts b/src/resources/apt/completions/apt.$.install.ts similarity index 100% rename from src/resources/apt/completions/apt.install.ts rename to src/resources/apt/completions/apt.$.install.ts diff --git a/src/resources/asdf/completions/asdf-plugin.plugin.ts b/src/resources/asdf/completions/asdf-plugin.$.plugin.ts similarity index 100% rename from src/resources/asdf/completions/asdf-plugin.plugin.ts rename to src/resources/asdf/completions/asdf-plugin.$.plugin.ts diff --git a/src/resources/asdf/completions/asdf.plugins.ts b/src/resources/asdf/completions/asdf.$.plugins.ts similarity index 100% rename from src/resources/asdf/completions/asdf.plugins.ts rename to src/resources/asdf/completions/asdf.$.plugins.ts diff --git a/src/resources/codex/completions/codex.config.model.ts b/src/resources/codex/completions/codex.$.config.model.ts similarity index 100% rename from src/resources/codex/completions/codex.config.model.ts rename to src/resources/codex/completions/codex.$.config.model.ts diff --git a/src/resources/cursor/completions/cursor.extensions.ts b/src/resources/cursor/completions/cursor.$.extensions.ts similarity index 100% rename from src/resources/cursor/completions/cursor.extensions.ts rename to src/resources/cursor/completions/cursor.$.extensions.ts diff --git a/src/resources/homebrew/completions/homebrew.casks.ts b/src/resources/homebrew/completions/homebrew.$.casks.ts similarity index 100% rename from src/resources/homebrew/completions/homebrew.casks.ts rename to src/resources/homebrew/completions/homebrew.$.casks.ts diff --git a/src/resources/homebrew/completions/homebrew.formulae.ts b/src/resources/homebrew/completions/homebrew.$.formulae.ts similarity index 100% rename from src/resources/homebrew/completions/homebrew.formulae.ts rename to src/resources/homebrew/completions/homebrew.$.formulae.ts diff --git a/src/resources/ios/ios-simulator/completions/ios-simulators.simulators.deviceType.ts b/src/resources/ios/ios-simulator/completions/ios-simulators.$.simulators[x].deviceType.ts similarity index 100% rename from src/resources/ios/ios-simulator/completions/ios-simulators.simulators.deviceType.ts rename to src/resources/ios/ios-simulator/completions/ios-simulators.$.simulators[x].deviceType.ts diff --git a/src/resources/ios/ios-simulator/completions/ios-simulators.simulators.runtime.ts b/src/resources/ios/ios-simulator/completions/ios-simulators.$.simulators[x].runtime.ts similarity index 100% rename from src/resources/ios/ios-simulator/completions/ios-simulators.simulators.runtime.ts rename to src/resources/ios/ios-simulator/completions/ios-simulators.$.simulators[x].runtime.ts diff --git a/src/resources/javascript/npm/completions/npm.install.ts b/src/resources/javascript/npm/completions/npm.$.install.ts similarity index 100% rename from src/resources/javascript/npm/completions/npm.install.ts rename to src/resources/javascript/npm/completions/npm.$.install.ts diff --git a/src/resources/javascript/nvm/completions/nvm.nodeVersions.ts b/src/resources/javascript/nvm/completions/nvm.$.nodeVersions.ts similarity index 100% rename from src/resources/javascript/nvm/completions/nvm.nodeVersions.ts rename to src/resources/javascript/nvm/completions/nvm.$.nodeVersions.ts diff --git a/src/resources/javascript/pnpm/completions/pnpm.globalEnvNodeVersion.ts b/src/resources/javascript/pnpm/completions/pnpm.$.globalEnvNodeVersion.ts similarity index 100% rename from src/resources/javascript/pnpm/completions/pnpm.globalEnvNodeVersion.ts rename to src/resources/javascript/pnpm/completions/pnpm.$.globalEnvNodeVersion.ts diff --git a/src/resources/jetbrains/clion/completions/clion.plugins.ts b/src/resources/jetbrains/clion/completions/clion.$.plugins.ts similarity index 100% rename from src/resources/jetbrains/clion/completions/clion.plugins.ts rename to src/resources/jetbrains/clion/completions/clion.$.plugins.ts diff --git a/src/resources/jetbrains/goland/completions/goland.plugins.ts b/src/resources/jetbrains/goland/completions/goland.$.plugins.ts similarity index 100% rename from src/resources/jetbrains/goland/completions/goland.plugins.ts rename to src/resources/jetbrains/goland/completions/goland.$.plugins.ts diff --git a/src/resources/jetbrains/intellij-idea/completions/intellij-idea.plugins.ts b/src/resources/jetbrains/intellij-idea/completions/intellij-idea.$.plugins.ts similarity index 100% rename from src/resources/jetbrains/intellij-idea/completions/intellij-idea.plugins.ts rename to src/resources/jetbrains/intellij-idea/completions/intellij-idea.$.plugins.ts diff --git a/src/resources/jetbrains/phpstorm/completions/phpstorm.plugins.ts b/src/resources/jetbrains/phpstorm/completions/phpstorm.$.plugins.ts similarity index 100% rename from src/resources/jetbrains/phpstorm/completions/phpstorm.plugins.ts rename to src/resources/jetbrains/phpstorm/completions/phpstorm.$.plugins.ts diff --git a/src/resources/jetbrains/pycharm/completions/pycharm.plugins.ts b/src/resources/jetbrains/pycharm/completions/pycharm.$.plugins.ts similarity index 100% rename from src/resources/jetbrains/pycharm/completions/pycharm.plugins.ts rename to src/resources/jetbrains/pycharm/completions/pycharm.$.plugins.ts diff --git a/src/resources/jetbrains/rider/completions/rider.plugins.ts b/src/resources/jetbrains/rider/completions/rider.$.plugins.ts similarity index 100% rename from src/resources/jetbrains/rider/completions/rider.plugins.ts rename to src/resources/jetbrains/rider/completions/rider.$.plugins.ts diff --git a/src/resources/jetbrains/rubymine/completions/rubymine.plugins.ts b/src/resources/jetbrains/rubymine/completions/rubymine.$.plugins.ts similarity index 100% rename from src/resources/jetbrains/rubymine/completions/rubymine.plugins.ts rename to src/resources/jetbrains/rubymine/completions/rubymine.$.plugins.ts diff --git a/src/resources/jetbrains/rustrover/completions/rustrover.plugins.ts b/src/resources/jetbrains/rustrover/completions/rustrover.$.plugins.ts similarity index 100% rename from src/resources/jetbrains/rustrover/completions/rustrover.plugins.ts rename to src/resources/jetbrains/rustrover/completions/rustrover.$.plugins.ts diff --git a/src/resources/ollama/completions/ollama.models.ts b/src/resources/ollama/completions/ollama.$.models.ts similarity index 100% rename from src/resources/ollama/completions/ollama.models.ts rename to src/resources/ollama/completions/ollama.$.models.ts diff --git a/src/resources/python/pip/completions/pip.install.ts b/src/resources/python/pip/completions/pip.$.install.ts similarity index 100% rename from src/resources/python/pip/completions/pip.install.ts rename to src/resources/python/pip/completions/pip.$.install.ts diff --git a/src/resources/python/pyenv/completions/pyenv.pythonVersions.ts b/src/resources/python/pyenv/completions/pyenv.$.pythonVersions.ts similarity index 100% rename from src/resources/python/pyenv/completions/pyenv.pythonVersions.ts rename to src/resources/python/pyenv/completions/pyenv.$.pythonVersions.ts diff --git a/src/resources/python/uv/completions/uv.pythonVersions.ts b/src/resources/python/uv/completions/uv.$.pythonVersions.ts similarity index 100% rename from src/resources/python/uv/completions/uv.pythonVersions.ts rename to src/resources/python/uv/completions/uv.$.pythonVersions.ts diff --git a/src/resources/python/uv/completions/uv.tools.ts b/src/resources/python/uv/completions/uv.$.tools.ts similarity index 100% rename from src/resources/python/uv/completions/uv.tools.ts rename to src/resources/python/uv/completions/uv.$.tools.ts diff --git a/src/resources/ruby/rbenv/completions/rbenv.rubyVersions.ts b/src/resources/ruby/rbenv/completions/rbenv.$.rubyVersions.ts similarity index 100% rename from src/resources/ruby/rbenv/completions/rbenv.rubyVersions.ts rename to src/resources/ruby/rbenv/completions/rbenv.$.rubyVersions.ts diff --git a/src/resources/snap/completions/snap.install.ts b/src/resources/snap/completions/snap.$.install.ts similarity index 100% rename from src/resources/snap/completions/snap.install.ts rename to src/resources/snap/completions/snap.$.install.ts diff --git a/src/resources/vscode/completions/vscode.extensions.ts b/src/resources/vscode/completions/vscode.$.extensions.ts similarity index 100% rename from src/resources/vscode/completions/vscode.extensions.ts rename to src/resources/vscode/completions/vscode.$.extensions.ts diff --git a/src/resources/webstorm/completions/webstorm.plugins.ts b/src/resources/webstorm/completions/webstorm.$.plugins.ts similarity index 100% rename from src/resources/webstorm/completions/webstorm.plugins.ts rename to src/resources/webstorm/completions/webstorm.$.plugins.ts diff --git a/src/resources/xcodes/completions/xcodes.xcodeVersions.ts b/src/resources/xcodes/completions/xcodes.$.xcodeVersions.ts similarity index 100% rename from src/resources/xcodes/completions/xcodes.xcodeVersions.ts rename to src/resources/xcodes/completions/xcodes.$.xcodeVersions.ts From 0d6dd97b9d1a26da7c1d3fd493ca89ddf86adc76 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Fri, 3 Jul 2026 11:47:16 -0400 Subject: [PATCH 07/18] feat: Bug fixes and additional completions --- CLAUDE.md | 8 +- .../src/__generated__/completions-index.ts | 232 ++++++++++++------ completions-cron/src/index.ts | 52 +++- package.json | 2 +- scripts/generate-completions-index.ts | 13 +- .../go/goenv/completions/goenv.$.global.ts | 1 + .../java/jenv/completions/jenv.$.global.ts | 1 + .../fast-node-manager.$.defaultVersion.ts | 1 + .../nvm/completions/nvm.$.global.ts | 1 + .../pyenv/completions/pyenv.$.global.ts | 1 + .../ruby/rbenv/completions/rbenv.$.global.ts | 1 + .../xcodes/completions/xcodes.$.selected.ts | 1 + 12 files changed, 229 insertions(+), 85 deletions(-) create mode 100644 src/resources/go/goenv/completions/goenv.$.global.ts create mode 100644 src/resources/java/jenv/completions/jenv.$.global.ts create mode 100644 src/resources/javascript/fast-node-manager/completions/fast-node-manager.$.defaultVersion.ts create mode 100644 src/resources/javascript/nvm/completions/nvm.$.global.ts create mode 100644 src/resources/python/pyenv/completions/pyenv.$.global.ts create mode 100644 src/resources/ruby/rbenv/completions/rbenv.$.global.ts create mode 100644 src/resources/xcodes/completions/xcodes.$.selected.ts diff --git a/CLAUDE.md b/CLAUDE.md index e31892ac..6e0c3d22 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -380,7 +380,13 @@ The Codify Editor supports auto-complete for certain resource parameters (e.g. H - `codex.$.config.model.ts` → `resource_type=codex`, `parameter_path=$.config.model` 4. For parameters **nested inside array items** (e.g. a property on each object in an array), use `[x]` in the filename to encode the `[*]` array wildcard — bundlers treat `[*]` as a glob pattern in import paths, so `[x]` is used as the safe filename equivalent and is translated to `[*]` by the codegen script: - `ios-simulators.$.simulators[x].deviceType.ts` → `parameter_path=$.simulators[*].deviceType` -5. Run `npm run build:completions` to regenerate the index +5. For **mirror completions** — where a parameter's suggestions should reflect the current value of a sibling parameter on the same resource — export a plain object instead of a fetch function: + ```typescript + // xcodes.$.selected.ts — offers whatever the user typed in xcodeVersions + export default { mirrorParameter: '$.xcodeVersions' } as const; + ``` + The codegen script detects the export type at build time. The cron job writes a single metadata row to Supabase (with `mirror_parameter_path` set and no `value` rows). The dashboard reads this and serves completions client-side from the resource's current config — no DB query needed. +6. Run `npm run build:completions` to regenerate the index ```bash npm run build:completions # regenerate completions-cron/src/__generated__/completions-index.ts diff --git a/completions-cron/src/__generated__/completions-index.ts b/completions-cron/src/__generated__/completions-index.ts index 9ed4a4a3..5f853e2c 100644 --- a/completions-cron/src/__generated__/completions-index.ts +++ b/completions-cron/src/__generated__/completions-index.ts @@ -2,77 +2,169 @@ // Re-run `npm run build:completions` to regenerate import mod0 from '../../../src/resources/xcodes/completions/xcodes.$.xcodeVersions.js'; -import mod1 from '../../../src/resources/webstorm/completions/webstorm.$.plugins.js'; -import mod2 from '../../../src/resources/vscode/completions/vscode.$.extensions.js'; -import mod3 from '../../../src/resources/snap/completions/snap.$.install.js'; -import mod4 from '../../../src/resources/ruby/rbenv/completions/rbenv.$.rubyVersions.js'; -import mod5 from '../../../src/resources/python/uv/completions/uv.$.tools.js'; -import mod6 from '../../../src/resources/python/uv/completions/uv.$.pythonVersions.js'; -import mod7 from '../../../src/resources/python/pyenv/completions/pyenv.$.pythonVersions.js'; -import mod8 from '../../../src/resources/python/pip/completions/pip.$.install.js'; -import mod9 from '../../../src/resources/ollama/completions/ollama.$.models.js'; -import mod10 from '../../../src/resources/jetbrains/rustrover/completions/rustrover.$.plugins.js'; -import mod11 from '../../../src/resources/jetbrains/rubymine/completions/rubymine.$.plugins.js'; -import mod12 from '../../../src/resources/jetbrains/rider/completions/rider.$.plugins.js'; -import mod13 from '../../../src/resources/jetbrains/pycharm/completions/pycharm.$.plugins.js'; -import mod14 from '../../../src/resources/jetbrains/phpstorm/completions/phpstorm.$.plugins.js'; -import mod15 from '../../../src/resources/jetbrains/intellij-idea/completions/intellij-idea.$.plugins.js'; -import mod16 from '../../../src/resources/jetbrains/goland/completions/goland.$.plugins.js'; -import mod17 from '../../../src/resources/jetbrains/clion/completions/clion.$.plugins.js'; -import mod18 from '../../../src/resources/javascript/pnpm/completions/pnpm.$.globalEnvNodeVersion.js'; -import mod19 from '../../../src/resources/javascript/nvm/completions/nvm.$.nodeVersions.js'; -import mod20 from '../../../src/resources/javascript/npm/completions/npm.$.install.js'; -import mod21 from '../../../src/resources/ios/ios-simulator/completions/ios-simulators.$.simulators[x].runtime.js'; -import mod22 from '../../../src/resources/ios/ios-simulator/completions/ios-simulators.$.simulators[x].deviceType.js'; -import mod23 from '../../../src/resources/homebrew/completions/homebrew.$.formulae.js'; -import mod24 from '../../../src/resources/homebrew/completions/homebrew.$.casks.js'; -import mod25 from '../../../src/resources/cursor/completions/cursor.$.extensions.js'; -import mod26 from '../../../src/resources/codex/completions/codex.$.config.model.js'; -import mod27 from '../../../src/resources/asdf/completions/asdf.$.plugins.js'; -import mod28 from '../../../src/resources/asdf/completions/asdf-plugin.$.plugin.js'; -import mod29 from '../../../src/resources/apt/completions/apt.$.install.js'; -import mod30 from '../../../src/resources/android/android-studios/completions/android-studio.$.version.js'; -import mod31 from '../../../src/resources/android/android-cli/completions/android-cli.$.sdkPackages.js'; -import mod32 from '../../../src/resources/android/android-cli/completions/android-cli.$.emulators.js'; +import mod1 from '../../../src/resources/xcodes/completions/xcodes.$.selected.js'; +import mod2 from '../../../src/resources/webstorm/completions/webstorm.$.plugins.js'; +import mod3 from '../../../src/resources/vscode/completions/vscode.$.extensions.js'; +import mod4 from '../../../src/resources/snap/completions/snap.$.install.js'; +import mod5 from '../../../src/resources/ruby/rbenv/completions/rbenv.$.rubyVersions.js'; +import mod6 from '../../../src/resources/ruby/rbenv/completions/rbenv.$.global.js'; +import mod7 from '../../../src/resources/python/uv/completions/uv.$.tools.js'; +import mod8 from '../../../src/resources/python/uv/completions/uv.$.pythonVersions.js'; +import mod9 from '../../../src/resources/python/pyenv/completions/pyenv.$.pythonVersions.js'; +import mod10 from '../../../src/resources/python/pyenv/completions/pyenv.$.global.js'; +import mod11 from '../../../src/resources/python/pip/completions/pip.$.install.js'; +import mod12 from '../../../src/resources/ollama/completions/ollama.$.models.js'; +import mod13 from '../../../src/resources/jetbrains/rustrover/completions/rustrover.$.plugins.js'; +import mod14 from '../../../src/resources/jetbrains/rubymine/completions/rubymine.$.plugins.js'; +import mod15 from '../../../src/resources/jetbrains/rider/completions/rider.$.plugins.js'; +import mod16 from '../../../src/resources/jetbrains/pycharm/completions/pycharm.$.plugins.js'; +import mod17 from '../../../src/resources/jetbrains/phpstorm/completions/phpstorm.$.plugins.js'; +import mod18 from '../../../src/resources/jetbrains/intellij-idea/completions/intellij-idea.$.plugins.js'; +import mod19 from '../../../src/resources/jetbrains/goland/completions/goland.$.plugins.js'; +import mod20 from '../../../src/resources/jetbrains/clion/completions/clion.$.plugins.js'; +import mod21 from '../../../src/resources/javascript/pnpm/completions/pnpm.$.globalEnvNodeVersion.js'; +import mod22 from '../../../src/resources/javascript/nvm/completions/nvm.$.nodeVersions.js'; +import mod23 from '../../../src/resources/javascript/nvm/completions/nvm.$.global.js'; +import mod24 from '../../../src/resources/javascript/npm/completions/npm.$.install.js'; +import mod25 from '../../../src/resources/javascript/fast-node-manager/completions/fast-node-manager.$.defaultVersion.js'; +import mod26 from '../../../src/resources/java/jenv/completions/jenv.$.global.js'; +import mod27 from '../../../src/resources/ios/ios-simulator/completions/ios-simulators.$.simulators[x].runtime.js'; +import mod28 from '../../../src/resources/ios/ios-simulator/completions/ios-simulators.$.simulators[x].deviceType.js'; +import mod29 from '../../../src/resources/homebrew/completions/homebrew.$.formulae.js'; +import mod30 from '../../../src/resources/homebrew/completions/homebrew.$.casks.js'; +import mod31 from '../../../src/resources/go/goenv/completions/goenv.$.global.js'; +import mod32 from '../../../src/resources/cursor/completions/cursor.$.extensions.js'; +import mod33 from '../../../src/resources/codex/completions/codex.$.config.model.js'; +import mod34 from '../../../src/resources/asdf/completions/asdf.$.plugins.js'; +import mod35 from '../../../src/resources/asdf/completions/asdf-plugin.$.plugin.js'; +import mod36 from '../../../src/resources/apt/completions/apt.$.install.js'; +import mod37 from '../../../src/resources/android/android-studios/completions/android-studio.$.version.js'; +import mod38 from '../../../src/resources/android/android-cli/completions/android-cli.$.sdkPackages.js'; +import mod39 from '../../../src/resources/android/android-cli/completions/android-cli.$.emulators.js'; -export interface CompletionModule { - resourceType: string - parameterPath: string - fetch: () => Promise -} +export type CompletionModule = + | { kind: 'fetch'; resourceType: string; parameterPath: string; fetch: () => Promise } + | { kind: 'mirror'; resourceType: string; parameterPath: string; mirrorParameter: string } export const completionModules: CompletionModule[] = [ - { resourceType: 'xcodes', parameterPath: '$.xcodeVersions', fetch: mod0 }, - { resourceType: 'webstorm', parameterPath: '$.plugins', fetch: mod1 }, - { resourceType: 'vscode', parameterPath: '$.extensions', fetch: mod2 }, - { resourceType: 'snap', parameterPath: '$.install', fetch: mod3 }, - { resourceType: 'rbenv', parameterPath: '$.rubyVersions', fetch: mod4 }, - { resourceType: 'uv', parameterPath: '$.tools', fetch: mod5 }, - { resourceType: 'uv', parameterPath: '$.pythonVersions', fetch: mod6 }, - { resourceType: 'pyenv', parameterPath: '$.pythonVersions', fetch: mod7 }, - { resourceType: 'pip', parameterPath: '$.install', fetch: mod8 }, - { resourceType: 'ollama', parameterPath: '$.models', fetch: mod9 }, - { resourceType: 'rustrover', parameterPath: '$.plugins', fetch: mod10 }, - { resourceType: 'rubymine', parameterPath: '$.plugins', fetch: mod11 }, - { resourceType: 'rider', parameterPath: '$.plugins', fetch: mod12 }, - { resourceType: 'pycharm', parameterPath: '$.plugins', fetch: mod13 }, - { resourceType: 'phpstorm', parameterPath: '$.plugins', fetch: mod14 }, - { resourceType: 'intellij-idea', parameterPath: '$.plugins', fetch: mod15 }, - { resourceType: 'goland', parameterPath: '$.plugins', fetch: mod16 }, - { resourceType: 'clion', parameterPath: '$.plugins', fetch: mod17 }, - { resourceType: 'pnpm', parameterPath: '$.globalEnvNodeVersion', fetch: mod18 }, - { resourceType: 'nvm', parameterPath: '$.nodeVersions', fetch: mod19 }, - { resourceType: 'npm', parameterPath: '$.install', fetch: mod20 }, - { resourceType: 'ios-simulators', parameterPath: '$.simulators[*].runtime', fetch: mod21 }, - { resourceType: 'ios-simulators', parameterPath: '$.simulators[*].deviceType', fetch: mod22 }, - { resourceType: 'homebrew', parameterPath: '$.formulae', fetch: mod23 }, - { resourceType: 'homebrew', parameterPath: '$.casks', fetch: mod24 }, - { resourceType: 'cursor', parameterPath: '$.extensions', fetch: mod25 }, - { resourceType: 'codex', parameterPath: '$.config.model', fetch: mod26 }, - { resourceType: 'asdf', parameterPath: '$.plugins', fetch: mod27 }, - { resourceType: 'asdf-plugin', parameterPath: '$.plugin', fetch: mod28 }, - { resourceType: 'apt', parameterPath: '$.install', fetch: mod29 }, - { resourceType: 'android-studio', parameterPath: '$.version', fetch: mod30 }, - { resourceType: 'android-cli', parameterPath: '$.sdkPackages', fetch: mod31 }, - { resourceType: 'android-cli', parameterPath: '$.emulators', fetch: mod32 }, + typeof mod0 === 'function' + ? { kind: 'fetch', resourceType: 'xcodes', parameterPath: '$.xcodeVersions', fetch: mod0 } + : { kind: 'mirror', resourceType: 'xcodes', parameterPath: '$.xcodeVersions', mirrorParameter: (mod0 as any).mirrorParameter }, + typeof mod1 === 'function' + ? { kind: 'fetch', resourceType: 'xcodes', parameterPath: '$.selected', fetch: mod1 } + : { kind: 'mirror', resourceType: 'xcodes', parameterPath: '$.selected', mirrorParameter: (mod1 as any).mirrorParameter }, + typeof mod2 === 'function' + ? { kind: 'fetch', resourceType: 'webstorm', parameterPath: '$.plugins', fetch: mod2 } + : { kind: 'mirror', resourceType: 'webstorm', parameterPath: '$.plugins', mirrorParameter: (mod2 as any).mirrorParameter }, + typeof mod3 === 'function' + ? { kind: 'fetch', resourceType: 'vscode', parameterPath: '$.extensions', fetch: mod3 } + : { kind: 'mirror', resourceType: 'vscode', parameterPath: '$.extensions', mirrorParameter: (mod3 as any).mirrorParameter }, + typeof mod4 === 'function' + ? { kind: 'fetch', resourceType: 'snap', parameterPath: '$.install', fetch: mod4 } + : { kind: 'mirror', resourceType: 'snap', parameterPath: '$.install', mirrorParameter: (mod4 as any).mirrorParameter }, + typeof mod5 === 'function' + ? { kind: 'fetch', resourceType: 'rbenv', parameterPath: '$.rubyVersions', fetch: mod5 } + : { kind: 'mirror', resourceType: 'rbenv', parameterPath: '$.rubyVersions', mirrorParameter: (mod5 as any).mirrorParameter }, + typeof mod6 === 'function' + ? { kind: 'fetch', resourceType: 'rbenv', parameterPath: '$.global', fetch: mod6 } + : { kind: 'mirror', resourceType: 'rbenv', parameterPath: '$.global', mirrorParameter: (mod6 as any).mirrorParameter }, + typeof mod7 === 'function' + ? { kind: 'fetch', resourceType: 'uv', parameterPath: '$.tools', fetch: mod7 } + : { kind: 'mirror', resourceType: 'uv', parameterPath: '$.tools', mirrorParameter: (mod7 as any).mirrorParameter }, + typeof mod8 === 'function' + ? { kind: 'fetch', resourceType: 'uv', parameterPath: '$.pythonVersions', fetch: mod8 } + : { kind: 'mirror', resourceType: 'uv', parameterPath: '$.pythonVersions', mirrorParameter: (mod8 as any).mirrorParameter }, + typeof mod9 === 'function' + ? { kind: 'fetch', resourceType: 'pyenv', parameterPath: '$.pythonVersions', fetch: mod9 } + : { kind: 'mirror', resourceType: 'pyenv', parameterPath: '$.pythonVersions', mirrorParameter: (mod9 as any).mirrorParameter }, + typeof mod10 === 'function' + ? { kind: 'fetch', resourceType: 'pyenv', parameterPath: '$.global', fetch: mod10 } + : { kind: 'mirror', resourceType: 'pyenv', parameterPath: '$.global', mirrorParameter: (mod10 as any).mirrorParameter }, + typeof mod11 === 'function' + ? { kind: 'fetch', resourceType: 'pip', parameterPath: '$.install', fetch: mod11 } + : { kind: 'mirror', resourceType: 'pip', parameterPath: '$.install', mirrorParameter: (mod11 as any).mirrorParameter }, + typeof mod12 === 'function' + ? { kind: 'fetch', resourceType: 'ollama', parameterPath: '$.models', fetch: mod12 } + : { kind: 'mirror', resourceType: 'ollama', parameterPath: '$.models', mirrorParameter: (mod12 as any).mirrorParameter }, + typeof mod13 === 'function' + ? { kind: 'fetch', resourceType: 'rustrover', parameterPath: '$.plugins', fetch: mod13 } + : { kind: 'mirror', resourceType: 'rustrover', parameterPath: '$.plugins', mirrorParameter: (mod13 as any).mirrorParameter }, + typeof mod14 === 'function' + ? { kind: 'fetch', resourceType: 'rubymine', parameterPath: '$.plugins', fetch: mod14 } + : { kind: 'mirror', resourceType: 'rubymine', parameterPath: '$.plugins', mirrorParameter: (mod14 as any).mirrorParameter }, + typeof mod15 === 'function' + ? { kind: 'fetch', resourceType: 'rider', parameterPath: '$.plugins', fetch: mod15 } + : { kind: 'mirror', resourceType: 'rider', parameterPath: '$.plugins', mirrorParameter: (mod15 as any).mirrorParameter }, + typeof mod16 === 'function' + ? { kind: 'fetch', resourceType: 'pycharm', parameterPath: '$.plugins', fetch: mod16 } + : { kind: 'mirror', resourceType: 'pycharm', parameterPath: '$.plugins', mirrorParameter: (mod16 as any).mirrorParameter }, + typeof mod17 === 'function' + ? { kind: 'fetch', resourceType: 'phpstorm', parameterPath: '$.plugins', fetch: mod17 } + : { kind: 'mirror', resourceType: 'phpstorm', parameterPath: '$.plugins', mirrorParameter: (mod17 as any).mirrorParameter }, + typeof mod18 === 'function' + ? { kind: 'fetch', resourceType: 'intellij-idea', parameterPath: '$.plugins', fetch: mod18 } + : { kind: 'mirror', resourceType: 'intellij-idea', parameterPath: '$.plugins', mirrorParameter: (mod18 as any).mirrorParameter }, + typeof mod19 === 'function' + ? { kind: 'fetch', resourceType: 'goland', parameterPath: '$.plugins', fetch: mod19 } + : { kind: 'mirror', resourceType: 'goland', parameterPath: '$.plugins', mirrorParameter: (mod19 as any).mirrorParameter }, + typeof mod20 === 'function' + ? { kind: 'fetch', resourceType: 'clion', parameterPath: '$.plugins', fetch: mod20 } + : { kind: 'mirror', resourceType: 'clion', parameterPath: '$.plugins', mirrorParameter: (mod20 as any).mirrorParameter }, + typeof mod21 === 'function' + ? { kind: 'fetch', resourceType: 'pnpm', parameterPath: '$.globalEnvNodeVersion', fetch: mod21 } + : { kind: 'mirror', resourceType: 'pnpm', parameterPath: '$.globalEnvNodeVersion', mirrorParameter: (mod21 as any).mirrorParameter }, + typeof mod22 === 'function' + ? { kind: 'fetch', resourceType: 'nvm', parameterPath: '$.nodeVersions', fetch: mod22 } + : { kind: 'mirror', resourceType: 'nvm', parameterPath: '$.nodeVersions', mirrorParameter: (mod22 as any).mirrorParameter }, + typeof mod23 === 'function' + ? { kind: 'fetch', resourceType: 'nvm', parameterPath: '$.global', fetch: mod23 } + : { kind: 'mirror', resourceType: 'nvm', parameterPath: '$.global', mirrorParameter: (mod23 as any).mirrorParameter }, + typeof mod24 === 'function' + ? { kind: 'fetch', resourceType: 'npm', parameterPath: '$.install', fetch: mod24 } + : { kind: 'mirror', resourceType: 'npm', parameterPath: '$.install', mirrorParameter: (mod24 as any).mirrorParameter }, + typeof mod25 === 'function' + ? { kind: 'fetch', resourceType: 'fast-node-manager', parameterPath: '$.defaultVersion', fetch: mod25 } + : { kind: 'mirror', resourceType: 'fast-node-manager', parameterPath: '$.defaultVersion', mirrorParameter: (mod25 as any).mirrorParameter }, + typeof mod26 === 'function' + ? { kind: 'fetch', resourceType: 'jenv', parameterPath: '$.global', fetch: mod26 } + : { kind: 'mirror', resourceType: 'jenv', parameterPath: '$.global', mirrorParameter: (mod26 as any).mirrorParameter }, + typeof mod27 === 'function' + ? { kind: 'fetch', resourceType: 'ios-simulators', parameterPath: '$.simulators[*].runtime', fetch: mod27 } + : { kind: 'mirror', resourceType: 'ios-simulators', parameterPath: '$.simulators[*].runtime', mirrorParameter: (mod27 as any).mirrorParameter }, + typeof mod28 === 'function' + ? { kind: 'fetch', resourceType: 'ios-simulators', parameterPath: '$.simulators[*].deviceType', fetch: mod28 } + : { kind: 'mirror', resourceType: 'ios-simulators', parameterPath: '$.simulators[*].deviceType', mirrorParameter: (mod28 as any).mirrorParameter }, + typeof mod29 === 'function' + ? { kind: 'fetch', resourceType: 'homebrew', parameterPath: '$.formulae', fetch: mod29 } + : { kind: 'mirror', resourceType: 'homebrew', parameterPath: '$.formulae', mirrorParameter: (mod29 as any).mirrorParameter }, + typeof mod30 === 'function' + ? { kind: 'fetch', resourceType: 'homebrew', parameterPath: '$.casks', fetch: mod30 } + : { kind: 'mirror', resourceType: 'homebrew', parameterPath: '$.casks', mirrorParameter: (mod30 as any).mirrorParameter }, + typeof mod31 === 'function' + ? { kind: 'fetch', resourceType: 'goenv', parameterPath: '$.global', fetch: mod31 } + : { kind: 'mirror', resourceType: 'goenv', parameterPath: '$.global', mirrorParameter: (mod31 as any).mirrorParameter }, + typeof mod32 === 'function' + ? { kind: 'fetch', resourceType: 'cursor', parameterPath: '$.extensions', fetch: mod32 } + : { kind: 'mirror', resourceType: 'cursor', parameterPath: '$.extensions', mirrorParameter: (mod32 as any).mirrorParameter }, + typeof mod33 === 'function' + ? { kind: 'fetch', resourceType: 'codex', parameterPath: '$.config.model', fetch: mod33 } + : { kind: 'mirror', resourceType: 'codex', parameterPath: '$.config.model', mirrorParameter: (mod33 as any).mirrorParameter }, + typeof mod34 === 'function' + ? { kind: 'fetch', resourceType: 'asdf', parameterPath: '$.plugins', fetch: mod34 } + : { kind: 'mirror', resourceType: 'asdf', parameterPath: '$.plugins', mirrorParameter: (mod34 as any).mirrorParameter }, + typeof mod35 === 'function' + ? { kind: 'fetch', resourceType: 'asdf-plugin', parameterPath: '$.plugin', fetch: mod35 } + : { kind: 'mirror', resourceType: 'asdf-plugin', parameterPath: '$.plugin', mirrorParameter: (mod35 as any).mirrorParameter }, + typeof mod36 === 'function' + ? { kind: 'fetch', resourceType: 'apt', parameterPath: '$.install', fetch: mod36 } + : { kind: 'mirror', resourceType: 'apt', parameterPath: '$.install', mirrorParameter: (mod36 as any).mirrorParameter }, + typeof mod37 === 'function' + ? { kind: 'fetch', resourceType: 'android-studio', parameterPath: '$.version', fetch: mod37 } + : { kind: 'mirror', resourceType: 'android-studio', parameterPath: '$.version', mirrorParameter: (mod37 as any).mirrorParameter }, + typeof mod38 === 'function' + ? { kind: 'fetch', resourceType: 'android-cli', parameterPath: '$.sdkPackages', fetch: mod38 } + : { kind: 'mirror', resourceType: 'android-cli', parameterPath: '$.sdkPackages', mirrorParameter: (mod38 as any).mirrorParameter }, + typeof mod39 === 'function' + ? { kind: 'fetch', resourceType: 'android-cli', parameterPath: '$.emulators', fetch: mod39 } + : { kind: 'mirror', resourceType: 'android-cli', parameterPath: '$.emulators', mirrorParameter: (mod39 as any).mirrorParameter }, ] diff --git a/completions-cron/src/index.ts b/completions-cron/src/index.ts index 9c87babd..bca330b3 100644 --- a/completions-cron/src/index.ts +++ b/completions-cron/src/index.ts @@ -28,7 +28,7 @@ async function getResourceId( return data[0].id } -async function processModule( +async function processFetchModule( supabase: SupabaseClient, resourceType: string, parameterPath: string, @@ -36,10 +36,10 @@ async function processModule( prerelease: boolean, resourceIdCache: Map ): Promise { - console.log(`Processing ${resourceType}${parameterPath}...`) + console.log(`Processing ${resourceType} → ${parameterPath}...`) const values = await fetchFn() - console.log(` [${resourceType}${parameterPath}] Fetched ${values.length} values`) + console.log(` [${resourceType} → ${parameterPath}] Fetched ${values.length} values`) const resourceId = await getResourceId(supabase, resourceType, prerelease, resourceIdCache) @@ -63,11 +63,47 @@ async function processModule( .insert(rows.slice(i, i + BATCH_SIZE)) if (error) { - throw new Error(`Insert failed for ${resourceType}${parameterPath}: ${error.message}`) + throw new Error(`Insert failed for ${resourceType} → ${parameterPath}: ${error.message}`) } } - console.log(` [${resourceType}${parameterPath}] Done: inserted ${values.length} completions`) + console.log(` [${resourceType} → ${parameterPath}] Done: inserted ${values.length} completions`) +} + +async function processMirrorModule( + supabase: SupabaseClient, + resourceType: string, + parameterPath: string, + mirrorParameter: string, + prerelease: boolean, + resourceIdCache: Map +): Promise { + console.log(`Processing mirror ${resourceType} → ${parameterPath} (mirrors ${mirrorParameter})...`) + + const resourceId = await getResourceId(supabase, resourceType, prerelease, resourceIdCache) + + // Delete any existing metadata row for this path (value IS NULL for mirror rows) + await supabase + .from('resource_parameter_completions') + .delete() + .eq('resource_type', resourceType) + .eq('resource_id', resourceId) + .eq('parameter_path', parameterPath) + + const { error } = await supabase + .from('resource_parameter_completions') + .insert({ + resource_type: resourceType, + resource_id: resourceId, + parameter_path: parameterPath, + mirror_parameter_path: mirrorParameter, + }) + + if (error) { + throw new Error(`Mirror insert failed for ${resourceType} → ${parameterPath}: ${error.message}`) + } + + console.log(` [${resourceType} → ${parameterPath}] Done: mirror metadata row written`) } async function runCompletions(env: Env): Promise { @@ -76,8 +112,10 @@ async function runCompletions(env: Env): Promise { const resourceIdCache = new Map() const results = await Promise.allSettled( - completionModules.map(({ resourceType, parameterPath, fetch }: CompletionModule) => - processModule(supabase, resourceType, parameterPath, fetch, prerelease, resourceIdCache) + completionModules.map((mod: CompletionModule) => + mod.kind === 'fetch' + ? processFetchModule(supabase, mod.resourceType, mod.parameterPath, mod.fetch, prerelease, resourceIdCache) + : processMirrorModule(supabase, mod.resourceType, mod.parameterPath, mod.mirrorParameter, prerelease, resourceIdCache) ) ) diff --git a/package.json b/package.json index adcbc486..7ec93aed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "default", - "version": "1.15.0-beta.4", + "version": "1.15.0-beta.6", "description": "Default plugin for Codify - provides 50+ declarative resources for managing development tools and system configuration across macOS and Linux", "main": "dist/index.js", "scripts": { diff --git a/scripts/generate-completions-index.ts b/scripts/generate-completions-index.ts index 29736355..c26d2715 100644 --- a/scripts/generate-completions-index.ts +++ b/scripts/generate-completions-index.ts @@ -43,15 +43,16 @@ for (const { importName, importPath } of modules) { } lines.push('') -lines.push('export interface CompletionModule {') -lines.push(' resourceType: string') -lines.push(' parameterPath: string') -lines.push(' fetch: () => Promise') -lines.push('}') +lines.push('export type CompletionModule =') +lines.push(' | { kind: \'fetch\'; resourceType: string; parameterPath: string; fetch: () => Promise }') +lines.push(' | { kind: \'mirror\'; resourceType: string; parameterPath: string; mirrorParameter: string }') lines.push('') lines.push('export const completionModules: CompletionModule[] = [') for (const { importName, resourceType, parameterPath } of modules) { - lines.push(` { resourceType: '${resourceType}', parameterPath: '${parameterPath}', fetch: ${importName} },`) + // A mirror module exports { mirrorParameter: '...' }, a fetch module exports a function. + lines.push(` typeof ${importName} === 'function'`) + lines.push(` ? { kind: 'fetch', resourceType: '${resourceType}', parameterPath: '${parameterPath}', fetch: ${importName} }`) + lines.push(` : { kind: 'mirror', resourceType: '${resourceType}', parameterPath: '${parameterPath}', mirrorParameter: (${importName} as any).mirrorParameter },`) } lines.push(']') lines.push('') diff --git a/src/resources/go/goenv/completions/goenv.$.global.ts b/src/resources/go/goenv/completions/goenv.$.global.ts new file mode 100644 index 00000000..1b00184c --- /dev/null +++ b/src/resources/go/goenv/completions/goenv.$.global.ts @@ -0,0 +1 @@ +export default { mirrorParameter: '$.goVersions' } as const; diff --git a/src/resources/java/jenv/completions/jenv.$.global.ts b/src/resources/java/jenv/completions/jenv.$.global.ts new file mode 100644 index 00000000..673cadc1 --- /dev/null +++ b/src/resources/java/jenv/completions/jenv.$.global.ts @@ -0,0 +1 @@ +export default { mirrorParameter: '$.add' } as const; diff --git a/src/resources/javascript/fast-node-manager/completions/fast-node-manager.$.defaultVersion.ts b/src/resources/javascript/fast-node-manager/completions/fast-node-manager.$.defaultVersion.ts new file mode 100644 index 00000000..103d6901 --- /dev/null +++ b/src/resources/javascript/fast-node-manager/completions/fast-node-manager.$.defaultVersion.ts @@ -0,0 +1 @@ +export default { mirrorParameter: '$.nodeVersions' } as const; diff --git a/src/resources/javascript/nvm/completions/nvm.$.global.ts b/src/resources/javascript/nvm/completions/nvm.$.global.ts new file mode 100644 index 00000000..103d6901 --- /dev/null +++ b/src/resources/javascript/nvm/completions/nvm.$.global.ts @@ -0,0 +1 @@ +export default { mirrorParameter: '$.nodeVersions' } as const; diff --git a/src/resources/python/pyenv/completions/pyenv.$.global.ts b/src/resources/python/pyenv/completions/pyenv.$.global.ts new file mode 100644 index 00000000..d63f1999 --- /dev/null +++ b/src/resources/python/pyenv/completions/pyenv.$.global.ts @@ -0,0 +1 @@ +export default { mirrorParameter: '$.pythonVersions' } as const; diff --git a/src/resources/ruby/rbenv/completions/rbenv.$.global.ts b/src/resources/ruby/rbenv/completions/rbenv.$.global.ts new file mode 100644 index 00000000..5cf00cc5 --- /dev/null +++ b/src/resources/ruby/rbenv/completions/rbenv.$.global.ts @@ -0,0 +1 @@ +export default { mirrorParameter: '$.rubyVersions' } as const; diff --git a/src/resources/xcodes/completions/xcodes.$.selected.ts b/src/resources/xcodes/completions/xcodes.$.selected.ts new file mode 100644 index 00000000..6687c92f --- /dev/null +++ b/src/resources/xcodes/completions/xcodes.$.selected.ts @@ -0,0 +1 @@ +export default { mirrorParameter: '$.xcodeVersions' } as const; From 31cbf486e9a304ab916234d36a40c7d487d0a80e Mon Sep 17 00:00:00 2001 From: kevinwang Date: Mon, 6 Jul 2026 11:36:59 -0400 Subject: [PATCH 08/18] feat: add additional simulators and accept license --- package.json | 2 +- ...s-simulators.$.simulators[x].deviceType.ts | 161 ++++++++++++++---- .../ios-simulators.$.simulators[x].runtime.ts | 51 +++--- .../ios/ios-simulator/ios-simulator.ts | 67 +++++++- src/resources/xcodes/xcodes-resource.ts | 21 ++- 5 files changed, 239 insertions(+), 63 deletions(-) diff --git a/package.json b/package.json index 7ec93aed..3ed97681 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "default", - "version": "1.15.0-beta.6", + "version": "1.15.0", "description": "Default plugin for Codify - provides 50+ declarative resources for managing development tools and system configuration across macOS and Linux", "main": "dist/index.js", "scripts": { diff --git a/src/resources/ios/ios-simulator/completions/ios-simulators.$.simulators[x].deviceType.ts b/src/resources/ios/ios-simulator/completions/ios-simulators.$.simulators[x].deviceType.ts index 8635f10c..95dc3374 100644 --- a/src/resources/ios/ios-simulator/completions/ios-simulators.$.simulators[x].deviceType.ts +++ b/src/resources/ios/ios-simulator/completions/ios-simulators.$.simulators[x].deviceType.ts @@ -1,55 +1,142 @@ -// 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 { +export default async function loadSimulatorDeviceTypes(): Promise { 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', + // iPhone + 'com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro-Max', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-Air', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-17', 'com.apple.CoreSimulator.SimDeviceType.iPhone-16-Pro', 'com.apple.CoreSimulator.SimDeviceType.iPhone-16-Pro-Max', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-16e', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-16', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-16-Plus', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro-Max', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-15', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-15-Plus', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-14-Pro', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-14-Pro-Max', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-14', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-14-Plus', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-SE-3rd-generation', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-13-Pro', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-13-Pro-Max', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-13', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-13-mini', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-12-Pro', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-12-Pro-Max', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-12', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-12-mini', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-SE--2nd-generation-', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-11-Pro', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-11-Pro-Max', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-11', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-XR', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-XS', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-XS-Max', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-X', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-8', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-8-Plus', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-7', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-7-Plus', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-SE', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-6s', + 'com.apple.CoreSimulator.SimDeviceType.iPhone-6s-Plus', - // iPad mini - 'com.apple.CoreSimulator.SimDeviceType.iPad-mini-6th-generation', - 'com.apple.CoreSimulator.SimDeviceType.iPad-mini-A17-Pro', + // iPad Pro + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-13-inch-M5-16GB', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-13-inch-M5-12GB', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M5-16GB', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M5-12GB', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-13-inch-M4-16GB', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-13-inch-M4-8GB', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M4-16GB', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M4-8GB', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-12-9-inch-6th-generation-16GB', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-12-9-inch-6th-generation-8GB', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-4th-generation-16GB', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-4th-generation-8GB', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-12-9-inch-5th-generation', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-3rd-generation', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro--12-9-inch---4th-generation-', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro--11-inch---2nd-generation-', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro--12-9-inch---3rd-generation-', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro--11-inch-', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro--10-5-inch-', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro--12-9-inch---2nd-generation-', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro--9-7-inch-', + 'com.apple.CoreSimulator.SimDeviceType.iPad-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-M3', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Air-11-inch-M3', 'com.apple.CoreSimulator.SimDeviceType.iPad-Air-13-inch-M2', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Air-11-inch-M2', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Air-5th-generation', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Air--4th-generation-', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Air--3rd-generation-', + 'com.apple.CoreSimulator.SimDeviceType.iPad-Air-2', - // 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', + // iPad + 'com.apple.CoreSimulator.SimDeviceType.iPad-A16', + 'com.apple.CoreSimulator.SimDeviceType.iPad-10th-generation', + 'com.apple.CoreSimulator.SimDeviceType.iPad-9th-generation', + 'com.apple.CoreSimulator.SimDeviceType.iPad--8th-generation-', + 'com.apple.CoreSimulator.SimDeviceType.iPad--7th-generation-', + 'com.apple.CoreSimulator.SimDeviceType.iPad--6th-generation-', + 'com.apple.CoreSimulator.SimDeviceType.iPad--5th-generation-', - // 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', + // iPad mini + 'com.apple.CoreSimulator.SimDeviceType.iPad-mini-A17-Pro', + 'com.apple.CoreSimulator.SimDeviceType.iPad-mini-6th-generation', + 'com.apple.CoreSimulator.SimDeviceType.iPad-mini--5th-generation-', + 'com.apple.CoreSimulator.SimDeviceType.iPad-mini-4', // Apple TV 'com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-3rd-generation-4K', 'com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-3rd-generation-1080p', + 'com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-2nd-generation-4K', + 'com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-2nd-generation-1080p', + 'com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-4K', + 'com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-1080p', + 'com.apple.CoreSimulator.SimDeviceType.Apple-TV-1080p', + + // Apple Watch + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-11-46mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-11-42mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-10-46mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-10-42mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Ultra-3-49mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Ultra-2-49mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-9-45mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-9-41mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-SE-3-44mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-SE-3-40mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-8-45mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-8-41mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Ultra-49mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-SE-44mm-2nd-generation', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-SE-40mm-2nd-generation', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-7-45mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-7-41mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-6-44mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-6-40mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-SE-44mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-SE-40mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-5-44mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-5-40mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-4-44mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-4-40mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-3-42mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-3-38mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-2-42mm', + 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-2-38mm', // Apple Vision Pro + 'com.apple.CoreSimulator.SimDeviceType.Apple-Vision-Pro-4K', 'com.apple.CoreSimulator.SimDeviceType.Apple-Vision-Pro', + + // iPod touch + 'com.apple.CoreSimulator.SimDeviceType.iPod-touch--7th-generation-', ]; } diff --git a/src/resources/ios/ios-simulator/completions/ios-simulators.$.simulators[x].runtime.ts b/src/resources/ios/ios-simulator/completions/ios-simulators.$.simulators[x].runtime.ts index c781be4c..c65b2d64 100644 --- a/src/resources/ios/ios-simulator/completions/ios-simulators.$.simulators[x].runtime.ts +++ b/src/resources/ios/ios-simulator/completions/ios-simulators.$.simulators[x].runtime.ts @@ -1,38 +1,43 @@ -// 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 { 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-26-0', 'com.apple.CoreSimulator.SimRuntime.iOS-18-4', + 'com.apple.CoreSimulator.SimRuntime.iOS-18-3', + 'com.apple.CoreSimulator.SimRuntime.iOS-18-2', + 'com.apple.CoreSimulator.SimRuntime.iOS-18-1', + 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', + 'com.apple.CoreSimulator.SimRuntime.iOS-17-5', + 'com.apple.CoreSimulator.SimRuntime.iOS-17-4', + 'com.apple.CoreSimulator.SimRuntime.iOS-17-2', + 'com.apple.CoreSimulator.SimRuntime.iOS-17-0', + 'com.apple.CoreSimulator.SimRuntime.iOS-16-4', + 'com.apple.CoreSimulator.SimRuntime.iOS-16-2', + 'com.apple.CoreSimulator.SimRuntime.iOS-16-1', + 'com.apple.CoreSimulator.SimRuntime.iOS-16-0', // 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-26-0', + 'com.apple.CoreSimulator.SimRuntime.watchOS-11-4', 'com.apple.CoreSimulator.SimRuntime.watchOS-11-2', + 'com.apple.CoreSimulator.SimRuntime.watchOS-11-0', + 'com.apple.CoreSimulator.SimRuntime.watchOS-10-4', + 'com.apple.CoreSimulator.SimRuntime.watchOS-10-0', // 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-26-0', + 'com.apple.CoreSimulator.SimRuntime.tvOS-18-4', 'com.apple.CoreSimulator.SimRuntime.tvOS-18-2', + 'com.apple.CoreSimulator.SimRuntime.tvOS-18-0', + 'com.apple.CoreSimulator.SimRuntime.tvOS-17-4', + 'com.apple.CoreSimulator.SimRuntime.tvOS-17-0', // 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-26-0', + 'com.apple.CoreSimulator.SimRuntime.xrOS-2-4', 'com.apple.CoreSimulator.SimRuntime.xrOS-2-2', + 'com.apple.CoreSimulator.SimRuntime.xrOS-2-0', + 'com.apple.CoreSimulator.SimRuntime.xrOS-1-2', + 'com.apple.CoreSimulator.SimRuntime.xrOS-1-0', ]; } diff --git a/src/resources/ios/ios-simulator/ios-simulator.ts b/src/resources/ios/ios-simulator/ios-simulator.ts index a8a68a48..e7ae85ec 100644 --- a/src/resources/ios/ios-simulator/ios-simulator.ts +++ b/src/resources/ios/ios-simulator/ios-simulator.ts @@ -25,6 +25,13 @@ const schema = z.object({ .array(simulatorSchema) .optional() .describe('List of iOS simulators to create and manage.'), + acceptLicense: z + .boolean() + .optional() + .describe( + 'Automatically accept the Xcode license agreement if it has not been accepted yet. ' + + 'Runs `sudo xcodebuild -license accept`. Defaults to true.' + ), }); export type IosSimulatorConfig = z.infer; @@ -40,6 +47,16 @@ interface SimctlDevicesOutput { devices: Record; } +interface SimctlRuntime { + identifier: string; + isAvailable: boolean; + name: string; +} + +interface SimctlRuntimesOutput { + runtimes: SimctlRuntime[]; +} + const defaultConfig: Partial & { os: any } = { simulators: [], os: ['macOS'], @@ -85,6 +102,12 @@ const exampleMultiDevice: ExampleConfig = { ], }; +// com.apple.CoreSimulator.SimRuntime.iOS-18-0 → "iOS" +function runtimeToXcodebuildPlatform(runtimeId: string): string { + const match = runtimeId.match(/SimRuntime\.([a-zA-Z]+)/); + return match ? match[1] : runtimeId; +} + export class IosSimulatorResource extends Resource { getSettings(): ResourceSettings { return { @@ -109,6 +132,7 @@ export class IosSimulatorResource extends Resource { current.filter((c) => desired.some((d) => d.name === c.name)), canModify: true, }, + acceptLicense: { type: 'boolean', setting: true, default: true }, }, }; } @@ -132,7 +156,11 @@ export class IosSimulatorResource extends Resource { } async create(plan: CreatePlan): Promise { + if (plan.desiredConfig.acceptLicense !== false) { + await this.acceptLicenseIfNeeded(); + } await this.assertSimctlAvailable(); + await this.assertRuntimesAvailable(plan.desiredConfig.simulators ?? []); const $ = getPty(); for (const sim of plan.desiredConfig.simulators ?? []) { await $.spawn( @@ -192,7 +220,8 @@ export class IosSimulatorResource extends Resource { private async assertSimctlAvailable(): Promise { const $ = getPty(); - const { status } = await $.spawnSafe('xcrun simctl help'); + // Use `xcrun -f simctl` to locate the binary without invoking it — avoids triggering the license prompt + const { status } = await $.spawnSafe('xcrun -f simctl'); if (status !== SpawnStatus.SUCCESS) { throw new Error( 'xcrun simctl is not available. Xcode must be installed to manage iOS simulators. ' + @@ -201,6 +230,42 @@ export class IosSimulatorResource extends Resource { } } + private async assertRuntimesAvailable(simulators: SimulatorDeclaration[]): Promise { + const $ = getPty(); + const { status, data } = await $.spawnSafe('xcrun simctl list runtimes --json'); + if (status !== SpawnStatus.SUCCESS) return; // can't verify, let simctl fail with its own message + + let availableRuntimes: Set; + try { + const parsed: SimctlRuntimesOutput = JSON.parse(data); + availableRuntimes = new Set( + parsed.runtimes.filter((r) => r.isAvailable).map((r) => r.identifier), + ); + } catch { + return; + } + + const missing = [...new Set(simulators.map((s) => s.runtime))].filter( + (r) => !availableRuntimes.has(r), + ); + + if (missing.length > 0) { + throw new Error( + `The following simulator runtime${missing.length > 1 ? 's are' : ' is'} not installed:\n` + + missing.map((r) => ` ${r}`).join('\n') + '\n' + + 'Download runtimes in Xcode → Settings → Platforms, or via:\n' + + missing.map((r) => ` xcodebuild -downloadPlatform ${runtimeToXcodebuildPlatform(r)}`).join('\n'), + ); + } + } + + private async acceptLicenseIfNeeded(): Promise { + const $ = getPty(); + const { status } = await $.spawnSafe('xcodebuild -license status'); + if (status === SpawnStatus.SUCCESS) return; + await $.spawn('xcodebuild -license accept', { requiresRoot: true }); + } + private async listAllDevices(): Promise | null> { const $ = getPty(); const { status, data } = await $.spawnSafe('xcrun simctl list devices --json'); diff --git a/src/resources/xcodes/xcodes-resource.ts b/src/resources/xcodes/xcodes-resource.ts index 1ede2bef..b40bd5e4 100644 --- a/src/resources/xcodes/xcodes-resource.ts +++ b/src/resources/xcodes/xcodes-resource.ts @@ -1,4 +1,5 @@ import { + CreatePlan, ExampleConfig, Resource, ResourceSettings, @@ -39,6 +40,13 @@ const schema = z .describe( 'Apple ID password. Required alongside appleId for non-interactive installs.' ), + acceptLicense: z + .boolean() + .optional() + .describe( + 'Automatically accept the Xcode license agreement after installation. ' + + 'Runs `sudo xcodebuild -license accept`. Defaults to true.' + ), }) .describe('xcodes resource — install and manage multiple Xcode versions via the xcodes CLI'); @@ -86,6 +94,7 @@ export class XcodesResource extends Resource { selected: { type: 'stateful', definition: new XcodesSelectedParameter(), order: 2 }, appleId: { type: 'string', setting: true }, appleIdPassword: { type: 'string', setting: true }, + acceptLicense: { type: 'boolean', setting: true, default: true }, }, }; } @@ -96,11 +105,21 @@ export class XcodesResource extends Resource { return status === SpawnStatus.SUCCESS ? {} : null; } - override async create(): Promise { + override async create(plan: CreatePlan): Promise { await Utils.installViaPkgMgr('xcodes', undefined, PackageManager.BREW); + if (plan.desiredConfig.acceptLicense !== false) { + await this.acceptLicenseIfNeeded(); + } } override async destroy(): Promise { await Utils.uninstallViaPkgMgr('xcodes', undefined, PackageManager.BREW); } + + private async acceptLicenseIfNeeded(): Promise { + const $ = getPty(); + const { status } = await $.spawnSafe('xcodebuild -license status'); + if (status === SpawnStatus.SUCCESS) return; + await $.spawn('xcodebuild -license accept', { requiresRoot: true }); + } } From ab070198f048b93a1addc25b1d718d54d6a65f7e Mon Sep 17 00:00:00 2001 From: kevinwang Date: Mon, 6 Jul 2026 11:39:08 -0400 Subject: [PATCH 09/18] feat: skip fixer job if fix already exists --- .github/workflows/claude-fixer.yml | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/claude-fixer.yml b/.github/workflows/claude-fixer.yml index fabdbc34..1e91fa5a 100644 --- a/.github/workflows/claude-fixer.yml +++ b/.github/workflows/claude-fixer.yml @@ -19,8 +19,27 @@ jobs: echo "runner=ubuntu-latest" >> $GITHUB_OUTPUT fi - fix-on-failure: + check-existing-pr: + runs-on: ubuntu-latest needs: determine-runner + outputs: + skip: ${{ steps.check.outputs.skip }} + steps: + - id: check + env: + GH_TOKEN: ${{ github.token }} + run: | + BRANCH="${{ github.event.workflow_run.head_branch }}" + COUNT=$(gh pr list --repo "$GITHUB_REPOSITORY" --head "fix/claude-auto-" --base "$BRANCH" --state open --json number --jq 'length') + if [ "${COUNT:-0}" -gt 0 ]; then + echo "skip=true" >> $GITHUB_OUTPUT + else + echo "skip=false" >> $GITHUB_OUTPUT + fi + + fix-on-failure: + needs: [determine-runner, check-existing-pr] + if: needs.check-existing-pr.outputs.skip == 'false' runs-on: ${{ needs.determine-runner.outputs.runner }} permissions: contents: write From 96d6eeae7a56024550bec4ca76f050bc889679da Mon Sep 17 00:00:00 2001 From: kevinwang Date: Sun, 12 Jul 2026 13:32:51 -0400 Subject: [PATCH 10/18] fix: added code to install missing runtimes --- package.json | 2 +- .../ios/ios-simulator/ios-simulator.ts | 119 ++++++++++++++---- 2 files changed, 98 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 3ed97681..4fb3307a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "default", - "version": "1.15.0", + "version": "1.15.1-beta.2", "description": "Default plugin for Codify - provides 50+ declarative resources for managing development tools and system configuration across macOS and Linux", "main": "dist/index.js", "scripts": { diff --git a/src/resources/ios/ios-simulator/ios-simulator.ts b/src/resources/ios/ios-simulator/ios-simulator.ts index e7ae85ec..556b298a 100644 --- a/src/resources/ios/ios-simulator/ios-simulator.ts +++ b/src/resources/ios/ios-simulator/ios-simulator.ts @@ -32,6 +32,20 @@ const schema = z.object({ 'Automatically accept the Xcode license agreement if it has not been accepted yet. ' + 'Runs `sudo xcodebuild -license accept`. Defaults to true.' ), + downloadRuntimes: z + .boolean() + .optional() + .describe( + 'Automatically download missing simulator runtimes via `xcodebuild -downloadPlatform`. ' + + 'Defaults to true. Set to false if you manage runtimes manually through Xcode.' + ), + destroyRuntimes: z + .boolean() + .optional() + .describe( + 'Delete simulator runtimes that are no longer used by any simulator when this resource is destroyed. ' + + 'Defaults to false. Enable with caution — runtimes are several GB and take time to re-download.' + ), }); export type IosSimulatorConfig = z.infer; @@ -133,6 +147,8 @@ export class IosSimulatorResource extends Resource { canModify: true, }, acceptLicense: { type: 'boolean', setting: true, default: true }, + downloadRuntimes: { type: 'boolean', setting: true, default: true }, + destroyRuntimes: { type: 'boolean', setting: true, default: false }, }, }; } @@ -160,17 +176,32 @@ export class IosSimulatorResource extends Resource { await this.acceptLicenseIfNeeded(); } await this.assertSimctlAvailable(); - await this.assertRuntimesAvailable(plan.desiredConfig.simulators ?? []); + const simulators = plan.desiredConfig.simulators ?? []; + if (plan.desiredConfig.downloadRuntimes !== false) { + await this.downloadMissingRuntimes(simulators); + } else { + await this.assertRuntimesAvailable(simulators); + } const $ = getPty(); - for (const sim of plan.desiredConfig.simulators ?? []) { - await $.spawn( + for (const sim of simulators) { + const { status, data } = await $.spawnSafe( `xcrun simctl create "${sim.name}" "${sim.deviceType}" "${sim.runtime}"`, { interactive: true }, ); + if (status !== SpawnStatus.SUCCESS) { + if (data.includes('Invalid runtime')) { + throw new Error( + `Runtime "${sim.runtime}" is not installed or not available.\n` + + 'Download it in Xcode → Settings → Platforms, or via:\n' + + ` xcodebuild -downloadPlatform ${runtimeToXcodebuildPlatform(sim.runtime)}`, + ); + } + throw new Error(`Failed to create simulator "${sim.name}": ${data}`); + } } } - async modify(pc: ParameterChange, _plan: ModifyPlan): Promise { + async modify(pc: ParameterChange, plan: ModifyPlan): Promise { if (pc.name !== 'simulators') return; const $ = getPty(); @@ -194,6 +225,11 @@ export class IosSimulatorResource extends Resource { } const toAdd = desired.filter((d) => !previous.some((p) => p.name === d.name)); + if (toAdd.length > 0 && plan.desiredConfig.downloadRuntimes !== false) { + await this.downloadMissingRuntimes(toAdd); + } else if (toAdd.length > 0) { + await this.assertRuntimesAvailable(toAdd); + } for (const sim of toAdd) { await $.spawn( `xcrun simctl create "${sim.name}" "${sim.deviceType}" "${sim.runtime}"`, @@ -207,7 +243,10 @@ export class IosSimulatorResource extends Resource { const allDevices = await this.listAllDevices(); if (!allDevices) return; - for (const sim of plan.currentConfig.simulators ?? []) { + const simulatorsToDestroy = plan.currentConfig.simulators ?? []; + const runtimesInUse = new Set(simulatorsToDestroy.map((s) => s.runtime)); + + for (const sim of simulatorsToDestroy) { for (const devices of Object.values(allDevices)) { const match = devices.find((d) => d.name === sim.name); if (match) { @@ -216,6 +255,10 @@ export class IosSimulatorResource extends Resource { } } } + + if (plan.currentConfig.destroyRuntimes) { + await this.deleteOrphanedRuntimes(runtimesInUse); + } } private async assertSimctlAvailable(): Promise { @@ -230,35 +273,67 @@ export class IosSimulatorResource extends Resource { } } - private async assertRuntimesAvailable(simulators: SimulatorDeclaration[]): Promise { + private async deleteOrphanedRuntimes(candidateRuntimes: Set): Promise { + if (candidateRuntimes.size === 0) return; + + const allDevices = await this.listAllDevices(); + const stillInUse = new Set(); + if (allDevices) { + for (const [runtimeId, devices] of Object.entries(allDevices)) { + if (devices.length > 0) stillInUse.add(runtimeId); + } + } + + const $ = getPty(); + for (const runtimeId of candidateRuntimes) { + if (!stillInUse.has(runtimeId)) { + await $.spawnSafe(`xcrun simctl runtime delete "${runtimeId}"`); + } + } + } + + private async getMissingRuntimes(simulators: SimulatorDeclaration[]): Promise { const $ = getPty(); const { status, data } = await $.spawnSafe('xcrun simctl list runtimes --json'); - if (status !== SpawnStatus.SUCCESS) return; // can't verify, let simctl fail with its own message + if (status !== SpawnStatus.SUCCESS) return []; - let availableRuntimes: Set; + let allRuntimes: SimctlRuntime[]; try { const parsed: SimctlRuntimesOutput = JSON.parse(data); - availableRuntimes = new Set( - parsed.runtimes.filter((r) => r.isAvailable).map((r) => r.identifier), - ); + allRuntimes = parsed.runtimes; } catch { - return; + return []; } - const missing = [...new Set(simulators.map((s) => s.runtime))].filter( - (r) => !availableRuntimes.has(r), - ); + const availableIds = new Set(allRuntimes.filter((r) => r.isAvailable).map((r) => r.identifier)); + const requiredRuntimes = [...new Set(simulators.map((s) => s.runtime))]; + return requiredRuntimes.filter((r) => !availableIds.has(r)); + } - if (missing.length > 0) { - throw new Error( - `The following simulator runtime${missing.length > 1 ? 's are' : ' is'} not installed:\n` + - missing.map((r) => ` ${r}`).join('\n') + '\n' + - 'Download runtimes in Xcode → Settings → Platforms, or via:\n' + - missing.map((r) => ` xcodebuild -downloadPlatform ${runtimeToXcodebuildPlatform(r)}`).join('\n'), - ); + private async downloadMissingRuntimes(simulators: SimulatorDeclaration[]): Promise { + const missing = await this.getMissingRuntimes(simulators); + if (missing.length === 0) return; + + const $ = getPty(); + const platforms = [...new Set(missing.map(runtimeToXcodebuildPlatform))]; + for (const platform of platforms) { + await $.spawn(`xcodebuild -downloadPlatform ${platform}`, { stdin: true }); } } + private async assertRuntimesAvailable(simulators: SimulatorDeclaration[]): Promise { + const missing = await this.getMissingRuntimes(simulators); + if (missing.length === 0) return; + + const lines: string[] = [ + `The following simulator runtime${missing.length > 1 ? 's are' : ' is'} not installed or not available:`, + ...missing.map((r) => ` ${r}`), + 'Download runtimes in Xcode → Settings → Platforms, or via:', + ...missing.map((r) => ` xcodebuild -downloadPlatform ${runtimeToXcodebuildPlatform(r)}`), + ]; + throw new Error(lines.join('\n')); + } + private async acceptLicenseIfNeeded(): Promise { const $ = getPty(); const { status } = await $.spawnSafe('xcodebuild -license status'); From af9b80c37afef65f5dc5fc7f5f768768e8761c9d Mon Sep 17 00:00:00 2001 From: kevinwang Date: Sun, 12 Jul 2026 21:12:59 -0400 Subject: [PATCH 11/18] fix: refresh not working properly after deletion --- package.json | 2 +- .../ios/ios-simulator/ios-simulator.ts | 119 +++++++++++------- 2 files changed, 77 insertions(+), 44 deletions(-) diff --git a/package.json b/package.json index 4fb3307a..2679f00a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "default", - "version": "1.15.1-beta.2", + "version": "1.15.1-beta.8", "description": "Default plugin for Codify - provides 50+ declarative resources for managing development tools and system configuration across macOS and Linux", "main": "dist/index.js", "scripts": { diff --git a/src/resources/ios/ios-simulator/ios-simulator.ts b/src/resources/ios/ios-simulator/ios-simulator.ts index 556b298a..7a3adcaa 100644 --- a/src/resources/ios/ios-simulator/ios-simulator.ts +++ b/src/resources/ios/ios-simulator/ios-simulator.ts @@ -54,6 +54,7 @@ interface SimDevice { udid: string; name: string; state: string; + isAvailable: boolean; deviceTypeIdentifier: string; } @@ -71,6 +72,7 @@ interface SimctlRuntimesOutput { runtimes: SimctlRuntime[]; } + const defaultConfig: Partial & { os: any } = { simulators: [], os: ['macOS'], @@ -122,6 +124,20 @@ function runtimeToXcodebuildPlatform(runtimeId: string): string { return match ? match[1] : runtimeId; } +// com.apple.CoreSimulator.SimRuntime.iOS-26-0 → "com.apple.CoreSimulator.SimRuntime.iOS-26" +function runtimeMajorPrefix(runtimeId: string): string { + // Strip the patch version component (last -N segment) to get a major-match prefix. + // iOS-26-0 and iOS-26-3 both share prefix "...iOS-26". + return runtimeId.replace(/-\d+$/, ''); +} + +// Two runtime IDs match if they are equal or share the same major-version prefix. +// Allows iOS-26-0 (declared) to match iOS-26-3 (installed). +function runtimesMatch(a: string, b: string): boolean { + if (a === b) return true; + return runtimeMajorPrefix(a) === runtimeMajorPrefix(b); +} + export class IosSimulatorResource extends Resource { getSettings(): ResourceSettings { return { @@ -141,7 +157,7 @@ export class IosSimulatorResource extends Resource { isElementEqual: (a, b) => a.name === b.name && a.deviceType === b.deviceType && - a.runtime === b.runtime, + runtimesMatch(a.runtime, b.runtime), filterInStatelessMode: (desired, current) => current.filter((c) => desired.some((d) => d.name === c.name)), canModify: true, @@ -154,21 +170,7 @@ export class IosSimulatorResource extends Resource { } async refresh(): Promise | null> { - const allDevices = await this.listAllDevices(); - if (!allDevices) return null; - - const simulators: SimulatorDeclaration[] = []; - for (const [runtimeId, devices] of Object.entries(allDevices)) { - for (const device of devices) { - simulators.push({ - name: device.name, - deviceType: device.deviceTypeIdentifier, - runtime: runtimeId, - }); - } - } - - return simulators.length > 0 ? { simulators } : null; + return null; } async create(plan: CreatePlan): Promise { @@ -182,10 +184,17 @@ export class IosSimulatorResource extends Resource { } else { await this.assertRuntimesAvailable(simulators); } + const available = await this.listAvailableRuntimes(); + const existingDevices = await this.listAllDevices() ?? {}; + const existingNames = new Set( + Object.values(existingDevices).flat().map((d) => d.name), + ); const $ = getPty(); for (const sim of simulators) { + if (existingNames.has(sim.name)) continue; + const runtimeId = this.resolveRuntimeId(sim.runtime, available); const { status, data } = await $.spawnSafe( - `xcrun simctl create "${sim.name}" "${sim.deviceType}" "${sim.runtime}"`, + `xcrun simctl create "${sim.name}" "${sim.deviceType}" "${runtimeId}"`, { interactive: true }, ); if (status !== SpawnStatus.SUCCESS) { @@ -230,9 +239,11 @@ export class IosSimulatorResource extends Resource { } else if (toAdd.length > 0) { await this.assertRuntimesAvailable(toAdd); } + const available = await this.listAvailableRuntimes(); for (const sim of toAdd) { + const runtimeId = this.resolveRuntimeId(sim.runtime, available); await $.spawn( - `xcrun simctl create "${sim.name}" "${sim.deviceType}" "${sim.runtime}"`, + `xcrun simctl create "${sim.name}" "${sim.deviceType}" "${runtimeId}"`, { interactive: true }, ); } @@ -273,41 +284,44 @@ export class IosSimulatorResource extends Resource { } } - private async deleteOrphanedRuntimes(candidateRuntimes: Set): Promise { - if (candidateRuntimes.size === 0) return; - - const allDevices = await this.listAllDevices(); - const stillInUse = new Set(); - if (allDevices) { - for (const [runtimeId, devices] of Object.entries(allDevices)) { - if (devices.length > 0) stillInUse.add(runtimeId); - } - } - - const $ = getPty(); - for (const runtimeId of candidateRuntimes) { - if (!stillInUse.has(runtimeId)) { - await $.spawnSafe(`xcrun simctl runtime delete "${runtimeId}"`); - } - } - } - - private async getMissingRuntimes(simulators: SimulatorDeclaration[]): Promise { + private async listAvailableRuntimes(): Promise { const $ = getPty(); const { status, data } = await $.spawnSafe('xcrun simctl list runtimes --json'); if (status !== SpawnStatus.SUCCESS) return []; - - let allRuntimes: SimctlRuntime[]; try { const parsed: SimctlRuntimesOutput = JSON.parse(data); - allRuntimes = parsed.runtimes; + return parsed.runtimes.filter((r) => r.isAvailable); } catch { return []; } + } + + // Resolve a declared runtime ID to the actual available one. + // If the exact ID is available, return it unchanged. + // Otherwise fall back to the highest-versioned available runtime sharing the same major prefix + // (e.g. iOS-26-0 → iOS-26-3 when only iOS 26.3 is installed). + private resolveRuntimeId(declared: string, available: SimctlRuntime[]): string { + if (available.some((r) => r.identifier === declared)) return declared; + + const prefix = runtimeMajorPrefix(declared); + const candidates = available.filter((r) => r.identifier.startsWith(prefix)); + if (candidates.length === 0) return declared; + + // Pick the lexicographically highest patch version + candidates.sort((a, b) => b.identifier.localeCompare(a.identifier)); + return candidates[0].identifier; + } - const availableIds = new Set(allRuntimes.filter((r) => r.isAvailable).map((r) => r.identifier)); + private async getMissingRuntimes(simulators: SimulatorDeclaration[]): Promise { + const available = await this.listAvailableRuntimes(); + const availableIds = new Set(available.map((r) => r.identifier)); const requiredRuntimes = [...new Set(simulators.map((s) => s.runtime))]; - return requiredRuntimes.filter((r) => !availableIds.has(r)); + return requiredRuntimes.filter((declared) => { + if (availableIds.has(declared)) return false; + // Also consider it present if a same-major-version runtime is available + const prefix = runtimeMajorPrefix(declared); + return !available.some((r) => r.identifier.startsWith(prefix)); + }); } private async downloadMissingRuntimes(simulators: SimulatorDeclaration[]): Promise { @@ -334,6 +348,25 @@ export class IosSimulatorResource extends Resource { throw new Error(lines.join('\n')); } + private async deleteOrphanedRuntimes(candidateRuntimes: Set): Promise { + if (candidateRuntimes.size === 0) return; + + const allDevices = await this.listAllDevices(); + const stillInUse = new Set(); + if (allDevices) { + for (const [runtimeId, devices] of Object.entries(allDevices)) { + if (devices.length > 0) stillInUse.add(runtimeId); + } + } + + const $ = getPty(); + for (const runtimeId of candidateRuntimes) { + if (!stillInUse.has(runtimeId)) { + await $.spawnSafe(`xcrun simctl runtime delete "${runtimeId}"`); + } + } + } + private async acceptLicenseIfNeeded(): Promise { const $ = getPty(); const { status } = await $.spawnSafe('xcodebuild -license status'); From b9017c27015ee128b60f540e7f97072d69e62c12 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Sun, 12 Jul 2026 21:43:59 -0400 Subject: [PATCH 12/18] fix: added ability to download older runtimes --- .../ios/ios-simulator/ios-simulator.ts | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/resources/ios/ios-simulator/ios-simulator.ts b/src/resources/ios/ios-simulator/ios-simulator.ts index 7a3adcaa..31844cd3 100644 --- a/src/resources/ios/ios-simulator/ios-simulator.ts +++ b/src/resources/ios/ios-simulator/ios-simulator.ts @@ -174,10 +174,10 @@ export class IosSimulatorResource extends Resource { } async create(plan: CreatePlan): Promise { + await this.assertSimctlAvailable(); if (plan.desiredConfig.acceptLicense !== false) { await this.acceptLicenseIfNeeded(); } - await this.assertSimctlAvailable(); const simulators = plan.desiredConfig.simulators ?? []; if (plan.desiredConfig.downloadRuntimes !== false) { await this.downloadMissingRuntimes(simulators); @@ -201,8 +201,10 @@ export class IosSimulatorResource extends Resource { if (data.includes('Invalid runtime')) { throw new Error( `Runtime "${sim.runtime}" is not installed or not available.\n` + - 'Download it in Xcode → Settings → Platforms, or via:\n' + - ` xcodebuild -downloadPlatform ${runtimeToXcodebuildPlatform(sim.runtime)}`, + 'If this is the latest runtime for your Xcode version, download it via:\n' + + ` xcodebuild -downloadPlatform ${runtimeToXcodebuildPlatform(sim.runtime)}\n` + + 'Older runtimes cannot be fetched this way — install them from Xcode → Settings → ' + + 'Platforms, or via `xcodes runtimes install ""`.', ); } throw new Error(`Failed to create simulator "${sim.name}": ${data}`); @@ -333,6 +335,13 @@ export class IosSimulatorResource extends Resource { for (const platform of platforms) { await $.spawn(`xcodebuild -downloadPlatform ${platform}`, { stdin: true }); } + + // `xcodebuild -downloadPlatform` only ever fetches the single latest runtime for a + // platform (tied to the active Xcode version) — it cannot target an older/specific + // version. If the declared runtime is still missing after the download, it's an + // older version that isn't downloadable this way, so fail with actionable guidance + // instead of letting the caller hit a generic "Invalid runtime" error later. + await this.assertRuntimesAvailable(simulators); } private async assertRuntimesAvailable(simulators: SimulatorDeclaration[]): Promise { @@ -342,8 +351,10 @@ export class IosSimulatorResource extends Resource { const lines: string[] = [ `The following simulator runtime${missing.length > 1 ? 's are' : ' is'} not installed or not available:`, ...missing.map((r) => ` ${r}`), - 'Download runtimes in Xcode → Settings → Platforms, or via:', - ...missing.map((r) => ` xcodebuild -downloadPlatform ${runtimeToXcodebuildPlatform(r)}`), + '`xcodebuild -downloadPlatform` only fetches the latest runtime for the currently ' + + 'installed Xcode version — it cannot download older/specific versions.', + 'To install an older runtime, either pick it from Xcode → Settings → Platforms, ' + + 'or use a tool like `xcodes runtimes install ""` (https://github.com/XcodesOrg/xcodes).', ]; throw new Error(lines.join('\n')); } From 86704f2c4a92ef1efc8147e941a3fddd114a7eb1 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Sun, 12 Jul 2026 21:47:01 -0400 Subject: [PATCH 13/18] feat: disable running the claude fixer job for manual runs --- .github/workflows/claude-fixer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-fixer.yml b/.github/workflows/claude-fixer.yml index 1e91fa5a..7eb807fb 100644 --- a/.github/workflows/claude-fixer.yml +++ b/.github/workflows/claude-fixer.yml @@ -7,7 +7,7 @@ on: jobs: determine-runner: runs-on: ubuntu-latest - if: ${{ github.event.workflow_run.conclusion == 'failure' }} + if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.event == 'schedule' }} outputs: runner: ${{ steps.pick.outputs.runner }} steps: From b98447fa577caeea16ed4cff4ca422d47a1cb987 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Sun, 12 Jul 2026 21:56:29 -0400 Subject: [PATCH 14/18] fix: update test to ios 26 --- test/ios/ios-simulator.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/ios/ios-simulator.test.ts b/test/ios/ios-simulator.test.ts index eb3ad4be..c4d39ed5 100644 --- a/test/ios/ios-simulator.test.ts +++ b/test/ios/ios-simulator.test.ts @@ -22,7 +22,7 @@ describe('iOS Simulator tests', { skip }, async () => { { name: 'codify-test-iphone', deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15', - runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', + runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-26-0', }, ], }, @@ -41,12 +41,12 @@ describe('iOS Simulator tests', { skip }, async () => { { name: 'codify-test-iphone', deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPhone-15', - runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', + runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-26-0', }, { name: 'codify-test-ipad', deviceType: 'com.apple.CoreSimulator.SimDeviceType.iPad-mini-6th-generation', - runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-18-0', + runtime: 'com.apple.CoreSimulator.SimRuntime.iOS-26-0', }, ], }], From 753627585798bf35f0b61ff244c941823a0b0265 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Mon, 13 Jul 2026 23:35:49 -0400 Subject: [PATCH 15/18] fix: ios simulators refresh --- .../ios/ios-simulator/ios-simulator.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/resources/ios/ios-simulator/ios-simulator.ts b/src/resources/ios/ios-simulator/ios-simulator.ts index 31844cd3..666ab602 100644 --- a/src/resources/ios/ios-simulator/ios-simulator.ts +++ b/src/resources/ios/ios-simulator/ios-simulator.ts @@ -170,7 +170,22 @@ export class IosSimulatorResource extends Resource { } async refresh(): Promise | null> { - return null; + const allDevices = await this.listAllDevices(); + if (!allDevices) return null; + + const simulators: SimulatorDeclaration[] = []; + for (const [runtimeId, devices] of Object.entries(allDevices)) { + for (const device of devices) { + if (!device.isAvailable) continue; + simulators.push({ + name: device.name, + deviceType: device.deviceTypeIdentifier, + runtime: runtimeId, + }); + } + } + + return simulators.length > 0 ? { simulators } : null; } async create(plan: CreatePlan): Promise { From 29cb52686be5c986118b49839224e623aabd00d1 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Tue, 14 Jul 2026 00:05:13 -0400 Subject: [PATCH 16/18] feat: bump library --- package-lock.json | 12 ++++++------ package.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 90bdef73..41d7eb32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "default", - "version": "1.12.0", + "version": "1.15.1-beta.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "default", - "version": "1.12.0", + "version": "1.15.1-beta.8", "license": "ISC", "dependencies": { - "@codifycli/plugin-core": "^1.2.5", + "@codifycli/plugin-core": "^1.2.6-beta.1", "@codifycli/schemas": "1.2.0", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", @@ -172,9 +172,9 @@ } }, "node_modules/@codifycli/plugin-core": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@codifycli/plugin-core/-/plugin-core-1.2.5.tgz", - "integrity": "sha512-PioxDEsm/mIFL4jd0Ve7BpWUrccw0kvBO6B1jOdDHjxqOg4AGZfQP6gSzn+Le2rnMXs6VTgwYvvthvoPAQgNew==", + "version": "1.2.6-beta.1", + "resolved": "https://registry.npmjs.org/@codifycli/plugin-core/-/plugin-core-1.2.6-beta.1.tgz", + "integrity": "sha512-Somb4IsgMN1zl7bolW3+yw45FpyKy1Bj5Y1jM6KVR7lOVcdp9uqIGMseyNMqcCvhEORM0qKLwibHYr8+XFz9bg==", "license": "ISC", "dependencies": { "@codifycli/schemas": "^1.2.0", diff --git a/package.json b/package.json index 2679f00a..d96a18a2 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "license": "ISC", "type": "module", "dependencies": { - "@codifycli/plugin-core": "^1.2.5", + "@codifycli/plugin-core": "^1.2.6-beta.1", "@codifycli/schemas": "1.2.0", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", From c7eb744c8c7dc71078b87ae939164543d7f797d3 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Tue, 14 Jul 2026 00:25:03 -0400 Subject: [PATCH 17/18] chore: bump version --- package-lock.json | 8 ++++---- package.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 41d7eb32..81d3ef90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.15.1-beta.8", "license": "ISC", "dependencies": { - "@codifycli/plugin-core": "^1.2.6-beta.1", + "@codifycli/plugin-core": "^1.2.6", "@codifycli/schemas": "1.2.0", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", @@ -172,9 +172,9 @@ } }, "node_modules/@codifycli/plugin-core": { - "version": "1.2.6-beta.1", - "resolved": "https://registry.npmjs.org/@codifycli/plugin-core/-/plugin-core-1.2.6-beta.1.tgz", - "integrity": "sha512-Somb4IsgMN1zl7bolW3+yw45FpyKy1Bj5Y1jM6KVR7lOVcdp9uqIGMseyNMqcCvhEORM0qKLwibHYr8+XFz9bg==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@codifycli/plugin-core/-/plugin-core-1.2.6.tgz", + "integrity": "sha512-HNCpMZV2TeAVysFDOZWIoWEXkpsYOUybawC0/nTS1p8O0Lv14nnHgKFWaiCP5WRGNcjjCHADUsnJHYWZWsLSEw==", "license": "ISC", "dependencies": { "@codifycli/schemas": "^1.2.0", diff --git a/package.json b/package.json index d96a18a2..e38d52a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "default", - "version": "1.15.1-beta.8", + "version": "1.15.1", "description": "Default plugin for Codify - provides 50+ declarative resources for managing development tools and system configuration across macOS and Linux", "main": "dist/index.js", "scripts": { @@ -41,7 +41,7 @@ "license": "ISC", "type": "module", "dependencies": { - "@codifycli/plugin-core": "^1.2.6-beta.1", + "@codifycli/plugin-core": "^1.2.6", "@codifycli/schemas": "1.2.0", "ajv": "^8.18.0", "ajv-formats": "^2.1.1", From 20a53404bd8621311ff36db1288d4de3f6971698 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Tue, 14 Jul 2026 00:37:36 -0400 Subject: [PATCH 18/18] fix: git-lfs bug --- package.json | 2 +- src/resources/git/lfs/git-lfs.ts | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index e38d52a2..ea499d8c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "default", - "version": "1.15.1", + "version": "1.15.2", "description": "Default plugin for Codify - provides 50+ declarative resources for managing development tools and system configuration across macOS and Linux", "main": "dist/index.js", "scripts": { diff --git a/src/resources/git/lfs/git-lfs.ts b/src/resources/git/lfs/git-lfs.ts index e54a7fc6..5b566a10 100644 --- a/src/resources/git/lfs/git-lfs.ts +++ b/src/resources/git/lfs/git-lfs.ts @@ -74,14 +74,14 @@ export class GitLfsResource extends Resource { const gitLfsStatus = await $.spawn('git lfs env', { cwd: os.homedir(), interactive: true, disableWrapping: true }); const lines = gitLfsStatus.data.split('\n'); - // When git lfs exists but git lfs install hasn't been called then git lfs env returns: - // git config filter.lfs.process = "" - // git config filter.lfs.smudge = "" - // git config filter.lfs.clean = "" - const emptyLfsLines = lines.filter((l) => l.includes('git config filter.lfs')) - .map((l) => l.split('=')[1].trim()) - .includes('""'); - - return !emptyLfsLines; + // When git lfs exists but git lfs install hasn't been called (or has been undone by + // `git lfs uninstall`, which removes the whole [filter "lfs"] section), `git lfs env` prints + // either empty values (`git config filter.lfs.process = ""`) or no filter.lfs lines at all — + // both mean "not initialized", not just the empty-string case. + const filterLfsLines = lines.filter((l) => l.includes('git config filter.lfs')); + const hasConfiguredFilter = filterLfsLines.length > 0 + && filterLfsLines.every((l) => l.split('=')[1].trim() !== '""'); + + return hasConfiguredFilter; } }