Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions news/6765.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Process persisted package.json files before mirroring them into the web directory.
29 changes: 28 additions & 1 deletion reflex/utils/frontend_skeleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,33 @@ def sync_root_lockfile_to_web(filename: str, prune: bool = True) -> bool:
)


def sync_root_package_json_to_web() -> bool:
"""Render the persisted root package.json into ``.web``.

``package.json`` is not a plain lockfile: Reflex owns required fields such
as the dev/build scripts, while the persisted root copy carries dependency
pins and user-added metadata. Re-render it instead of byte-copying so a
damaged root copy cannot remove required framework entries from ``.web``.

Returns:
True if an existing ``.web/package.json`` was meaningfully changed.
Initial creation does not count as a meaningful change since no install
cache could exist yet.
"""
root_package_json_path = get_root_lockfile_path(constants.PackageJson.PATH)
if not root_package_json_path.exists():
return sync_root_lockfile_to_web(constants.PackageJson.PATH, prune=False)

output_path = get_web_lockfile_path(constants.PackageJson.PATH)
rendered = _compile_package_json()
if output_path.exists() and output_path.read_text() == rendered:
return False

changed = output_path.exists()
output_path.write_text(rendered)
return changed
Comment thread
harsh21234i marked this conversation as resolved.


def sync_root_lockfiles_to_web() -> bool:
"""Mirror every persisted lockfile into ``.web``.

Expand All @@ -353,7 +380,7 @@ def sync_root_lockfiles_to_web() -> bool:
"""
# Materialize results so every lockfile is synced
changed = [sync_root_lockfile_to_web(name) for name in LOCKFILE_NAMES] + [
sync_root_lockfile_to_web(name, prune=False) for name in NO_PRUNE_LOCKFILE_NAMES
sync_root_package_json_to_web()
]
return any(changed)

Expand Down
39 changes: 39 additions & 0 deletions tests/units/test_prerequisites.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,25 @@ def test_sync_root_lockfiles_to_web_keeps_web_package_json(tmp_path, monkeypatch
assert not web_lock.exists()


def test_sync_root_lockfiles_to_web_processes_package_json(tmp_path, monkeypatch):
"""Persisted package.json is rendered into .web instead of byte-copied."""
web_dir = tmp_path / constants.Dirs.WEB
web_dir.mkdir()
_patch_web_dir(monkeypatch, web_dir)

root_pkg = tmp_path / constants.Bun.ROOT_LOCKFILE_DIR / constants.PackageJson.PATH
root_pkg.parent.mkdir(parents=True, exist_ok=True)
root_pkg.write_text(json.dumps({"dependencies": {"react": "19.2.5"}}))

with chdir(tmp_path):
frontend_skeleton.sync_root_lockfiles_to_web()

web_pkg = json.loads((web_dir / constants.PackageJson.PATH).read_text())
assert web_pkg["dependencies"] == {"react": "19.2.5"}
assert web_pkg["scripts"]["dev"] == constants.PackageJson.Commands.DEV
assert web_pkg["scripts"]["export"] == constants.PackageJson.Commands.EXPORT


def test_install_frontend_packages_syncs_root_bun_lock(
install_packages_env: InstallPackagesEnv,
):
Expand Down Expand Up @@ -907,6 +926,26 @@ def run_package_manager(args, **kwargs):
)


def test_install_frontend_packages_repairs_missing_package_json_scripts(
install_packages_env: InstallPackagesEnv,
):
"""A damaged persisted package.json is repaired before it reaches .web."""
env = install_packages_env
env.root_package_json.write_text(json.dumps({}))
calls = _record_calls(env)

env.install()

web_package_json = json.loads(env.web_package_json.read_text())
root_package_json = json.loads(env.root_package_json.read_text())
assert web_package_json["scripts"]["dev"] == constants.PackageJson.Commands.DEV
assert (
web_package_json["scripts"]["export"] == constants.PackageJson.Commands.EXPORT
)
assert root_package_json == web_package_json
assert calls == []


def test_compile_package_json_recovers_dependencies(tmp_path, monkeypatch):
"""_compile_package_json should restore deps/devDeps from reflex.lock."""
root_pkg = tmp_path / constants.Bun.ROOT_LOCKFILE_DIR / constants.PackageJson.PATH
Expand Down
Loading