Skip to main content

freezeray freeze

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

Synopsis

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.
Migration tests are NOT generated by freeze. Use freezeray generate migration-tests when starting work on a new schema version.

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:
@FreezeSchema(version: "1.0.0")
enum AppSchemaV1: VersionedSchema {
    // ...
}

Options

--simulator <NAME>

Default: iPhone 17 Specify which iOS Simulator to use for freezing:
freezeray freeze 1.0.0 --simulator "iPhone 15 Pro"
Always use a consistent simulator across your team to avoid subtle schema differences.

--scheme <NAME>

Default: Auto-detected Specify the Xcode scheme to build:
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.
freezeray freeze 1.0.0 --force
Only use --force during development before shipping v1.0.0 to production. Overwriting frozen fixtures breaks the immutability guarantee.
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:
freezeray freeze 1.0.0 --output /custom/path/fixtures/1.0.0
Custom output paths are useful for monorepos or non-standard project structures.

--config <PATH>

Experimental. Specify path to .freezeray.yml config file:
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/

FileDescription
App-1_0_0.sqliteReal SQLite database with schema structure
schema-1_0_0.jsonHuman-readable schema metadata (entity names, counts)
schema-1_0_0.sqlSQL DDL statements for schema structure
schema-1_0_0.sha256SHA256 checksum for drift detection
export_metadata.txtTimestamp and version info

Tests: FreezeRay/Tests/

FileDescription
AppSchemaV1_DriftTests.swiftDrift detection test (scaffolded if doesn’t exist)
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.

Examples

Basic usage

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

# 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

freezeray freeze 1.0.0 --simulator "iPhone 15 Pro"
Uses iPhone 15 Pro instead of the default iPhone 17.

Force overwrite (development only)

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:
// Add to your schema file
@FreezeSchema(version: "1.0.0")
enum AppSchemaV1: VersionedSchema {
    // ...
}
Then run:
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:
freezeray freeze 1.0.0 --force  # ⚠️ Only during development!
If you have shipped to production:
// Create new schema version instead
@FreezeSchema(version: "1.0.1")
enum AppSchemaV1_1: VersionedSchema {
    // Updated schema
}
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:
xcrun simctl list devices available
Use an existing simulator:
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:
// 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:
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/.
iOS Simulator can’t write directly to your source tree, so fixtures go through /tmp as an intermediary.

Step 6: Test Scaffolding

Generates permanent test files: Drift test:
// 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):
// 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

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

Commit Fixtures Immediately

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

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:
# 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

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