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
47 changes: 34 additions & 13 deletions reflex/utils/js_runtimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,21 +459,19 @@ def _is_bun_package_manager(package_manager: str) -> bool:


def _run_initial_install(primary_package_manager: str, env: dict) -> None:
"""Run the initial frozen-lockfile install with a friendly recovery hint.
"""Run the initial frozen-lockfile install and repair a mismatched Bun lock.

bun reports ``error: lockfile had changes, but lockfile is frozen`` when
the persisted lockfile cannot satisfy the recovered package.json. When
that happens, point the user at ``reflex.lock/package.json`` so they can
delete it and let Reflex regenerate the dep set from scratch on the
next run.
the persisted lockfile cannot satisfy the recovered package.json. Retry
that specific failure without ``--frozen-lockfile`` so Bun reconciles the
pair; the successful install flow then persists both files together.

Args:
primary_package_manager: Path to the package manager executable.
env: Extra environment variables for the subprocess.

Raises:
SystemExit: If the install fails. The exit message tells the user
how to recover from a frozen-lockfile mismatch when applicable.
SystemExit: If the install or mismatch recovery fails.
"""
install_args = [
primary_package_manager,
Expand All @@ -499,14 +497,37 @@ def _run_initial_install(primary_package_manager: str, env: dict) -> None:
if process.returncode == 0:
return

if any("lockfile had changes, but lockfile is frozen" in line for line in logs):
root_dir = Path.cwd() / constants.Bun.ROOT_LOCKFILE_DIR
console.error(
if _is_bun_package_manager(primary_package_manager) and any(
"lockfile had changes, but lockfile is frozen" in line for line in logs
):
console.warn(
"The persisted lockfile is out of sync with the recovered "
f"package.json. Delete the [bold]{root_dir}[/bold] directory "
"and rerun so Reflex regenerates it from scratch."
"package.json. Regenerating the lockfile."
)
raise SystemExit(1)
recovery_args = [
primary_package_manager,
"install",
"--legacy-peer-deps",
]
recovery_process = processes.new_process(
processes.get_command_with_loglevel(recovery_args),
cwd=get_web_dir(),
shell=constants.IS_WINDOWS,
env=env,
)
processes.show_status(
"Regenerating frontend lockfile",
recovery_process,
)
if recovery_process.returncode != 0:
root_dir = Path.cwd() / constants.Bun.ROOT_LOCKFILE_DIR
console.error(
"Failed to regenerate the frontend lockfile. Delete the "
f"[bold]{root_dir}[/bold] directory and rerun so Reflex "
"regenerates it from scratch."
)
raise SystemExit(1)
Comment thread
greptile-apps[bot] marked this conversation as resolved.
return

# Replay captured logs so the user can diagnose other failures (mirrors
# show_status's default error path, which we suppressed above).
Expand Down
48 changes: 45 additions & 3 deletions tests/units/test_prerequisites.py
Original file line number Diff line number Diff line change
Expand Up @@ -1096,8 +1096,50 @@ def run_package_manager(args, **kwargs):


@pytest.mark.usefixtures("install_packages_env")
def test_run_initial_install_frozen_lockfile_error_helpful_message(monkeypatch, capsys):
"""A frozen-lockfile mismatch surfaces a 'delete reflex.lock/package.json' hint."""
def test_run_initial_install_repairs_frozen_lockfile_mismatch(monkeypatch, capsys):
"""A frozen-lockfile mismatch retries without freezing the lockfile."""

class _FakeProcess:
def __init__(self, returncode):
self.returncode = returncode

calls = []
processes = iter((_FakeProcess(1), _FakeProcess(0)))

def new_process(args, **kwargs):
calls.append(args)
return next(processes)

monkeypatch.setattr(
js_runtimes.processes,
"new_process",
new_process,
)
monkeypatch.setattr(
js_runtimes.processes,
"show_status",
lambda message, process, suppress_errors=False: (
["error: lockfile had changes, but lockfile is frozen\n"]
if process.returncode
else []
),
)

js_runtimes._run_initial_install("bun", env={})

captured = capsys.readouterr()
output = captured.out + captured.err
assert "out of sync" in output
assert len(calls) == 2
assert "--frozen-lockfile" in calls[0]
assert "--frozen-lockfile" not in calls[1]
Comment thread
greptile-apps[bot] marked this conversation as resolved.


@pytest.mark.usefixtures("install_packages_env")
def test_run_initial_install_failed_recovery_has_actionable_message(
monkeypatch, capsys
):
"""A failed mismatch recovery tells the user how to regenerate from scratch."""

class _FakeProcess:
returncode = 1
Expand All @@ -1120,7 +1162,7 @@ class _FakeProcess:

captured = capsys.readouterr()
output = captured.out + captured.err
assert "out of sync" in output
assert "Failed to regenerate" in output
assert constants.Bun.ROOT_LOCKFILE_DIR in output


Expand Down