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
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.
30 changes: 29 additions & 1 deletion reflex/utils/frontend_skeleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,34 @@ 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()
path_ops.mkdir(output_path.parent)
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 +381,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