Bun version validation (#1002)

This commit is contained in:
Elijah Ahianyo 2023-05-12 23:43:11 +00:00 committed by GitHub
parent 5b3d0a51e8
commit 7e8a4930ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 147 additions and 29 deletions

View File

@ -18,7 +18,7 @@ VERSION = pkg_resources.get_distribution(PACKAGE_NAME).version
MIN_NODE_VERSION = "16.6.0"
# Valid bun versions.
MIN_BUN_VERSION = "0.5.8"
MIN_BUN_VERSION = "0.5.9"
MAX_BUN_VERSION = "0.5.9"
INVALID_BUN_VERSIONS = ["0.5.5", "0.5.6", "0.5.7"]
@ -70,8 +70,10 @@ FRONTEND_PORT = "3000"
BACKEND_PORT = "8000"
# The backend api url.
API_URL = "http://localhost:8000"
# bun root location
BUN_ROOT_PATH = "$HOME/.bun"
# The default path where bun is installed.
BUN_PATH = "$HOME/.bun/bin/bun"
BUN_PATH = f"{BUN_ROOT_PATH}/bin/bun"
# Command to install bun.
INSTALL_BUN = f"curl -fsSL https://bun.sh/install | bash -s -- bun-v{MAX_BUN_VERSION}"
# Default host in dev mode.

View File

@ -38,25 +38,25 @@ def init(
)
raise typer.Exit()
with console.status(f"[bold]Initializing {app_name}"):
# Set up the web directory.
prerequisites.install_bun()
prerequisites.initialize_web_directory()
console.rule(f"[bold]Initializing {app_name}")
# Set up the web directory.
prerequisites.validate_and_install_bun()
prerequisites.initialize_web_directory()
# Set up the app directory, only if the config doesn't exist.
if not os.path.exists(constants.CONFIG_FILE):
prerequisites.create_config(app_name)
prerequisites.initialize_app_directory(app_name, template)
build.set_pynecone_project_hash()
telemetry.send("init", get_config().telemetry_enabled)
else:
build.set_pynecone_project_hash()
telemetry.send("reinit", get_config().telemetry_enabled)
# Set up the app directory, only if the config doesn't exist.
if not os.path.exists(constants.CONFIG_FILE):
prerequisites.create_config(app_name)
prerequisites.initialize_app_directory(app_name, template)
build.set_pynecone_project_hash()
telemetry.send("init", get_config().telemetry_enabled)
else:
build.set_pynecone_project_hash()
telemetry.send("reinit", get_config().telemetry_enabled)
# Initialize the .gitignore.
prerequisites.initialize_gitignore()
# Finish initializing the app.
console.log(f"[bold green]Finished Initializing: {app_name}")
# Initialize the .gitignore.
prerequisites.initialize_gitignore()
# Finish initializing the app.
console.log(f"[bold green]Finished Initializing: {app_name}")
@cli.command()

View File

@ -48,18 +48,21 @@ def rule(title: str) -> None:
_console.rule(title)
def ask(question: str, choices: Optional[List[str]] = None) -> str:
def ask(
question: str, choices: Optional[List[str]] = None, default: Optional[str] = None
) -> str:
"""Takes a prompt question and optionally a list of choices
and returns the user input.
Args:
question (str): The question to ask the user.
choices (Optional[List[str]]): A list of choices to select from
choices (Optional[List[str]]): A list of choices to select from.
default(Optional[str]): The default option selected.
Returns:
A string
"""
return Prompt.ask(question, choices=choices)
return Prompt.ask(question, choices=choices, default=default) # type: ignore
def status(msg: str) -> Status:

View File

@ -38,6 +38,9 @@ def run_frontend(app: App, root: Path, port: str):
root: root path of the project.
port: port of the app.
"""
# validate bun version
prerequisites.validate_and_install_bun(initialize=False)
# Set up the frontend.
setup_frontend(root)

View File

@ -52,7 +52,9 @@ def get_bun_version() -> Optional[version.Version]:
try:
# Run the bun -v command and capture the output
result = subprocess.run(
["bun", "-v"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
[os.path.expandvars(get_config().bun_path), "-v"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
return version.parse(result.stdout.decode().strip())
except Exception:
@ -213,12 +215,16 @@ def initialize_web_directory():
json.dump(pynecone_json, f, ensure_ascii=False)
def install_bun():
"""Install bun onto the user's system.
def validate_and_install_bun(initialize=True):
"""Check that bun version requirements are met. If they are not,
ask user whether to install required version.
Args:
initialize: whether this function is called on `pc init` or `pc run`.
Raises:
FileNotFoundError: If the required packages are not installed.
Exit: If the bun version is not supported.
"""
bun_version = get_bun_version()
if bun_version is not None and (
@ -229,11 +235,37 @@ def install_bun():
console.print(
f"""[red]Bun version {bun_version} is not supported by Pynecone. Please change your to bun version to be between {constants.MIN_BUN_VERSION} and {constants.MAX_BUN_VERSION}."""
)
console.print(
f"""[red]Upgrade by running the following command:[/red]\n\n{constants.INSTALL_BUN}"""
action = console.ask(
"Enter 'yes' to install the latest supported bun version or 'no' to exit.",
choices=["yes", "no"],
default="no",
)
raise typer.Exit()
if action == "yes":
remove_existing_bun_installation()
install_bun()
return
else:
raise typer.Exit()
if initialize:
install_bun()
def remove_existing_bun_installation():
"""Remove existing bun installation."""
package_manager = get_package_manager()
if os.path.exists(package_manager):
console.log("Removing bun...")
path_ops.rm(os.path.expandvars(constants.BUN_ROOT_PATH))
def install_bun():
"""Install bun onto the user's system.
Raises:
FileNotFoundError: if unzip or curl packages are not found.
"""
# Bun is not supported on Windows.
if platform.system() == "Windows":
console.log("Skipping bun installation on Windows.")

View File

@ -2,10 +2,16 @@ import typing
from typing import Any, List, Union
import pytest
from packaging import version
from pynecone.utils import build, format, imports, prerequisites, types
from pynecone.vars import Var
V055 = version.parse("0.5.5")
V059 = version.parse("0.5.9")
V056 = version.parse("0.5.6")
V0510 = version.parse("0.5.10")
@pytest.mark.parametrize(
"input,output",
@ -231,6 +237,78 @@ def test_format_route(route: str, expected: bool):
assert format.format_route(route) == expected
@pytest.mark.parametrize(
"bun_version,is_valid, prompt_input",
[
(V055, False, "yes"),
(V059, True, None),
(V0510, False, "yes"),
],
)
def test_bun_validate_and_install(mocker, bun_version, is_valid, prompt_input):
"""Test that the bun version on host system is validated properly. Also test that
the required bun version is installed should the user opt for it.
Args:
mocker: Pytest mocker object.
bun_version: The bun version.
is_valid: Whether bun version is valid for running pynecone.
prompt_input: The input from user on whether to install bun.
"""
mocker.patch(
"pynecone.utils.prerequisites.get_bun_version", return_value=bun_version
)
mocker.patch("pynecone.utils.prerequisites.console.ask", return_value=prompt_input)
bun_install = mocker.patch("pynecone.utils.prerequisites.install_bun")
remove_existing_bun_installation = mocker.patch(
"pynecone.utils.prerequisites.remove_existing_bun_installation"
)
prerequisites.validate_and_install_bun()
if not is_valid:
remove_existing_bun_installation.assert_called_once()
bun_install.assert_called_once()
def test_bun_validation_exception(mocker):
"""Test that an exception is thrown and program exists when user selects no when asked
whether to install bun or not.
Args:
mocker: Pytest mocker.
"""
mocker.patch("pynecone.utils.prerequisites.get_bun_version", return_value=V056)
mocker.patch("pynecone.utils.prerequisites.console.ask", return_value="no")
with pytest.raises(RuntimeError):
prerequisites.validate_and_install_bun()
def test_remove_existing_bun_installation(mocker, tmp_path):
"""Test that existing bun installation is removed.
Args:
mocker: Pytest mocker.
tmp_path: test path.
"""
bun_location = tmp_path / ".bun"
bun_location.mkdir()
mocker.patch(
"pynecone.utils.prerequisites.get_package_manager",
return_value=str(bun_location),
)
mocker.patch(
"pynecone.utils.prerequisites.os.path.expandvars",
return_value=str(bun_location),
)
prerequisites.remove_existing_bun_installation()
assert not bun_location.exists()
def test_setup_frontend(tmp_path, mocker):
"""Test checking if assets content have been
copied into the .web/public folder.