> ## Documentation Index
> Fetch the complete documentation index at: https://docs.freezeray.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# freezeray freeze

> Freeze a schema version

# freezeray freeze

Freeze a schema version by generating immutable fixture artifacts from the actual SwiftData schema running in iOS Simulator.

## Synopsis

```bash theme={null}
freezeray freeze <VERSION> [OPTIONS]
```

## Description

The `freeze` command captures an immutable snapshot of your SwiftData schema at a specific version. It:

1. **Auto-detects** your Xcode project and scheme
2. **Discovers** `@FreezeSchema(version: "X.X.X")` annotations in source files
3. **Builds and runs** your project in iOS Simulator
4. **Extracts fixtures** from the simulator container to `FreezeRay/Fixtures/`
5. **Scaffolds drift test** (if it doesn't already exist)
6. **Updates Xcode project** to include test files and fixture resources

This process ensures fixtures are based on the **actual database schema** that SwiftData creates, not a theoretical representation.

<Warning>
  **Migration tests are NOT generated by `freeze`.** Use [`freezeray generate migration-tests`](/cli/generate#generate-migration-tests) when starting work on a new schema version.
</Warning>

## Arguments

### `<VERSION>`

**Required.** The version string to freeze (e.g., "1.0.0", "2.0.0").

Must match a `@FreezeSchema(version: "X.X.X")` annotation in your source code:

```swift theme={null}
@FreezeSchema(version: "1.0.0")
enum AppSchemaV1: VersionedSchema {
    // ...
}
```

## Options

### `--simulator <NAME>`

**Default:** `iPhone 17`

Specify which iOS Simulator to use for freezing:

```bash theme={null}
freezeray freeze 1.0.0 --simulator "iPhone 15 Pro"
```

<Info>
  Always use a consistent simulator across your team to avoid subtle schema differences.
</Info>

### `--scheme <NAME>`

**Default:** Auto-detected

Specify the Xcode scheme to build:

```bash theme={null}
freezeray freeze 1.0.0 --scheme MyApp
```

If not specified, FreezeRay attempts to auto-detect the scheme from your Xcode project.

### `--force`

**Dangerous!** Overwrite existing frozen fixtures for the specified version.

```bash theme={null}
freezeray freeze 1.0.0 --force
```

<Warning>
  Only use `--force` during development **before** shipping v1.0.0 to production. Overwriting frozen fixtures breaks the immutability guarantee.
</Warning>

**When to use:**

* You're iterating on an unreleased schema version
* Fixtures were corrupted and need regeneration

**Never use if:**

* The version has been shipped to production users
* Other developers depend on the frozen fixtures

### `--output <PATH>`

**Default:** `FreezeRay/Fixtures/<VERSION>/`

Override the output directory for fixtures:

```bash theme={null}
freezeray freeze 1.0.0 --output /custom/path/fixtures/1.0.0
```

<Info>
  Custom output paths are useful for monorepos or non-standard project structures.
</Info>

### `--config <PATH>`

**Experimental.** Specify path to `.freezeray.yml` config file:

```bash theme={null}
freezeray freeze 1.0.0 --config .freezeray.yml
```

Currently unused but reserved for future configuration features.

## What Gets Created

After running `freezeray freeze 1.0.0`, you'll find:

### Fixtures: `FreezeRay/Fixtures/1.0.0/`

| File                  | Description                                           |
| --------------------- | ----------------------------------------------------- |
| `App-1_0_0.sqlite`    | Real SQLite database with schema structure            |
| `schema-1_0_0.json`   | Human-readable schema metadata (entity names, counts) |
| `schema-1_0_0.sql`    | SQL DDL statements for schema structure               |
| `schema-1_0_0.sha256` | SHA256 checksum for drift detection                   |
| `export_metadata.txt` | Timestamp and version info                            |

### Tests: `FreezeRay/Tests/`

| File                           | Description                                        |
| ------------------------------ | -------------------------------------------------- |
| `AppSchemaV1_DriftTests.swift` | Drift detection test (scaffolded if doesn't exist) |

<Info>
  Drift test files are scaffolded **once** and then owned by you. Customize them with assertions as needed.

  **Migration tests are NOT created by `freeze`.** Use `freezeray generate migration-tests` when starting work on a new schema version.
</Info>

## Examples

### Basic usage

```bash theme={null}
cd MyApp
freezeray freeze 1.0.0
```

**Output:**

```
🔹 FreezeRay v0.4.3
🔹 Freezing schema version: 1.0.0

🔹 Auto-detecting project configuration...
   Found: MyApp.xcodeproj
   Scheme: MyApp (auto-detected)
   Test target: MyAppTests (inferred)

🔹 Parsing source files for @Freeze(version: "1.0.0")...
   Found: AppSchemaV1 in Schemas.swift

🔹 Generating freeze test...
   Generated: FreezeSchemaV1_0_0_Test.swift

🔹 Building and running tests in simulator...
   Simulator: iPhone 17
   Running: xcodebuild test -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone 17'
   ...

🔹 Extracting fixtures from simulator...
   Copied: App-1_0_0.sqlite → FreezeRay/Fixtures/1.0.0/
   Copied: schema-1_0_0.json → FreezeRay/Fixtures/1.0.0/
   Copied: schema-1_0_0.sql → FreezeRay/Fixtures/1.0.0/
   Copied: schema-1_0_0.sha256 → FreezeRay/Fixtures/1.0.0/
   Copied: export_metadata.txt → FreezeRay/Fixtures/1.0.0/

🔹 Scaffolding drift test...
   Created: AppSchemaV1_1_0_0_DriftTests.swift

✅ Schema v1.0.0 frozen successfully!

📝 Next steps:
   1. Review fixtures: FreezeRay/Fixtures/1.0.0
   2. Customize drift test: FreezeRay/Tests/AppSchemaV1_1_0_0_DriftTests.swift
   3. Run tests: xcodebuild test -scheme MyApp
   4. Commit to git: git add FreezeRay/
```

### Complete workflow with migration

```bash theme={null}
# 1. Freeze v1
freezeray freeze 1.0.0

# 2. Generate migration test when starting v2 work
freezeray generate migration-tests --from-schema 1.0.0 --to-schema 2.0.0

# 3. Edit test, implement schema, run tests normally

# 4. Freeze v2
freezeray freeze 2.0.0
```

**Output of `freeze 2.0.0`:**

```
...
🔹 Scaffolding drift test...
   Created: AppSchemaV2_DriftTests.swift
   ✅ Added test files to MyAppTests target
   ✅ Added FreezeRay/Fixtures to test bundle resources
...
```

Note: `freeze` does NOT generate migration tests. You created `MigrateV1_0_0toV2_0_0_Tests.swift` earlier with the `generate` command.

### Custom simulator

```bash theme={null}
freezeray freeze 1.0.0 --simulator "iPhone 15 Pro"
```

Uses iPhone 15 Pro instead of the default iPhone 17.

### Force overwrite (development only)

```bash theme={null}
freezeray freeze 1.0.0 --force
```

**Output:**

```
⚠️  WARNING: Overwriting existing fixtures for v1.0.0
⚠️  Frozen schemas should be immutable once shipped to production!
...
```

## Requirements

* **macOS 14+**
* **Xcode 15+**
* **iOS Simulator** (iPhone 17 recommended)
* **Project structure:**
  * Must have `@FreezeSchema(version: "X.X.X")` annotation in source code
  * Must have a test target (for running freeze tests)
  * Xcode project or Swift Package with iOS target

## Common Errors

### "No @Freeze(version: "X.X.X") annotation found"

**Cause:** The specified version doesn't match any `@FreezeSchema` annotation in your source files.

**Fix:**

```swift theme={null}
// Add to your schema file
@FreezeSchema(version: "1.0.0")
enum AppSchemaV1: VersionedSchema {
    // ...
}
```

Then run:

```bash theme={null}
freezeray freeze 1.0.0
```

### "Fixtures for vX.X.X already exist"

**Cause:** You've already frozen this version.

**Solution:**

If you **haven't shipped** to production yet:

```bash theme={null}
freezeray freeze 1.0.0 --force  # ⚠️ Only during development!
```

If you **have shipped** to production:

```swift theme={null}
// Create new schema version instead
@FreezeSchema(version: "1.0.1")
enum AppSchemaV1_1: VersionedSchema {
    // Updated schema
}
```

```bash theme={null}
freezeray freeze 1.0.1
```

### "Build failed" or "Tests failed"

**Cause:** Your project doesn't build or tests are failing.

**Fix:**

1. Ensure your project builds in Xcode: ⌘B
2. Ensure your tests pass in Xcode: ⌘U
3. Check the Xcode console for specific errors

Common issues:

* Missing dependencies
* Syntax errors in schema
* Test target not configured correctly

### "Simulator not found: iPhone 17"

**Cause:** The specified simulator doesn't exist.

**Fix:**

List available simulators:

```bash theme={null}
xcrun simctl list devices available
```

Use an existing simulator:

```bash theme={null}
freezeray freeze 1.0.0 --simulator "iPhone 15"
```

### "Could not update Xcode project"

**Cause:** Xcode project manipulation failed (rare).

**Solution:** Manually add files to Xcode:

1. Open Xcode
2. Drag `FreezeRay/Tests/` to your test target
3. Add `FreezeRay/` folder to test target's Copy Bundle Resources:
   * Select test target
   * Build Phases → Copy Bundle Resources → + → Add `FreezeRay/` folder

## How It Works

### Step 1: Project Discovery

FreezeRay auto-detects:

* Xcode project (`*.xcodeproj`) or Swift Package (`Package.swift`)
* Scheme (first available scheme or user-specified)
* Test target (inferred from scheme name)

### Step 2: Source Parsing

Uses SwiftSyntax to parse all `.swift` files and find:

* `@FreezeSchema(version: "X.X.X")` annotations
* `SchemaMigrationPlan` types

No configuration file needed!

### Step 3: Test Generation

Creates a temporary test file:

```swift theme={null}
// FreezeSchemaV1_0_0_Test.swift (temporary)
import XCTest
import FreezeRay
@testable import MyApp

final class FreezeSchemaV1_0_0_Test: XCTestCase {
    func testFreezeSchemaV1_0_0() throws {
        try AppSchemaV1.__freezeray_freeze_1_0_0()
    }
}
```

This test calls the macro-generated freeze function.

### Step 4: Simulator Execution

Runs:

```bash theme={null}
xcodebuild test \
  -scheme MyApp \
  -destination 'platform=iOS Simulator,name=iPhone 17' \
  -only-testing:MyAppTests/FreezeSchemaV1_0_0_Test
```

The test function:

1. Creates a ModelContainer with your schema
2. Generates SQLite database in simulator's Documents directory
3. Exports fixtures to `/tmp/FreezeRay/Fixtures/1.0.0/`

### Step 5: Fixture Extraction

CLI copies fixtures from `/tmp/FreezeRay/Fixtures/1.0.0/` to your project's `FreezeRay/Fixtures/1.0.0/`.

<Info>
  iOS Simulator can't write directly to your source tree, so fixtures go through `/tmp` as an intermediary.
</Info>

### Step 6: Test Scaffolding

Generates permanent test files:

**Drift test:**

```swift theme={null}
// AppSchemaV1_1_0_0_DriftTests.swift
@Test("AppSchemaV1 v1.0.0 has not drifted")
func testAppSchemaV1_1_0_0_Drift() throws {
    try AppSchemaV1.__freezeray_check_1_0_0()
}
```

**Migration test (if previous version exists):**

```swift theme={null}
// MigrateV1_0_0toV2_0_0_Tests.swift
@Test("Migrate v1.0.0 → v2.0.0")
func testMigrateV1_0_0toV2_0_0() throws {
    try FreezeRayRuntime.testMigration(
        from: AppSchemaV1.self,
        to: AppSchemaV2.self,
        migrationPlan: AppMigrations.self
    )

    // TODO: Add custom data validation
}
```

### Step 7: Xcode Project Update

For Xcode projects (not Swift Packages):

* Adds test files to test target's sources
* Adds `FreezeRay/` folder to test target's resources

## Best Practices

<Warning>
  **Never freeze on a dirty working tree.** Commit all changes before freezing so the fixtures are traceable to a specific commit.
</Warning>

### Commit Fixtures Immediately

```bash theme={null}
freezeray freeze 1.0.0
git add FreezeRay/
git commit -m "Freeze schema v1.0.0"
git push
```

This ensures:

* Fixtures are in version control
* CI can run drift/migration tests
* Team members get the fixtures

### Use Semantic Versioning

```bash theme={null}
freezeray freeze 1.0.0  # Initial schema
freezeray freeze 2.0.0  # Breaking changes
freezeray freeze 2.1.0  # Additive changes
freezeray freeze 2.1.1  # Bug fixes
```

Semantic versioning helps communicate the impact of schema changes.

### Freeze Before Releasing

Freeze your schema **before** submitting to App Store:

```bash theme={null}
# Before release
freezeray freeze 2.0.0
git add FreezeRay/
git commit -m "Freeze schema v2.0.0 for App Store release"
git push

# Then release to App Store
```

This ensures production users get the exact schema you tested.

### Run Tests After Freezing

```bash theme={null}
freezeray freeze 1.0.0

# Verify drift test passes
xcodebuild test -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone 17'
```

Drift tests should pass immediately after freezing. If they fail, something went wrong during the freeze.

## Next Steps

<Columns cols={2}>
  <Card title="Drift Detection" icon="triangle-exclamation" href="/concepts/drift-detection">
    Understand how frozen fixtures detect schema changes
  </Card>

  <Card title="Migration Testing" icon="code-branch" href="/concepts/migration-testing">
    Learn to test migrations between frozen versions
  </Card>

  <Card title="First Freeze Guide" icon="snowflake" href="/guides/first-freeze">
    Step-by-step tutorial for freezing your first schema
  </Card>

  <Card title="@FreezeSchema Macro" icon="code" href="/macros/freeze-schema">
    See what code the macro generates
  </Card>
</Columns>
