Skip to content

Commit 974be80

Browse files
committed
no need to kill nextjs on package change (#5389)
* no need to kill nextjs on package change * make it info
1 parent d6d8137 commit 974be80

File tree

1 file changed

+102
-27
lines changed

1 file changed

+102
-27
lines changed

reflex/utils/exec.py

Lines changed: 102 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import sys
1313
from collections.abc import Sequence
1414
from pathlib import Path
15+
from typing import NamedTuple, TypedDict
1516
from urllib.parse import urljoin
1617

1718
import psutil
@@ -27,26 +28,102 @@
2728
frontend_process = None
2829

2930

30-
def detect_package_change(json_file_path: Path) -> str:
31-
"""Calculates the SHA-256 hash of a JSON file and returns it as a hexadecimal string.
31+
def get_package_json_and_hash(package_json_path: Path) -> tuple[PackageJson, str]:
32+
"""Get the content of package.json and its hash.
3233
3334
Args:
34-
json_file_path: The path to the JSON file to be hashed.
35+
package_json_path: The path to the package.json file.
3536
3637
Returns:
37-
str: The SHA-256 hash of the JSON file as a hexadecimal string.
38-
39-
Example:
40-
>>> detect_package_change("package.json")
41-
'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2'
38+
A tuple containing the content of package.json as a dictionary and its SHA-256 hash.
4239
"""
43-
with json_file_path.open("r") as file:
40+
with package_json_path.open("r") as file:
4441
json_data = json.load(file)
4542

4643
# Calculate the hash
4744
json_string = json.dumps(json_data, sort_keys=True)
4845
hash_object = hashlib.sha256(json_string.encode())
49-
return hash_object.hexdigest()
46+
return (json_data, hash_object.hexdigest())
47+
48+
49+
class PackageJson(TypedDict):
50+
"""package.json content."""
51+
52+
dependencies: dict[str, str]
53+
devDependencies: dict[str, str]
54+
55+
56+
class Change(NamedTuple):
57+
"""A named tuple to represent a change in package dependencies."""
58+
59+
added: set[str]
60+
removed: set[str]
61+
62+
63+
def format_change(name: str, change: Change) -> str:
64+
"""Format the change for display.
65+
66+
Args:
67+
name: The name of the change (e.g., "dependencies" or "devDependencies").
68+
change: The Change named tuple containing added and removed packages.
69+
70+
Returns:
71+
A formatted string representing the changes.
72+
"""
73+
if not change.added and not change.removed:
74+
return ""
75+
added_str = ", ".join(sorted(change.added))
76+
removed_str = ", ".join(sorted(change.removed))
77+
change_str = f"{name}:\n"
78+
if change.added:
79+
change_str += f" Added: {added_str}\n"
80+
if change.removed:
81+
change_str += f" Removed: {removed_str}\n"
82+
return change_str.strip()
83+
84+
85+
def get_different_packages(
86+
old_package_json_content: PackageJson,
87+
new_package_json_content: PackageJson,
88+
) -> tuple[Change, Change]:
89+
"""Get the packages that are different between two package JSON contents.
90+
91+
Args:
92+
old_package_json_content: The content of the old package JSON.
93+
new_package_json_content: The content of the new package JSON.
94+
95+
Returns:
96+
A tuple containing two `Change` named tuples:
97+
- The first `Change` contains the changes in the `dependencies` section.
98+
- The second `Change` contains the changes in the `devDependencies` section.
99+
"""
100+
101+
def get_changes(old: dict[str, str], new: dict[str, str]) -> Change:
102+
"""Get the changes between two dictionaries.
103+
104+
Args:
105+
old: The old dictionary of packages.
106+
new: The new dictionary of packages.
107+
108+
Returns:
109+
A `Change` named tuple containing the added and removed packages.
110+
"""
111+
old_keys = set(old.keys())
112+
new_keys = set(new.keys())
113+
added = new_keys - old_keys
114+
removed = old_keys - new_keys
115+
return Change(added=added, removed=removed)
116+
117+
dependencies_change = get_changes(
118+
old_package_json_content.get("dependencies", {}),
119+
new_package_json_content.get("dependencies", {}),
120+
)
121+
dev_dependencies_change = get_changes(
122+
old_package_json_content.get("devDependencies", {}),
123+
new_package_json_content.get("devDependencies", {}),
124+
)
125+
126+
return dependencies_change, dev_dependencies_change
50127

51128

52129
def kill(proc_pid: int):
@@ -86,7 +163,7 @@ def run_process_and_launch_url(
86163
from reflex.utils import processes
87164

88165
json_file_path = get_web_dir() / constants.PackageJson.PATH
89-
last_hash = detect_package_change(json_file_path)
166+
last_content, last_hash = get_package_json_and_hash(json_file_path)
90167
process = None
91168
first_run = True
92169

@@ -105,6 +182,18 @@ def run_process_and_launch_url(
105182
frontend_process = process
106183
if process.stdout:
107184
for line in processes.stream_logs("Starting frontend", process):
185+
new_content, new_hash = get_package_json_and_hash(json_file_path)
186+
if new_hash != last_hash:
187+
dependencies_change, dev_dependencies_change = (
188+
get_different_packages(last_content, new_content)
189+
)
190+
last_content, last_hash = new_content, new_hash
191+
console.info(
192+
"Detected changes in package.json.\n"
193+
+ format_change("Dependencies", dependencies_change)
194+
+ format_change("Dev Dependencies", dev_dependencies_change)
195+
)
196+
108197
match = re.search(constants.Next.FRONTEND_LISTENING_REGEX, line)
109198
if match:
110199
if first_run:
@@ -119,22 +208,8 @@ def run_process_and_launch_url(
119208
notify_backend()
120209
first_run = False
121210
else:
122-
console.print("New packages detected: Updating app...")
123-
else:
124-
if any(
125-
x in line for x in ("bin executable does not exist on disk",)
126-
):
127-
console.error(
128-
"Try setting `REFLEX_USE_NPM=1` and re-running `reflex init` and `reflex run` to use npm instead of bun:\n"
129-
"`REFLEX_USE_NPM=1 reflex init`\n"
130-
"`REFLEX_USE_NPM=1 reflex run`"
131-
)
132-
new_hash = detect_package_change(json_file_path)
133-
if new_hash != last_hash:
134-
last_hash = new_hash
135-
kill(process.pid)
136-
process = None
137-
break # for line in process.stdout
211+
console.print("Frontend is restarting...")
212+
138213
if process is not None:
139214
break # while True
140215

0 commit comments

Comments
 (0)