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

pantsbuild / pants / 22285099215

22 Feb 2026 08:52PM UTC coverage: 75.854% (-17.1%) from 92.936%
22285099215

Pull #23121

github

web-flow
Merge c7299df9c into ba8359840
Pull Request #23121: fix issue with optional fields in dependency validator

28 of 29 new or added lines in 2 files covered. (96.55%)

11174 existing lines in 400 files now uncovered.

53694 of 70786 relevant lines covered (75.85%)

1.88 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
4✔
4

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

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

40

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

45

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

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

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

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

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

66

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

71

72
async def _get_relevant_source_files(
4✔
73
    sources: Iterable[SourcesField], with_js: bool = False
74
) -> SourceFiles:
75
    return await determine_source_files(
3✔
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
4✔
86
async def install_node_packages_for_address(
4✔
87
    req: InstalledNodePackageRequest,
88
    union_membership: UnionMembership,
89
    nodejs: nodejs.NodeJS,
90
) -> InstalledNodePackage:
91
    project_env = await get_nodejs_environment(NodeJSProjectEnvironmentRequest(req.address))
3✔
92
    target = project_env.ensure_target()
3✔
93
    transitive_tgts = await transitive_targets(
3✔
94
        TransitiveTargetsRequest([target.address]), **implicitly()
95
    )
96

97
    source_files = await _get_relevant_source_files(
3✔
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
3✔
102

103
    install_result = await fallible_to_exec_result_or_raise(
3✔
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))
3✔
115

116
    return InstalledNodePackage(
3✔
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
4✔
130
async def add_sources_to_installed_node_package(
4✔
131
    req: InstalledNodePackageRequest,
132
) -> InstalledNodePackageWithSource:
133
    installation = await install_node_packages_for_address(req, **implicitly())
1✔
134
    transitive_tgts = await transitive_targets(
1✔
135
        TransitiveTargetsRequest([installation.target.address]), **implicitly()
136
    )
137

138
    source_files = await _get_relevant_source_files(
1✔
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)))
1✔
143
    return InstalledNodePackageWithSource(installation.project_env, digest=digest)
1✔
144

145

146
def rules() -> Iterable[Rule | UnionRule]:
4✔
147
    return [
4✔
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