• 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

95.88
/src/python/pants/backend/javascript/nodejs_project_environment.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, field
4✔
8

9
from pants.backend.javascript import package_json, resolve
4✔
10
from pants.backend.javascript.nodejs_project import NodeJSProject
4✔
11
from pants.backend.javascript.package_json import (
4✔
12
    NodePackageExtraEnvVarsField,
13
    NodePackageNameField,
14
    OwningNodePackage,
15
    OwningNodePackageRequest,
16
    find_owning_package,
17
)
18
from pants.backend.javascript.resolve import (
4✔
19
    ChosenNodeResolve,
20
    RequestNodeResolve,
21
    resolve_for_package,
22
)
23
from pants.backend.javascript.subsystems import nodejs
4✔
24
from pants.backend.javascript.subsystems.nodejs import NodeJSToolProcess, setup_node_tool_process
4✔
25
from pants.build_graph.address import Address
4✔
26
from pants.core.util_rules.env_vars import environment_vars_subset
4✔
27
from pants.engine.env_vars import EnvironmentVarsRequest
4✔
28
from pants.engine.internals.native_engine import EMPTY_DIGEST, Digest, MergeDigests
4✔
29
from pants.engine.internals.selectors import concurrently
4✔
30
from pants.engine.intrinsics import merge_digests, path_globs_to_digest
4✔
31
from pants.engine.process import Process
4✔
32
from pants.engine.rules import Rule, collect_rules, implicitly, rule
4✔
33
from pants.engine.target import Target
4✔
34
from pants.engine.unions import UnionRule
4✔
35
from pants.util.dirutil import fast_relpath
4✔
36
from pants.util.frozendict import FrozenDict
4✔
37
from pants.util.logging import LogLevel
4✔
38

39

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

44

45
@dataclass(frozen=True)
4✔
46
class NodeJsProjectEnvironment:
4✔
47
    resolve: ChosenNodeResolve
4✔
48
    package: OwningNodePackage | None = None
4✔
49

50
    @classmethod
4✔
51
    def from_root(cls, project: NodeJSProject) -> NodeJsProjectEnvironment:
4✔
UNCOV
52
        return cls(resolve=ChosenNodeResolve(project), package=None)
×
53

54
    @property
4✔
55
    def project(self) -> NodeJSProject:
4✔
56
        return self.resolve.project
3✔
57

58
    @property
4✔
59
    def root_dir(self) -> str:
4✔
60
        return self.project.root_dir
3✔
61

62
    @property
4✔
63
    def node_modules_directories(self) -> Iterable[str]:
4✔
64
        yield "node_modules"
3✔
65
        if self.package and not self.project.single_workspace:
3✔
66
            yield os.path.join(self.relative_workspace_directory(), "node_modules")
1✔
67

68
    @property
4✔
69
    def target(self) -> Target | None:
4✔
70
        if self.package:
3✔
71
            return self.package.target
3✔
72
        else:
UNCOV
73
            return None
×
74

75
    def package_dir(self) -> str:
4✔
76
        if self.package and not self.project.single_workspace:
1✔
77
            return self.ensure_target().residence_dir
1✔
78
        else:
UNCOV
79
            return self.root_dir
×
80

81
    def relative_workspace_directory(self) -> str:
4✔
82
        target = self.ensure_target()
1✔
83
        from_root_to_workspace = fast_relpath(target.residence_dir, self.root_dir)
1✔
84
        return from_root_to_workspace
1✔
85

86
    def ensure_target(self) -> Target:
4✔
87
        if self.target:
3✔
88
            return self.target
3✔
89
        raise ValueError("")
×
90

91

92
@dataclass(frozen=True)
4✔
93
class NodeJsProjectEnvironmentProcess:
4✔
94
    env: NodeJsProjectEnvironment
4✔
95
    args: Iterable[str]
4✔
96
    description: str
4✔
97
    level: LogLevel = LogLevel.INFO
4✔
98
    input_digest: Digest = EMPTY_DIGEST
4✔
99
    output_files: tuple[str, ...] = ()
4✔
100
    output_directories: tuple[str, ...] = ()
4✔
101
    per_package_caches: FrozenDict[str, str] = field(default_factory=FrozenDict)
4✔
102
    project_caches: FrozenDict[str, str] = field(default_factory=FrozenDict)
4✔
103
    timeout_seconds: int | None = None
4✔
104
    extra_env: FrozenDict[str, str] = field(default_factory=FrozenDict)
4✔
105

106
    def targeted_args(self) -> tuple[str, ...]:
4✔
107
        if (
3✔
108
            not self.env.project.single_workspace
109
            and self.env.target
110
            and self.env.root_dir != self.env.package_dir()
111
        ):
112
            target = self.env.ensure_target()
1✔
113
            return (
1✔
114
                self.env.project.workspace_specifier_arg,
115
                target[NodePackageNameField].value,
116
                *self.args,
117
            )
118
        else:
119
            return tuple(self.args)
3✔
120

121

122
@rule(desc="Assembling nodejs project environment")
4✔
123
async def get_nodejs_environment(req: NodeJSProjectEnvironmentRequest) -> NodeJsProjectEnvironment:
4✔
124
    node_resolve, owning_tgt = await concurrently(
3✔
125
        resolve_for_package(RequestNodeResolve(req.address), **implicitly()),
126
        find_owning_package(OwningNodePackageRequest(req.address)),
127
    )
128
    assert owning_tgt.target, f"Already ensured to exist by {ChosenNodeResolve.__name__}."
3✔
129

130
    return NodeJsProjectEnvironment(node_resolve, owning_tgt)
3✔
131

132

133
@rule
4✔
134
async def setup_nodejs_project_environment_process(
4✔
135
    req: NodeJsProjectEnvironmentProcess,
136
    nodejs: nodejs.NodeJS,
137
) -> Process:
138
    target_env_vars = (
3✔
139
        req.env.target.get(NodePackageExtraEnvVarsField).value or () if req.env.target else ()
140
    )
141

142
    lockfile_digest, project_digest, subsystem_env_vars, env_vars = await concurrently(
3✔
143
        path_globs_to_digest(req.env.resolve.get_lockfile_glob()),
144
        merge_digests(req.env.project.get_project_digest()),
145
        environment_vars_subset(EnvironmentVarsRequest(nodejs.extra_env_vars), **implicitly()),
146
        environment_vars_subset(EnvironmentVarsRequest(target_env_vars), **implicitly()),
147
    )
148
    merged = await merge_digests(MergeDigests((req.input_digest, lockfile_digest, project_digest)))
3✔
149

150
    args = req.targeted_args()
3✔
151
    output_files = req.output_files
3✔
152
    output_directories = req.output_directories
3✔
153
    per_package_caches = FrozenDict(
3✔
154
        {
155
            key: os.path.join(req.env.package_dir(), value)
156
            for key, value in req.per_package_caches.items()
157
        }
158
    )
159
    append_only_caches: FrozenDict[str, str] = FrozenDict(
3✔
160
        **per_package_caches, **req.project_caches, **req.env.project.extra_caches()
161
    )
162

163
    return await setup_node_tool_process(
3✔
164
        **implicitly(
165
            NodeJSToolProcess(
166
                tool=req.env.project.package_manager.name,
167
                tool_version=req.env.project.package_manager.version,
168
                args=args,
169
                description=req.description,
170
                level=req.level,
171
                input_digest=merged,
172
                working_directory=req.env.root_dir,
173
                output_files=output_files,
174
                output_directories=output_directories,
175
                append_only_caches=append_only_caches,
176
                timeout_seconds=req.timeout_seconds,
177
                project_digest=project_digest,
178
                extra_env=FrozenDict(
179
                    {
180
                        **subsystem_env_vars,
181
                        **env_vars,
182
                        **req.extra_env,
183
                        **req.env.project.extra_env(),
184
                    }
185
                ),
186
            )
187
        )
188
    )
189

190

191
def rules() -> Iterable[Rule | UnionRule]:
4✔
192
    return [*collect_rules(), *nodejs.rules(), *resolve.rules(), *package_json.rules()]
4✔
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