Skip to content

Commit 0d72e61

Browse files
authored
fix cert errors for package install (#5050)
1 parent d13ad9e commit 0d72e61

File tree

4 files changed

+96
-22
lines changed

4 files changed

+96
-22
lines changed

reflex/constants/installer.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ class Node(SimpleNamespace):
6767
# The minimum required node version.
6868
MIN_VERSION = "18.18.0"
6969

70+
# Path of the node config file.
71+
CONFIG_PATH = ".npmrc"
72+
73+
DEFAULT_CONFIG = """
74+
registry={registry}
75+
fetch-retries=0
76+
"""
77+
7078

7179
def _determine_nextjs_version() -> str:
7280
default_version = "15.2.4"

reflex/utils/prerequisites.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,9 @@ def compile_or_validate_app(compile: bool = False) -> bool:
510510
else:
511511
validate_app()
512512
except Exception as e:
513+
if isinstance(e, typer.Exit):
514+
return False
515+
513516
import traceback
514517

515518
sys_exception = sys.exception()
@@ -963,6 +966,9 @@ def initialize_web_directory():
963966
console.debug("Initializing the bun config file.")
964967
initialize_bun_config()
965968

969+
console.debug("Initializing the .npmrc file.")
970+
initialize_npmrc()
971+
966972
console.debug("Initializing the public directory.")
967973
path_ops.mkdir(get_web_dir() / constants.Dirs.PUBLIC)
968974

@@ -1012,6 +1018,20 @@ def initialize_bun_config():
10121018
bun_config_path.write_text(bunfig_content)
10131019

10141020

1021+
def initialize_npmrc():
1022+
"""Initialize the .npmrc file."""
1023+
npmrc_path = get_web_dir() / constants.Node.CONFIG_PATH
1024+
1025+
if (custom_npmrc := Path(constants.Node.CONFIG_PATH)).exists():
1026+
npmrc_content = custom_npmrc.read_text()
1027+
console.info(f"Copying custom .npmrc inside {get_web_dir()} folder")
1028+
else:
1029+
best_registry = get_npm_registry()
1030+
npmrc_content = constants.Node.DEFAULT_CONFIG.format(registry=best_registry)
1031+
1032+
npmrc_path.write_text(npmrc_content)
1033+
1034+
10151035
def init_reflex_json(project_hash: int | None):
10161036
"""Write the hash of the Reflex project to a REFLEX_JSON.
10171037
@@ -1241,6 +1261,14 @@ def install_frontend_packages(packages: set[str], config: Config):
12411261
raise_on_none=True
12421262
)
12431263

1264+
env = (
1265+
{
1266+
"NODE_TLS_REJECT_UNAUTHORIZED": "0",
1267+
}
1268+
if environment.SSL_NO_VERIFY.get()
1269+
else {}
1270+
)
1271+
12441272
primary_package_manager = install_package_managers[0]
12451273
fallbacks = install_package_managers[1:]
12461274

@@ -1251,6 +1279,7 @@ def install_frontend_packages(packages: set[str], config: Config):
12511279
show_status_message="Installing base frontend packages",
12521280
cwd=get_web_dir(),
12531281
shell=constants.IS_WINDOWS,
1282+
env=env,
12541283
)
12551284

12561285
if config.tailwind is not None:
@@ -1268,6 +1297,7 @@ def install_frontend_packages(packages: set[str], config: Config):
12681297
show_status_message="Installing tailwind",
12691298
cwd=get_web_dir(),
12701299
shell=constants.IS_WINDOWS,
1300+
env=env,
12711301
)
12721302

12731303
# Install custom packages defined in frontend_packages
@@ -1279,6 +1309,7 @@ def install_frontend_packages(packages: set[str], config: Config):
12791309
show_status_message="Installing frontend packages from config and components",
12801310
cwd=get_web_dir(),
12811311
shell=constants.IS_WINDOWS,
1312+
env=env,
12821313
)
12831314

12841315

reflex/utils/processes.py

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from reflex import constants
2121
from reflex.config import environment
2222
from reflex.utils import console, path_ops, prerequisites
23+
from reflex.utils.registry import get_npm_registry
2324

2425

2526
def kill(pid: int):
@@ -276,6 +277,7 @@ def stream_logs(
276277
progress: Progress | None = None,
277278
suppress_errors: bool = False,
278279
analytics_enabled: bool = False,
280+
prior_logs: Tuple[tuple[str, ...], ...] = (),
279281
):
280282
"""Stream the logs for a process.
281283
@@ -285,6 +287,7 @@ def stream_logs(
285287
progress: The ongoing progress bar if one is being used.
286288
suppress_errors: If True, do not exit if errors are encountered (for fallback).
287289
analytics_enabled: Whether analytics are enabled for this command.
290+
prior_logs: The logs of the prior processes that have been run.
288291
289292
Yields:
290293
The lines of the process output.
@@ -312,8 +315,31 @@ def stream_logs(
312315
accepted_return_codes = [0, -2, 15] if constants.IS_WINDOWS else [0, -2]
313316
if process.returncode not in accepted_return_codes and not suppress_errors:
314317
console.error(f"{message} failed with exit code {process.returncode}")
315-
for line in logs:
316-
console.error(line, end="")
318+
if "".join(logs).count("CERT_HAS_EXPIRED") > 0:
319+
bunfig = prerequisites.get_web_dir() / constants.Bun.CONFIG_PATH
320+
npm_registry_line = next(
321+
(
322+
line
323+
for line in bunfig.read_text().splitlines()
324+
if line.startswith("registry")
325+
),
326+
None,
327+
)
328+
if not npm_registry_line or "=" not in npm_registry_line:
329+
npm_registry = get_npm_registry()
330+
else:
331+
npm_registry = npm_registry_line.split("=")[1].strip()
332+
console.error(
333+
f"Failed to fetch securely from [bold]{npm_registry}[/bold]. Please check your network connection. "
334+
"You can try running the command again or changing the registry by setting the "
335+
"NPM_CONFIG_REGISTRY environment variable. If TLS is the issue, and you know what "
336+
"you are doing, you can disable it by setting the SSL_NO_VERIFY environment variable."
337+
)
338+
raise typer.Exit(1)
339+
for set_of_logs in (*prior_logs, tuple(logs)):
340+
for line in set_of_logs:
341+
console.error(line, end="")
342+
console.error("\n\n")
317343
if analytics_enabled:
318344
telemetry.send("error", context=message)
319345
console.error("Run with [bold]--loglevel debug [/bold] for the full log.")
@@ -336,26 +362,33 @@ def show_status(
336362
process: subprocess.Popen,
337363
suppress_errors: bool = False,
338364
analytics_enabled: bool = False,
339-
prior_processes: Tuple[subprocess.Popen, ...] = (),
340-
):
365+
prior_logs: Tuple[tuple[str, ...], ...] = (),
366+
) -> list[str]:
341367
"""Show the status of a process.
342368
343369
Args:
344370
message: The initial message to display.
345371
process: The process.
346372
suppress_errors: If True, do not exit if errors are encountered (for fallback).
347373
analytics_enabled: Whether analytics are enabled for this command.
348-
prior_processes: The prior processes that have been run.
374+
prior_logs: The logs of the prior processes that have been run.
375+
376+
Returns:
377+
The lines of the process output.
349378
"""
350-
for one_process in (*prior_processes, process):
351-
with console.status(message) as status:
352-
for line in stream_logs(
353-
message,
354-
one_process,
355-
suppress_errors=suppress_errors,
356-
analytics_enabled=analytics_enabled,
357-
):
358-
status.update(f"{message} {line}")
379+
lines = []
380+
381+
with console.status(message) as status:
382+
for line in stream_logs(
383+
message,
384+
process,
385+
suppress_errors=suppress_errors,
386+
analytics_enabled=analytics_enabled,
387+
prior_logs=prior_logs,
388+
):
389+
status.update(f"{message} {line}")
390+
lines.append(line)
391+
return lines
359392

360393

361394
def show_progress(message: str, process: subprocess.Popen, checkpoints: list[str]):
@@ -409,7 +442,7 @@ def run_process_with_fallbacks(
409442
show_status_message: str,
410443
fallbacks: str | Sequence[str] | Sequence[Sequence[str]] | None = None,
411444
analytics_enabled: bool = False,
412-
prior_processes: Tuple[subprocess.Popen, ...] = (),
445+
prior_logs: Tuple[tuple[str, ...], ...] = (),
413446
**kwargs,
414447
):
415448
"""Run subprocess and retry using fallback command if initial command fails.
@@ -419,7 +452,7 @@ def run_process_with_fallbacks(
419452
show_status_message: The status message to be displayed in the console.
420453
fallbacks: The fallback command to run if the initial command fails.
421454
analytics_enabled: Whether analytics are enabled for this command.
422-
prior_processes: The prior processes that have been run.
455+
prior_logs: The logs of the prior processes that have been run.
423456
**kwargs: Kwargs to pass to new_process function.
424457
"""
425458
process = new_process(get_command_with_loglevel(args), **kwargs)
@@ -429,11 +462,11 @@ def run_process_with_fallbacks(
429462
show_status_message,
430463
process,
431464
analytics_enabled=analytics_enabled,
432-
prior_processes=prior_processes,
465+
prior_logs=prior_logs,
433466
)
434467
else:
435468
# Suppress errors for initial command, because we will try to fallback
436-
show_status(show_status_message, process, suppress_errors=True)
469+
logs = show_status(show_status_message, process, suppress_errors=True)
437470

438471
current_fallback = fallbacks[0] if not isinstance(fallbacks, str) else fallbacks
439472
next_fallbacks = fallbacks[1:] if not isinstance(fallbacks, str) else None
@@ -453,7 +486,7 @@ def run_process_with_fallbacks(
453486
show_status_message=show_status_message,
454487
fallbacks=next_fallbacks,
455488
analytics_enabled=analytics_enabled,
456-
prior_processes=(*prior_processes, process),
489+
prior_logs=(*prior_logs, tuple(logs)),
457490
**kwargs,
458491
)
459492

reflex/utils/registry.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from reflex.config import environment
66
from reflex.utils import console, net
7+
from reflex.utils.decorator import once
78

89

910
def latency(registry: str) -> int:
@@ -48,15 +49,16 @@ def _get_best_registry() -> str:
4849
"""
4950
console.debug("Getting best registry...")
5051
registries = [
51-
"https://registry.npmjs.org",
52-
"https://r.cnpmjs.org",
52+
("https://registry.npmjs.org", 1),
53+
("https://registry.npmmirror.com", 2),
5354
]
5455

55-
best_registry = min(registries, key=average_latency)
56+
best_registry = min(registries, key=lambda x: average_latency(x[0]) * x[1])[0]
5657
console.debug(f"Best registry: {best_registry}")
5758
return best_registry
5859

5960

61+
@once
6062
def get_npm_registry() -> str:
6163
"""Get npm registry. If environment variable is set, use it first.
6264

0 commit comments

Comments
 (0)