Skip to content

Getting started

1. Install

uv add bluefox-upgrade

2. Create a .bluefox dotfile

The .bluefox file tracks which files the upgrade engine manages and what version the project is on.

from bluefox_upgrade import create_dotfile, full_tracked, structured_tracked, sections_tracked

tracked = dict([
    full_tracked("Dockerfile"),
    sections_tracked("app/__init__.py", ["bluefox:routers"]),
    structured_tracked("pyproject.toml"),
])

dotfile = create_dotfile(project_dir, version="0.1.0", tracked=tracked)

Tracking strategies

Managed What it tracks Conflict detection
full Entire file SHA-256 hash of full contents
sections Marked sections only SHA-256 hash of full file
structured Logical fields (e.g. dependencies) No hash — always safe to update

3. Define an upgrade

from bluefox_upgrade import Upgrade, ReplaceFile, BumpDependency, AddDependency

upgrade = Upgrade(
    from_version="0.1.0",
    to_version="0.2.0",
    description="Upgrade to v0.2.0",
    operations=[
        ReplaceFile(path="Dockerfile", template="dockerfile_v2.jinja", description="update base image"),
        BumpDependency(name="fastapi", old_spec=">=0.115", new_spec=">=0.120"),
        AddDependency(name="httpx", spec=">=0.27"),
    ],
)

4. Plan and execute

from bluefox_upgrade import plan_upgrade, check_conflicts, execute_upgrade

# Plan: builds the operation list, collapses redundant ops
plan = plan_upgrade(dotfile, [upgrade], target="0.2.0")

# Check: detect files the user has modified since last upgrade
conflicts = check_conflicts(plan, project_dir, dotfile)

if conflicts:
    for c in conflicts:
        print(f"Conflict: {c.conflict.message}")

# Execute: apply operations
result = execute_upgrade(plan, project_dir, dotfile, render_template)
print(f"Applied {len(result.applied)} operations")

5. Conflict modes

The executor supports three modes when it encounters user-modified files:

# Default: abort on first conflict
result = execute_upgrade(plan, project_dir, dotfile, render_template)

# Skip: preserve user edits, skip conflicting operations
result = execute_upgrade(plan, project_dir, dotfile, render_template, skip_conflicts=True)

# Force: backup originals, then overwrite
result = execute_upgrade(plan, project_dir, dotfile, render_template, force=True)

In force mode, originals are saved to .bluefox-backups/<timestamp>/.

6. Template rendering

The executor takes a render_template callable. You provide the implementation:

from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader("templates"))

def render_template(template_name: str) -> str:
    return env.get_template(template_name).render()

result = execute_upgrade(plan, project_dir, dotfile, render_template)

Next steps