From 4df6eed795c8d0e72f5ab2eb7a78af4ef9f47a77 Mon Sep 17 00:00:00 2001 From: Harsh Thakare Date: Fri, 17 Jul 2026 20:52:32 +0530 Subject: [PATCH 1/2] Process persisted package json before web sync --- news/6765.bugfix.md | 1 + reflex/utils/frontend_skeleton.py | 29 ++++++++++++++++++++++- tests/units/test_prerequisites.py | 39 +++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 news/6765.bugfix.md diff --git a/news/6765.bugfix.md b/news/6765.bugfix.md new file mode 100644 index 00000000000..c2dd6b35e29 --- /dev/null +++ b/news/6765.bugfix.md @@ -0,0 +1 @@ +Process persisted package.json files before mirroring them into the web directory. diff --git a/reflex/utils/frontend_skeleton.py b/reflex/utils/frontend_skeleton.py index de9f29e7bbd..b2333cad891 100644 --- a/reflex/utils/frontend_skeleton.py +++ b/reflex/utils/frontend_skeleton.py @@ -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 + + def sync_root_lockfiles_to_web() -> bool: """Mirror every persisted lockfile into ``.web``. @@ -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) diff --git a/tests/units/test_prerequisites.py b/tests/units/test_prerequisites.py index 16a03aab01d..f542ae839af 100644 --- a/tests/units/test_prerequisites.py +++ b/tests/units/test_prerequisites.py @@ -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, ): @@ -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 From 3b29939a3d016e46e0de8637560abd2ea7e905e3 Mon Sep 17 00:00:00 2001 From: HARSH THAKARE <151563948+harsh21234i@users.noreply.github.com> Date: Fri, 17 Jul 2026 21:08:09 +0530 Subject: [PATCH 2/2] Update reflex/utils/frontend_skeleton.py Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- reflex/utils/frontend_skeleton.py | 1 + 1 file changed, 1 insertion(+) diff --git a/reflex/utils/frontend_skeleton.py b/reflex/utils/frontend_skeleton.py index b2333cad891..9f1314a0f4e 100644 --- a/reflex/utils/frontend_skeleton.py +++ b/reflex/utils/frontend_skeleton.py @@ -368,6 +368,7 @@ def sync_root_package_json_to_web() -> bool: return False changed = output_path.exists() + path_ops.mkdir(output_path.parent) output_path.write_text(rendered) return changed