Skip to content

Commit 21bb9e9

Browse files
committed
node: Add support for Git sources with lockfile v2 processing
Signed-off-by: Ryan Gonzalez <[email protected]>
1 parent 67455d2 commit 21bb9e9

File tree

3 files changed

+52
-27
lines changed

3 files changed

+52
-27
lines changed

node/flatpak_node_generator/providers/npm.py

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@
4141
'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*'
4242
)
4343

44+
# TODO Once lockfile v2 syntax support is complete, use _process_packages_v2 for
45+
# both v2 and v3 lockfiles
46+
_LOCKFILE_V1_VERSIONS = {1, 2}
47+
_LOCKFILE_V2_VERSIONS = {3}
48+
4449

4550
class NpmLockfileProvider(LockfileProvider):
4651
_ALIAS_RE = re.compile(r'^npm:(.[^@]*)@(.*)$')
@@ -106,6 +111,16 @@ def _process_packages_v2(
106111
continue
107112

108113
name = info.get('name')
114+
inferred_name = False
115+
116+
# NOTE We can't reliably determine the package name from the lockfile v2 syntax,
117+
# but we need it for registry queries and special source processing;
118+
# If we didn't get the package name at this point, try determining it from
119+
# the install path as the last resort
120+
if name is None:
121+
inferred_name = True
122+
path_list = install_path.split('/')
123+
name = '/'.join(path_list[-path_list[::-1].index('node_modules') :])
109124

110125
source: PackageSource
111126
package_json_path = lockfile.parent / install_path / 'package.json'
@@ -114,7 +129,8 @@ def _process_packages_v2(
114129
and package_json_path.exists()
115130
):
116131
source = LocalSource(path=install_path)
117-
if name is None:
132+
if inferred_name:
133+
# Prefer getting the name directly instead of inferring it.
118134
with package_json_path.open('rb') as fp:
119135
name = json.load(fp)['name']
120136
elif 'resolved' in info:
@@ -130,23 +146,12 @@ def _process_packages_v2(
130146
integrity=integrity, resolved=info['resolved']
131147
)
132148
elif resolved_url.scheme.startswith('git+'):
133-
raise NotImplementedError(
134-
'Git sources in lockfile v2 format are not supported yet'
135-
f' (package {install_path} in {lockfile})'
136-
)
149+
source = self.parse_git_source(info['resolved'], name)
137150
else:
138151
raise NotImplementedError(
139152
f"Don't know how to handle package {install_path} in {lockfile}"
140153
)
141154

142-
# NOTE We can't reliably determine the package name from the lockfile v2 syntax,
143-
# but we need it for registry queries and special source processing;
144-
# If we didn't get the package name at this point, try determining it from
145-
# the install path as the last resort
146-
if name is None:
147-
path_list = install_path.split('/')
148-
name = '/'.join(path_list[-path_list[::-1].index('node_modules') :])
149-
150155
yield Package(
151156
name=name,
152157
version=info.get('version'),
@@ -158,11 +163,9 @@ def process_lockfile(self, lockfile: Path) -> Iterator[Package]:
158163
with open(lockfile) as fp:
159164
data = json.load(fp)
160165

161-
# TODO Once lockfile v2 syntax support is complete, use _process_packages_v2
162-
# for both v2 and v2 lockfiles
163-
if data['lockfileVersion'] in {1, 2}:
166+
if data['lockfileVersion'] in _LOCKFILE_V1_VERSIONS:
164167
yield from self._process_packages_v1(lockfile, data)
165-
elif data['lockfileVersion'] in {3}:
168+
elif data['lockfileVersion'] in _LOCKFILE_V2_VERSIONS:
166169
yield from self._process_packages_v2(lockfile, data)
167170
else:
168171
raise NotImplementedError(
@@ -426,14 +429,21 @@ def _finalize(self) -> None:
426429

427430
if self.git_sources:
428431
# Generate jq scripts to patch the package*.json files.
432+
433+
# NOTE: In package.json, we always assign to .value regardless of
434+
# $match_against, because even if we're matching against the keys / package
435+
# names, what we actually want to set is the value / version. With
436+
# package-lock.json, we are in fact matching the values themselves,
437+
# so we re-assign to it directly.
429438
scripts = {
430439
'package.json': r"""
431440
walk(
432441
if type == "object"
433442
then
434443
to_entries | map(
435-
if (.value | type == "string") and $data[.value]
436-
then .value = "git+file:\($buildroot)/\($data[.value])"
444+
.[$match_against] as $match |
445+
if ($match | type == "string") and $data[$match]
446+
then .value = "git+file:\($buildroot)/\($data[$match])"
437447
else .
438448
end
439449
) | from_entries
@@ -443,16 +453,34 @@ def _finalize(self) -> None:
443453
""",
444454
'package-lock.json': r"""
445455
walk(
446-
if type == "object" and (.version | type == "string") and $data[.version]
447-
then
448-
.version = "git+file:\($buildroot)/\($data[.version])"
456+
if type == "object" then
457+
.[$match_against] as $match |
458+
if ($match | type == "string") and $data[$match]
459+
then .[$match_against] =
460+
"git+file:\($buildroot)/\($data[$match])"
461+
else .
462+
end
449463
else .
450464
end
451465
)
452466
""",
453467
}
454468

455469
for lockfile, sources in self.git_sources.items():
470+
with lockfile.open() as fp:
471+
version = json.load(fp)['lockfileVersion']
472+
473+
if version in _LOCKFILE_V2_VERSIONS:
474+
match_against = {
475+
'package.json': 'key',
476+
'package-lock.json': 'resolved',
477+
}
478+
else:
479+
match_against = {
480+
'package.json': 'value',
481+
'package-lock.json': 'version',
482+
}
483+
456484
prefix = self.relative_lockfile_dir(lockfile)
457485
data: Dict[str, Dict[str, str]] = {
458486
'package.json': {},
@@ -486,6 +514,7 @@ def _finalize(self) -> None:
486514
patch_commands[lockfile].append(
487515
'jq'
488516
' --arg buildroot "$FLATPAK_BUILDER_BUILDDIR"'
517+
f' --arg match_against {match_against[filename]}'
489518
f' --argjson data {shlex.quote(json_data)}'
490519
f' {shlex.quote(script)} {target}'
491520
f' > {target}.new'

node/tests/data/packages/minimal-git/package-lock.v3.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
},
1414
"node_modules/nop": {
1515
"version": "1.0.0",
16-
"resolved": "git+ssh://[email protected]/supershabam/nop.git#f110e75f62cfe3bf4468ac3b74e3dc72ab9ae4bf",
17-
"integrity": "sha512-xCwdA7C4QIORvTMytKHMlkEN6axJGimR0gv5vgjKpEKRvQrPOwhjJnrZEcd5g0LP+7IY38+TY7MP59HRY6gcwA==",
16+
"resolved": "git+https://[email protected]/supershabam/nop.git#f110e75f62cfe3bf4468ac3b74e3dc72ab9ae4bf",
1817
"license": "MIT"
1918
}
2019
}

node/tests/test_providers.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@ async def test_minimal_git(
1313
provider_factory_spec: ProviderFactorySpec,
1414
node_version: int,
1515
) -> None:
16-
if node_version >= 18:
17-
pytest.xfail(reason='Git sources not yet supported for lockfile v2 syntax')
18-
1916
with ManifestGenerator() as gen:
2017
await provider_factory_spec.generate_modules('minimal-git', gen, node_version)
2118

0 commit comments

Comments
 (0)