11from __future__ import annotations
22
33import base64
4+ import contextlib
45import hashlib
56import json
67import os
@@ -37,6 +38,8 @@ def populate_store(manifest_path: str, tarball_dir: str, store_dir: str) -> None
3738 integrity_hex = info ['integrity_hex' ],
3839 store = store ,
3940 now = now ,
41+ tarball_url = info .get ('tarball_url' ),
42+ store_version = store_version ,
4043 )
4144
4245
@@ -48,8 +51,12 @@ def _process_tarball(
4851 integrity_hex : str ,
4952 store : str ,
5053 now : int ,
54+ tarball_url : str | None = None ,
55+ store_version : str = 'v3' ,
5156) -> None :
5257 index_files : dict [str , dict [str , object ]] = {}
58+ real_pkg_name = pkg_name
59+ real_pkg_version = pkg_version
5360
5461 with tarfile .open (tarball_path , 'r:gz' ) as tf :
5562 for member in tf .getmembers ():
@@ -60,6 +67,17 @@ def _process_tarball(
6067 continue
6168 data = fobj .read ()
6269
70+ if member .name .endswith ('package.json' ) and member .name .count ('/' ) <= 1 :
71+ with contextlib .suppress (ValueError , TypeError , UnicodeDecodeError ):
72+ pkg_data = json .loads (data .decode ('utf-8' ))
73+ if isinstance (pkg_data , dict ):
74+ if 'name' in pkg_data and isinstance (pkg_data ['name' ], str ):
75+ real_pkg_name = pkg_data ['name' ]
76+ if 'version' in pkg_data and isinstance (
77+ pkg_data ['version' ], str
78+ ):
79+ real_pkg_version = pkg_data ['version' ]
80+
6381 digest = hashlib .sha512 (data ).digest ()
6482 file_hex = digest .hex ()
6583 is_exec = bool (member .mode & 0o111 )
@@ -86,20 +104,42 @@ def _process_tarball(
86104 'size' : len (data ),
87105 }
88106
107+ index_data = {
108+ 'name' : real_pkg_name ,
109+ 'version' : real_pkg_version ,
110+ 'requiresBuild' : False ,
111+ 'files' : index_files ,
112+ }
113+
89114 idx_prefix = integrity_hex [:2 ]
90115 idx_rest = integrity_hex [2 :64 ]
91116 pkg_id = _SANITIZE_RE .sub ('+' , f'{ pkg_name } @{ pkg_version } ' )
92117 idx_dir = os .path .join (store , 'index' , idx_prefix )
93118 os .makedirs (idx_dir , exist_ok = True )
94119 idx_path = os .path .join (idx_dir , f'{ idx_rest } -{ pkg_id } .json' )
95- index_data = {
96- 'name' : pkg_name ,
97- 'version' : pkg_version ,
98- 'files' : index_files ,
99- }
100120 with open (idx_path , 'w' , encoding = 'utf-8' ) as out :
101121 json .dump (index_data , out )
102122
123+ # For tarball-URL packages, also create an index entry keyed by the URL hash
124+ # this is how pnpm looks up tarball deps without integrity
125+ if tarball_url :
126+ if store_version == 'v3' :
127+ url_hash = hashlib .sha256 (tarball_url .encode ()).hexdigest ()
128+ url_idx_prefix = url_hash [:2 ]
129+ url_idx_rest = url_hash [2 :64 ]
130+ url_idx_dir = os .path .join (store , 'index' , url_idx_prefix )
131+ os .makedirs (url_idx_dir , exist_ok = True )
132+ url_idx_path = os .path .join (url_idx_dir , f'{ url_idx_rest } -{ pkg_id } .json' )
133+ with open (url_idx_path , 'w' , encoding = 'utf-8' ) as out :
134+ json .dump (index_data , out )
135+ else :
136+ url_dir_name = re .sub (r'[:/]' , '+' , tarball_url )
137+ url_idx_dir = os .path .join (store , url_dir_name )
138+ os .makedirs (url_idx_dir , exist_ok = True )
139+ url_idx_path = os .path .join (url_idx_dir , 'integrity.json' )
140+ with open (url_idx_path , 'w' , encoding = 'utf-8' ) as out :
141+ json .dump (index_data , out )
142+
103143
104144if __name__ == '__main__' :
105145 if len (sys .argv ) != 4 :
0 commit comments