• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pantsbuild / pants / 24145945949

08 Apr 2026 04:14PM UTC coverage: 82.077% (-10.8%) from 92.91%
24145945949

Pull #23233

github

web-flow
Merge 089d98e3c into 9036734c9
Pull Request #23233: Introduce a LockfileFormat enum.

8 of 11 new or added lines in 4 files covered. (72.73%)

7635 existing lines in 306 files now uncovered.

63732 of 77649 relevant lines covered (82.08%)

2.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.41
/src/python/pants/backend/javascript/install_node_package.py
1
# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3
from __future__ import annotations
7✔
4

5
import os.path
7✔
6
from collections.abc import Iterable
7✔
7
from dataclasses import dataclass
7✔
8

9
from pants.backend.javascript import nodejs_project_environment
7✔
10
from pants.backend.javascript.dependency_inference.rules import rules as dependency_inference_rules
7✔
11
from pants.backend.javascript.nodejs_project_environment import (
7✔
12
    NodeJsProjectEnvironment,
13
    NodeJsProjectEnvironmentProcess,
14
    NodeJSProjectEnvironmentRequest,
15
    get_nodejs_environment,
16
)
17
from pants.backend.javascript.package_json import (
7✔
18
    NodePackageNameField,
19
    NodePackageVersionField,
20
    PackageJsonSourceField,
21
)
22
from pants.backend.javascript.package_manager import PackageManager
7✔
23
from pants.backend.javascript.subsystems import nodejs
7✔
24
from pants.backend.javascript.target_types import JSRuntimeSourceField
7✔
25
from pants.build_graph.address import Address
7✔
26
from pants.core.target_types import FileSourceField, ResourceSourceField
7✔
27
from pants.core.util_rules.source_files import (
7✔
28
    SourceFiles,
29
    SourceFilesRequest,
30
    determine_source_files,
31
)
32
from pants.engine.internals.graph import transitive_targets
7✔
33
from pants.engine.internals.native_engine import AddPrefix, Digest, MergeDigests
7✔
34
from pants.engine.intrinsics import add_prefix, merge_digests
7✔
35
from pants.engine.process import fallible_to_exec_result_or_raise
7✔
36
from pants.engine.rules import Rule, collect_rules, implicitly, rule
7✔
37
from pants.engine.target import SourcesField, Target, TransitiveTargetsRequest
7✔
38
from pants.engine.unions import UnionMembership, UnionRule
7✔
39

40

41
@dataclass(frozen=True)
7✔
42
class InstalledNodePackageRequest:
7✔
43
    address: Address
7✔
44

45

46
@dataclass(frozen=True)
7✔
47
class InstalledNodePackage:
7✔
48
    project_env: NodeJsProjectEnvironment
7✔
49
    digest: Digest
7✔
50

51
    @property
7✔
52
    def project_dir(self) -> str:
7✔
53
        return self.project_env.root_dir
4✔
54

55
    def join_relative_workspace_directory(self, path: str) -> str:
7✔
56
        return os.path.join(self.project_env.relative_workspace_directory(), path)
2✔
57

58
    @property
7✔
59
    def target(self) -> Target:
7✔
60
        return self.project_env.ensure_target()
2✔
61

62
    @property
7✔
63
    def package_manager(self) -> PackageManager:
7✔
UNCOV
64
        return self.project_env.project.package_manager
×
65

66

67
@dataclass(frozen=True)
7✔
68
class InstalledNodePackageWithSource(InstalledNodePackage):
7✔
69
    pass
7✔
70

71

72
async def _get_relevant_source_files(
7✔
73
    sources: Iterable[SourcesField], with_js: bool = False
74
) -> SourceFiles:
75
    return await determine_source_files(
5✔
76
        SourceFilesRequest(
77
            sources,
78
            for_sources_types=(PackageJsonSourceField, FileSourceField)
79
            + ((ResourceSourceField, JSRuntimeSourceField) if with_js else ()),
80
            enable_codegen=True,
81
        )
82
    )
83

84

85
@rule
7✔
86
async def install_node_packages_for_address(
7✔
87
    req: InstalledNodePackageRequest,
88
    union_membership: UnionMembership,
89
    nodejs: nodejs.NodeJS,
90
) -> InstalledNodePackage:
91
    project_env = await get_nodejs_environment(NodeJSProjectEnvironmentRequest(req.address))
5✔
92
    target = project_env.ensure_target()
5✔
93
    transitive_tgts = await transitive_targets(
5✔
94
        TransitiveTargetsRequest([target.address]), **implicitly()
95
    )
96

97
    source_files = await _get_relevant_source_files(
5✔
98
        (tgt[SourcesField] for tgt in transitive_tgts.closure if tgt.has_field(SourcesField)),
99
        with_js=False,
100
    )
101
    package_digest = source_files.snapshot.digest
5✔
102

103
    install_result = await fallible_to_exec_result_or_raise(
5✔
104
        **implicitly(
105
            NodeJsProjectEnvironmentProcess(
106
                project_env,
107
                project_env.project.immutable_install_args,
108
                description=f"Installing {target[NodePackageNameField].value}@{target[NodePackageVersionField].value}.",
109
                input_digest=package_digest,
110
                output_directories=tuple(project_env.node_modules_directories),
111
            )
112
        )
113
    )
114
    node_modules = await add_prefix(AddPrefix(install_result.output_digest, project_env.root_dir))
5✔
115

116
    return InstalledNodePackage(
5✔
117
        project_env,
118
        digest=await merge_digests(
119
            MergeDigests(
120
                [
121
                    package_digest,
122
                    node_modules,
123
                ]
124
            )
125
        ),
126
    )
127

128

129
@rule
7✔
130
async def add_sources_to_installed_node_package(
7✔
131
    req: InstalledNodePackageRequest,
132
) -> InstalledNodePackageWithSource:
133
    installation = await install_node_packages_for_address(req, **implicitly())
2✔
134
    transitive_tgts = await transitive_targets(
2✔
135
        TransitiveTargetsRequest([installation.target.address]), **implicitly()
136
    )
137

138
    source_files = await _get_relevant_source_files(
2✔
139
        (tgt[SourcesField] for tgt in transitive_tgts.dependencies if tgt.has_field(SourcesField)),
140
        with_js=True,
141
    )
142
    digest = await merge_digests(MergeDigests((installation.digest, source_files.snapshot.digest)))
2✔
143
    return InstalledNodePackageWithSource(installation.project_env, digest=digest)
2✔
144

145

146
def rules() -> Iterable[Rule | UnionRule]:
7✔
147
    return [
7✔
148
        *nodejs.rules(),
149
        *nodejs_project_environment.rules(),
150
        *dependency_inference_rules(),
151
        *collect_rules(),
152
    ]
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc