Skip to content

Commit dd396a6

Browse files
nukeoptonur
authored andcommitted
fix(node): pnpm - support v10 offline tarballs
1 parent 37eb3e5 commit dd396a6

5 files changed

Lines changed: 1063 additions & 1075 deletions

File tree

node/flatpak_node_generator/populate_pnpm_store.py

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import base64
4+
import contextlib
45
import hashlib
56
import json
67
import 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

104144
if __name__ == '__main__':
105145
if len(sys.argv) != 4:

node/flatpak_node_generator/providers/pnpm.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -189,27 +189,27 @@ async def generate_package(self, package: Package) -> None:
189189
if isinstance(source, ResolvedSource):
190190
assert source.resolved is not None
191191

192-
if source.integrity is None:
192+
integrity = source.integrity
193+
if integrity is None:
193194
print(
194-
f'WARNING: skipping {package.name}@{package.version}: '
195-
'no integrity in lockfile (required for pnpm store)',
195+
f'INFO: {package.name}@{package.version}: '
196+
'no integrity in lockfile, fetching to compute...',
196197
file=sys.stderr,
197198
)
198-
return
199+
integrity = await source.retrieve_integrity()
199200

200-
# Use name-version as filename; replace / in scoped names
201201
tarball_name = f'{package.name.replace("/", "__")}-{package.version}.tgz'
202202
self.gen.add_url_source(
203203
url=source.resolved,
204-
integrity=source.integrity,
204+
integrity=integrity,
205205
destination=self.tarball_dir / tarball_name,
206206
)
207207
self._tarballs.append(
208208
self._TarballInfo(
209209
tarball_name=tarball_name,
210210
name=package.name,
211211
version=package.version,
212-
integrity=source.integrity,
212+
integrity=integrity,
213213
)
214214
)
215215

@@ -236,11 +236,14 @@ def _finalize(self) -> None:
236236
def _add_store_population_script(self) -> None:
237237
packages = {}
238238
for info in self._tarballs:
239-
packages[info.tarball_name] = {
239+
entry: dict[str, str] = {
240240
'name': info.name,
241241
'version': info.version,
242242
'integrity_hex': info.integrity.digest,
243243
}
244+
if info.version.startswith(('http://', 'https://')):
245+
entry['tarball_url'] = info.version
246+
packages[info.tarball_name] = entry
244247

245248
manifest = {
246249
'store_version': self._store_version,

0 commit comments

Comments
 (0)