diff --git a/README.md b/README.md index af811b1..e7efbee 100644 --- a/README.md +++ b/README.md @@ -322,16 +322,16 @@ def main(context: dict[str, str]) -> int: cfg["node"][f"{constants.SYSTEM_MACHINE}_sha256"], reporoot, ) - node.install_yarn(cfg["node"]["yarn_version"], reporoot) + + node.install_pnpm(reporoot) print("installing node dependencies...") proc.run( ( - ".devenv/bin/yarn", + f"{reporoot}/.devenv/bin/pnpm", "install", "--frozen-lockfile", - "--no-progress", - "--non-interactive", + "--reporter=append-only", ), ) @@ -353,7 +353,6 @@ linux_x86_64 = https://storage.googleapis.com/sentry-dev-infra-assets/node/node- linux_x86_64_sha256 = efc0f295dd878e510ab12ea36bbadc3db03c687ab30c07e86c7cdba7eed879a9 # used for autoupdate version = v20.13.1 -yarn_version = 1.22.22 ``` ### brew diff --git a/devenv/lib/node.py b/devenv/lib/node.py index b4abab9..c3c02d0 100644 --- a/devenv/lib/node.py +++ b/devenv/lib/node.py @@ -1,5 +1,6 @@ from __future__ import annotations +import json import os import shutil import tempfile @@ -9,7 +10,7 @@ from devenv.lib import proc -_shims = ("node", "npm", "npx", "yarn", "pnpm") +_shims = ("node", "npm", "npx") def _install(url: str, sha256: str, into: str) -> None: @@ -32,7 +33,7 @@ def _install(url: str, sha256: str, into: str) -> None: def uninstall(binroot: str) -> None: shutil.rmtree(f"{binroot}/node-env", ignore_errors=True) - for shim in (*_shims, "yarn"): + for shim in (*_shims, "pnpm"): fp = f"{binroot}/{shim}" try: os.remove(fp) @@ -71,6 +72,51 @@ def installed(version: str, binroot: str) -> bool: return False +def installed_pnpm(version: str, binroot: str) -> bool: + if shutil.which( + "pnpm", path=binroot + ) != f"{binroot}/pnpm" or not os.path.exists( + f"{binroot}/node-env/bin/pnpm" + ): + return False + + stdout = proc.run((f"{binroot}/pnpm", "--version"), stdout=True) + installed_version = stdout.strip() + return version == installed_version + + +def install_pnpm(reporoot: str) -> None: + binroot = fs.ensure_binroot(reporoot) + + with open(f"{reporoot}/package.json") as f: + package_json = json.load(f) + pnpm = package_json["packageManager"] + pnpm_version = pnpm.split("@")[-1] + + if installed_pnpm(pnpm_version, binroot): + return + + print(f"installing pnpm {pnpm_version}...") + + # {binroot}/npm is a devenv-managed shim, so + # this install -g ends up putting pnpm into + # .devenv/bin/node-env/bin/pnpm which is pointed + # to by the {binroot}/pnpm shim + proc.run( + (f"{binroot}/npm", "install", "-g", f"pnpm@{pnpm_version}"), stdout=True + ) + + fs.write_script( + f"{binroot}/pnpm", + """#!/bin/sh +export PATH={binroot}/node-env/bin:"${{PATH}}" +export NPM_CONFIG_PREFIX={binroot}/node-env +exec {binroot}/node-env/bin/pnpm "$@" +""", + shell_escape={"binroot": binroot}, + ) + + def install(version: str, url: str, sha256: str, reporoot: str) -> None: binroot = fs.ensure_binroot(reporoot) @@ -81,9 +127,8 @@ def install(version: str, url: str, sha256: str, reporoot: str) -> None: uninstall(binroot) _install(url, sha256, binroot) - # NPM_CONFIG_PREFIX is needed to ensure npm install -g yarn - # puts yarn into our node-env. - + # NPM_CONFIG_PREFIX is needed to ensure npm install -g pnpm + # puts pnpm into our node-env. for shim in _shims: fs.write_script( f"{binroot}/{shim}", @@ -97,51 +142,3 @@ def install(version: str, url: str, sha256: str, reporoot: str) -> None: if not installed(version, binroot): raise SystemExit(f"failed to install node {version}!") - - -def installed_yarn(version: str, binroot: str) -> bool: - if shutil.which( - "yarn", path=binroot - ) != f"{binroot}/yarn" or not os.path.exists( - f"{binroot}/node-env/bin/yarn" - ): - return False - - with open(f"{binroot}/yarn", "r") as f: - sample = f.read(64) - if "VOLTA_HOME" in sample: - print("volta-based yarn detected") - return False - - stdout = proc.run((f"{binroot}/yarn", "--version"), stdout=True) - installed_version = stdout.strip() - if version == installed_version: - return True - - print(f"installed yarn {installed_version} is unexpected!") - return False - - -def install_yarn(version: str, reporoot: str) -> None: - binroot = fs.ensure_binroot(reporoot) - - if installed_yarn(version, binroot): - return - - # not explicitly uninstalling here, following steps - # will pave over it - print(f"installing yarn {version}...") - - proc.run((f"{binroot}/npm", "install", "-g", f"yarn@{version}")) - - fs.write_script( - f"{binroot}/yarn", - """#!/bin/sh -export PATH={binroot}/node-env/bin:"${{PATH}}" -exec {binroot}/node-env/bin/yarn "$@" -""", - shell_escape={"binroot": binroot}, - ) - - if not installed_yarn(version, binroot): - raise SystemExit(f"failed to install yarn {version}!") diff --git a/tests/lib/test_node.py b/tests/lib/test_node.py new file mode 100644 index 0000000..be7fe6a --- /dev/null +++ b/tests/lib/test_node.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +import os +import pathlib +from unittest.mock import patch + +from devenv.lib import node +from devenv.lib.repository import Repository + + +def test_install_pnpm(tmp_path: pathlib.Path) -> None: + repo = Repository(f"{tmp_path}/test") + + binroot = f"{repo.path}/.devenv/bin" + os.makedirs(binroot) + open(f"{binroot}/node", "w").close() + + with patch("devenv.lib.archive.download"), patch( + "devenv.lib.archive.unpack", + side_effect=lambda archive_file, tmpd, perform_strip1, strip1_new_prefix: os.makedirs( + f"{tmpd}/{strip1_new_prefix}" + ), + ), patch("devenv.lib.node.os.path.exists"), patch( + "devenv.lib.direnv.proc.run", side_effect=["0.0.0"] # node --version + ): + node.install("0.0.0", "bar", "baz", repo.path) + + with open(f"{binroot}/npm", "r") as f: + text = f.read() + assert ( + text + == f"""#!/bin/sh +export PATH={binroot}/node-env/bin:"${{PATH}}" +export NPM_CONFIG_PREFIX={binroot}/node-env +exec {binroot}/node-env/bin/npm "$@" +""" + )