Bun version validation (#1002)
This commit is contained in:
parent
5b3d0a51e8
commit
7e8a4930ba
@ -18,7 +18,7 @@ VERSION = pkg_resources.get_distribution(PACKAGE_NAME).version
|
|||||||
MIN_NODE_VERSION = "16.6.0"
|
MIN_NODE_VERSION = "16.6.0"
|
||||||
|
|
||||||
# Valid bun versions.
|
# Valid bun versions.
|
||||||
MIN_BUN_VERSION = "0.5.8"
|
MIN_BUN_VERSION = "0.5.9"
|
||||||
MAX_BUN_VERSION = "0.5.9"
|
MAX_BUN_VERSION = "0.5.9"
|
||||||
INVALID_BUN_VERSIONS = ["0.5.5", "0.5.6", "0.5.7"]
|
INVALID_BUN_VERSIONS = ["0.5.5", "0.5.6", "0.5.7"]
|
||||||
|
|
||||||
@ -70,8 +70,10 @@ FRONTEND_PORT = "3000"
|
|||||||
BACKEND_PORT = "8000"
|
BACKEND_PORT = "8000"
|
||||||
# The backend api url.
|
# The backend api url.
|
||||||
API_URL = "http://localhost:8000"
|
API_URL = "http://localhost:8000"
|
||||||
|
# bun root location
|
||||||
|
BUN_ROOT_PATH = "$HOME/.bun"
|
||||||
# The default path where bun is installed.
|
# 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.
|
# Command to install bun.
|
||||||
INSTALL_BUN = f"curl -fsSL https://bun.sh/install | bash -s -- bun-v{MAX_BUN_VERSION}"
|
INSTALL_BUN = f"curl -fsSL https://bun.sh/install | bash -s -- bun-v{MAX_BUN_VERSION}"
|
||||||
# Default host in dev mode.
|
# Default host in dev mode.
|
||||||
|
@ -38,25 +38,25 @@ def init(
|
|||||||
)
|
)
|
||||||
raise typer.Exit()
|
raise typer.Exit()
|
||||||
|
|
||||||
with console.status(f"[bold]Initializing {app_name}"):
|
console.rule(f"[bold]Initializing {app_name}")
|
||||||
# Set up the web directory.
|
# Set up the web directory.
|
||||||
prerequisites.install_bun()
|
prerequisites.validate_and_install_bun()
|
||||||
prerequisites.initialize_web_directory()
|
prerequisites.initialize_web_directory()
|
||||||
|
|
||||||
# Set up the app directory, only if the config doesn't exist.
|
# Set up the app directory, only if the config doesn't exist.
|
||||||
if not os.path.exists(constants.CONFIG_FILE):
|
if not os.path.exists(constants.CONFIG_FILE):
|
||||||
prerequisites.create_config(app_name)
|
prerequisites.create_config(app_name)
|
||||||
prerequisites.initialize_app_directory(app_name, template)
|
prerequisites.initialize_app_directory(app_name, template)
|
||||||
build.set_pynecone_project_hash()
|
build.set_pynecone_project_hash()
|
||||||
telemetry.send("init", get_config().telemetry_enabled)
|
telemetry.send("init", get_config().telemetry_enabled)
|
||||||
else:
|
else:
|
||||||
build.set_pynecone_project_hash()
|
build.set_pynecone_project_hash()
|
||||||
telemetry.send("reinit", get_config().telemetry_enabled)
|
telemetry.send("reinit", get_config().telemetry_enabled)
|
||||||
|
|
||||||
# Initialize the .gitignore.
|
# Initialize the .gitignore.
|
||||||
prerequisites.initialize_gitignore()
|
prerequisites.initialize_gitignore()
|
||||||
# Finish initializing the app.
|
# Finish initializing the app.
|
||||||
console.log(f"[bold green]Finished Initializing: {app_name}")
|
console.log(f"[bold green]Finished Initializing: {app_name}")
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
|
@ -48,18 +48,21 @@ def rule(title: str) -> None:
|
|||||||
_console.rule(title)
|
_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
|
"""Takes a prompt question and optionally a list of choices
|
||||||
and returns the user input.
|
and returns the user input.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
question (str): The question to ask the user.
|
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:
|
Returns:
|
||||||
A string
|
A string
|
||||||
"""
|
"""
|
||||||
return Prompt.ask(question, choices=choices)
|
return Prompt.ask(question, choices=choices, default=default) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def status(msg: str) -> Status:
|
def status(msg: str) -> Status:
|
||||||
|
@ -38,6 +38,9 @@ def run_frontend(app: App, root: Path, port: str):
|
|||||||
root: root path of the project.
|
root: root path of the project.
|
||||||
port: port of the app.
|
port: port of the app.
|
||||||
"""
|
"""
|
||||||
|
# validate bun version
|
||||||
|
prerequisites.validate_and_install_bun(initialize=False)
|
||||||
|
|
||||||
# Set up the frontend.
|
# Set up the frontend.
|
||||||
setup_frontend(root)
|
setup_frontend(root)
|
||||||
|
|
||||||
|
@ -52,7 +52,9 @@ def get_bun_version() -> Optional[version.Version]:
|
|||||||
try:
|
try:
|
||||||
# Run the bun -v command and capture the output
|
# Run the bun -v command and capture the output
|
||||||
result = subprocess.run(
|
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())
|
return version.parse(result.stdout.decode().strip())
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -213,12 +215,16 @@ def initialize_web_directory():
|
|||||||
json.dump(pynecone_json, f, ensure_ascii=False)
|
json.dump(pynecone_json, f, ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
def install_bun():
|
def validate_and_install_bun(initialize=True):
|
||||||
"""Install bun onto the user's system.
|
"""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:
|
Raises:
|
||||||
FileNotFoundError: If the required packages are not installed.
|
|
||||||
Exit: If the bun version is not supported.
|
Exit: If the bun version is not supported.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
bun_version = get_bun_version()
|
bun_version = get_bun_version()
|
||||||
if bun_version is not None and (
|
if bun_version is not None and (
|
||||||
@ -229,11 +235,37 @@ def install_bun():
|
|||||||
console.print(
|
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}."""
|
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(
|
action = console.ask(
|
||||||
f"""[red]Upgrade by running the following command:[/red]\n\n{constants.INSTALL_BUN}"""
|
"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.
|
# Bun is not supported on Windows.
|
||||||
if platform.system() == "Windows":
|
if platform.system() == "Windows":
|
||||||
console.log("Skipping bun installation on Windows.")
|
console.log("Skipping bun installation on Windows.")
|
||||||
|
@ -2,10 +2,16 @@ import typing
|
|||||||
from typing import Any, List, Union
|
from typing import Any, List, Union
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from packaging import version
|
||||||
|
|
||||||
from pynecone.utils import build, format, imports, prerequisites, types
|
from pynecone.utils import build, format, imports, prerequisites, types
|
||||||
from pynecone.vars import Var
|
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(
|
@pytest.mark.parametrize(
|
||||||
"input,output",
|
"input,output",
|
||||||
@ -231,6 +237,78 @@ def test_format_route(route: str, expected: bool):
|
|||||||
assert format.format_route(route) == expected
|
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):
|
def test_setup_frontend(tmp_path, mocker):
|
||||||
"""Test checking if assets content have been
|
"""Test checking if assets content have been
|
||||||
copied into the .web/public folder.
|
copied into the .web/public folder.
|
||||||
|
Loading…
Reference in New Issue
Block a user