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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/resource-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down Expand Up @@ -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).

Expand Down
21 changes: 17 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -369,11 +369,24 @@ The Codify Editor supports auto-complete for certain resource parameters (e.g. H

### Adding completions for a parameter

1. Create `src/resources/<category>/<resource>/completions/<type>.<param>.ts`
1. Create `src/resources/<category>/<resource>/completions/<resource-type>.<jsonpath>.ts`
2. Export a default async function returning `Promise<string[]>` — 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. 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
Expand Down
230 changes: 163 additions & 67 deletions completions-cron/src/__generated__/completions-index.ts

Large diffs are not rendered by default.

52 changes: 45 additions & 7 deletions completions-cron/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,18 @@ async function getResourceId(
return data[0].id
}

async function processModule(
async function processFetchModule(
supabase: SupabaseClient,
resourceType: string,
parameterPath: string,
fetchFn: () => Promise<string[]>,
prerelease: boolean,
resourceIdCache: Map<string, string>
): Promise<void> {
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)

Expand All @@ -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<string, string>
): Promise<void> {
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<void> {
Expand All @@ -76,8 +112,10 @@ async function runCompletions(env: Env): Promise<void> {
const resourceIdCache = new Map<string, string>()

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)
)
)

Expand Down
64 changes: 64 additions & 0 deletions docs/resources/(resources)/ios-simulators.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
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`. 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. 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:

- **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.<Device>`. Run `xcrun simctl list devicetypes` to see identifiers available on your machine.
- **runtime** *(string, required)* — CoreSimulator runtime identifier. Use the format `com.apple.CoreSimulator.SimRuntime.<Platform>-<Version>`. Run `xcrun simctl list runtimes` to see installed runtimes.


## Example usage:

```json title="codify.jsonc"
[
{
"type": "ios-simulators",
"simulators": [
{
"name": "iPhone 15 Dev",
"deviceType": "com.apple.CoreSimulator.SimDeviceType.iPhone-15",
"runtime": "com.apple.CoreSimulator.SimRuntime.iOS-18-0",

}
],
"os": ["macOS"]
}
]
```

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

},
{
"name": "iPad Pro 11-inch",
"deviceType": "com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-M4",
"runtime": "com.apple.CoreSimulator.SimRuntime.iOS-18-0",

}
],
"os": ["macOS"]
}
]
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "default",
"version": "1.14.0",
"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": {
Expand Down
20 changes: 11 additions & 9 deletions scripts/generate-completions-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <resource-type>.<parameter-name>.ts, got: ${filename}`
`Completion file must be named <resource-type>.<jsonpath>.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')
Expand All @@ -42,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<string[]>')
lines.push('}')
lines.push('export type CompletionModule =')
lines.push(' | { kind: \'fetch\'; resourceType: string; parameterPath: string; fetch: () => Promise<string[]> }')
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('')
Expand All @@ -59,5 +61,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}`)
}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -87,6 +88,7 @@ runPlugin(Plugin.create(
[
new GitResource(),
new XcodeToolsResource(),
new IosSimulatorResource(),
new XcodesResource(),
new PathResource(),
new AliasResource(),
Expand Down
1 change: 1 addition & 0 deletions src/resources/go/goenv/completions/goenv.$.global.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default { mirrorParameter: '$.goVersions' } as const;
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Known CoreSimulator device type identifiers shipped with Xcode.
// These identifiers are stable across Xcode versions for each device family.
export default async function loadIosSimulatorDeviceTypes(): Promise<string[]> {
return [
// iPhone SE
'com.apple.CoreSimulator.SimDeviceType.iPhone-SE-3rd-generation',

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

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

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

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

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

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

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

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

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

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

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

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

// visionOS
'com.apple.CoreSimulator.SimRuntime.xrOS-1-0',
'com.apple.CoreSimulator.SimRuntime.xrOS-1-2',
'com.apple.CoreSimulator.SimRuntime.xrOS-2-0',
'com.apple.CoreSimulator.SimRuntime.xrOS-2-2',
];
}
Loading
Loading