From 52263351cdd608e1af9550712596e29f03f651fb 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 1/7] 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 bc202f14951ef7a1da4ce5aa94a1d987c0d86f2f Mon Sep 17 00:00:00 2001 From: kevinwang Date: Thu, 2 Jul 2026 20:47:41 -0400 Subject: [PATCH 2/7] 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 4843100357ee4a4f4c343c35ef714db2cf2f0541 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Thu, 2 Jul 2026 22:39:04 -0400 Subject: [PATCH 3/7] 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 360fb4e2b8144762d168c9d08d2ae149ac718bef Mon Sep 17 00:00:00 2001 From: kevinwang Date: Fri, 3 Jul 2026 09:57:16 -0400 Subject: [PATCH 4/7] 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 9661458cc277d320b95792c9f2862dd2878cf1e5 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Fri, 3 Jul 2026 10:02:46 -0400 Subject: [PATCH 5/7] 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 4a7c95293d7a03ebaf221a41d5d0e748c4e1c88c Mon Sep 17 00:00:00 2001 From: kevinwang Date: Fri, 3 Jul 2026 10:49:53 -0400 Subject: [PATCH 6/7] 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 a660ea21c1965f0dbdc51df76dff71b7f9b39022 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Fri, 3 Jul 2026 11:47:16 -0400 Subject: [PATCH 7/7] 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;