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

pantsbuild / pants / 22740642519

05 Mar 2026 11:00PM UTC coverage: 52.677% (-40.3%) from 92.931%
22740642519

Pull #23157

github

web-flow
Merge 2aa18e6d4 into f0030f5e7
Pull Request #23157: [pants ng] Partition source files by config.

31678 of 60136 relevant lines covered (52.68%)

0.53 hits per line

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

87.63
/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
1✔
4

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

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

39

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

44

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

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

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

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

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

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

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

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

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

91

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

106
    def targeted_args(self) -> tuple[str, ...]:
1✔
107
        if (
1✔
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()
×
113
            return (
×
114
                self.env.project.workspace_specifier_arg,
115
                target[NodePackageNameField].value,
116
                *self.args,
117
            )
118
        else:
119
            return tuple(self.args)
1✔
120

121

122
@rule(desc="Assembling nodejs project environment")
1✔
123
async def get_nodejs_environment(req: NodeJSProjectEnvironmentRequest) -> NodeJsProjectEnvironment:
1✔
124
    node_resolve, owning_tgt = await concurrently(
1✔
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__}."
1✔
129

130
    return NodeJsProjectEnvironment(node_resolve, owning_tgt)
1✔
131

132

133
@rule
1✔
134
async def setup_nodejs_project_environment_process(
1✔
135
    req: NodeJsProjectEnvironmentProcess,
136
    nodejs: nodejs.NodeJS,
137
) -> Process:
138
    target_env_vars = (
1✔
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(
1✔
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)))
1✔
149

150
    args = req.targeted_args()
1✔
151
    output_files = req.output_files
1✔
152
    output_directories = req.output_directories
1✔
153
    per_package_caches = FrozenDict(
1✔
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(
1✔
160
        **per_package_caches, **req.project_caches, **req.env.project.extra_caches()
161
    )
162

163
    return await setup_node_tool_process(
1✔
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]:
1✔
192
    return [*collect_rules(), *nodejs.rules(), *resolve.rules(), *package_json.rules()]
1✔
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