Skip to content

Commit b9b8507

Browse files
committed
feat(pmpm): write our own minimum copy of npm in Python to remove the requirement for Node.js npm
1 parent e790de3 commit b9b8507

File tree

3 files changed

+74
-16
lines changed

3 files changed

+74
-16
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ Read this if you want to build a local version.
6868
- rust
6969
- python3.8 or later with header files (python3-dev)
7070
- spidermonkey 102.2.0 or later
71-
- npm (nodejs)
7271
- [Poetry](https://python-poetry.org/docs/#installation)
7372
- [poetry-dynamic-versioning](https://github.com/mtkennerly/poetry-dynamic-versioning)
7473

python/pminit/pmpm.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# @file pmpm.py
2+
# A minimum copy of npm written in pure Python.
3+
# Currently, this can only install dependencies specified by package-lock.json into node_modules.
4+
# @author Tom Tang <[email protected]>
5+
# @date July 2023
6+
7+
import json
8+
import io
9+
import os, shutil
10+
import tempfile
11+
import tarfile
12+
from dataclasses import dataclass
13+
import urllib.request
14+
from typing import List, Union
15+
16+
WORK_DIR = os.path.join(
17+
os.path.realpath(os.path.dirname(__file__)),
18+
"pythonmonkey"
19+
)
20+
21+
@dataclass
22+
class PackageItem:
23+
installation_path: str
24+
tarball_url: str
25+
has_install_script: bool
26+
27+
def parse_package_lock_json(json_data: Union[str, bytes]) -> List[PackageItem]:
28+
# See https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json#packages
29+
packages: dict = json.loads(json_data)["packages"]
30+
items: List[PackageItem] = []
31+
for key, entry in packages.items():
32+
if key == "":
33+
# Skip the root project (listed with a key of "")
34+
continue
35+
items.append(
36+
PackageItem(
37+
installation_path=key, # relative path from the root project folder
38+
# The path is flattened for nested node_modules, e.g., "node_modules/create-ecdh/node_modules/bn.js"
39+
tarball_url=entry["resolved"], # TODO: handle git dependencies
40+
has_install_script=entry.get("hasInstallScript", False) # the package has a preinstall, install, or postinstall script
41+
)
42+
)
43+
return items
44+
45+
def download_package(tarball_url: str) -> bytes:
46+
with urllib.request.urlopen(tarball_url) as response:
47+
tarball_data: bytes = response.read()
48+
return tarball_data
49+
50+
def unpack_package(installation_path: str, tarball_data: bytes):
51+
installation_path = os.path.join(WORK_DIR, installation_path)
52+
shutil.rmtree(installation_path, ignore_errors=True)
53+
54+
with tempfile.TemporaryDirectory(prefix="pmpm_cache-") as tmpdir:
55+
with io.BytesIO(tarball_data) as tar_file:
56+
with tarfile.open(fileobj=tar_file) as tar:
57+
tar.extractall(tmpdir)
58+
shutil.move(
59+
os.path.join(tmpdir, "package"), # Strip the root folder
60+
installation_path
61+
)
62+
63+
def main():
64+
with open(os.path.join(WORK_DIR, "package-lock.json"), encoding="utf-8") as f:
65+
items = parse_package_lock_json(f.read())
66+
for i in items:
67+
print("Installing " + i.installation_path)
68+
tarball_data = download_package(i.tarball_url)
69+
unpack_package(i.installation_path, tarball_data)
70+
71+
if __name__ == "__main__":
72+
main()

python/pminit/post-install-hook.py

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,7 @@
1-
import subprocess
2-
import sys
3-
4-
def execute(cmd: str):
5-
popen = subprocess.Popen(cmd, stdout = subprocess.PIPE, stderr = subprocess.STDOUT,
6-
shell = True, text = True )
7-
for stdout_line in iter(popen.stdout.readline, ""):
8-
sys.stdout.write(stdout_line)
9-
sys.stdout.flush()
10-
11-
popen.stdout.close()
12-
return_code = popen.wait()
13-
if return_code:
14-
raise subprocess.CalledProcessError(return_code, cmd)
1+
import pmpm
152

163
def main():
17-
execute("cd pythonmonkey && npm i --no-package-lock") # do not update package-lock.json
4+
pmpm.main() # cd pythonmonkey && npm i
185

196
if __name__ == "__main__":
207
main()

0 commit comments

Comments
 (0)