cumulusci-plus 5.0.15__py3-none-any.whl → 5.0.17__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of cumulusci-plus might be problematic. Click here for more details.

cumulusci/__about__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "5.0.15"
1
+ __version__ = "5.0.17"
cumulusci/cli/cci.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import code
2
2
  import contextlib
3
+ import os
3
4
  import pdb
4
5
  import runpy
5
6
  import signal
@@ -79,8 +80,33 @@ def _cleanup_on_signal(signum):
79
80
  except Exception as e:
80
81
  console.print(f"[red]Error during cleanup: {e}[/red]")
81
82
 
82
- # The parent process's trap is now responsible for killing the process group.
83
- # This process will exit gracefully after receiving the signal from the parent.
83
+ # Terminate child processes in the process group
84
+ try:
85
+ console.print("[yellow]Terminating child processes...[/yellow]")
86
+
87
+ # Temporarily ignore the signal to prevent recursion
88
+ old_handler = signal.signal(signum, signal.SIG_IGN)
89
+
90
+ try:
91
+ # Only use process group termination on Unix systems
92
+ if hasattr(os, "getpgrp") and hasattr(os, "killpg"):
93
+ pgrp = os.getpgrp()
94
+ # Send signal to all processes in the group except ourselves
95
+ os.killpg(pgrp, signum)
96
+ else:
97
+ # On Windows, we can't use process groups, so just log the attempt
98
+ console.print(
99
+ "[yellow]Process group termination not supported on this platform[/yellow]"
100
+ )
101
+ finally:
102
+ # Restore the original signal handler
103
+ signal.signal(signum, old_handler)
104
+
105
+ except ProcessLookupError:
106
+ # Process group may not exist or may already be terminated
107
+ pass
108
+ except Exception as e:
109
+ console.print(f"[red]Warning: Error terminating child processes: {e}[/red]")
84
110
 
85
111
  # Exit with appropriate failure code
86
112
  exit_code = 143 if signum == signal.SIGTERM else 130 # Standard exit codes
@@ -104,8 +130,15 @@ def main(args=None):
104
130
  """
105
131
  global _exit_stack
106
132
 
107
- # By not creating a new process group, cci remains in the parent's group.
108
- # This allows the parent's trap command to correctly terminate cci and its children.
133
+ # Create a new process group so we can terminate all child processes
134
+ # when we receive a termination signal
135
+ try:
136
+ if hasattr(os, "setpgrp"):
137
+ # On Unix systems, create a new process group
138
+ os.setpgrp()
139
+ except Exception:
140
+ # On Windows or if setpgrp fails, continue without process group
141
+ pass
109
142
 
110
143
  # Set up signal handlers for graceful termination
111
144
  signal.signal(signal.SIGTERM, _signal_handler)
@@ -152,7 +185,10 @@ def main(args=None):
152
185
  try:
153
186
  cli(args[1:], standalone_mode=False, obj=runtime)
154
187
  except click.Abort: # Keyboard interrupt
155
- _cleanup_on_signal(signal.SIGINT)
188
+ if _exit_stack:
189
+ _exit_stack.close()
190
+ Console(stderr=True).print("[yellow]Aborted![/yellow]")
191
+ sys.exit(1)
156
192
  except Exception as e:
157
193
  if debug:
158
194
  console = Console()
@@ -1018,6 +1018,7 @@ class TestUnmanagedVcsDependencyFlow:
1018
1018
  # Setup flow config mock
1019
1019
  mock_flow_config = mock.Mock()
1020
1020
  mock_project_config.get_flow.return_value = mock_flow_config
1021
+ mock_project_config.list_tasks.return_value = []
1021
1022
 
1022
1023
  # Setup coordinator mock
1023
1024
  mock_coordinator_instance = mock.Mock()
@@ -1076,11 +1077,7 @@ class TestUnmanagedVcsDependencyFlow:
1076
1077
  assert args[0] == mock_project_config
1077
1078
  assert args[1] == mock_flow_config
1078
1079
  assert kwargs["name"] == "install_deps"
1079
- assert kwargs["options"] == {
1080
- "unmanaged": True, # _get_unmanaged should return True for empty installed_packages
1081
- "namespace_inject": "test_ns",
1082
- "namespace_strip": "old_ns",
1083
- }
1080
+ assert kwargs["options"] == {}
1084
1081
  assert kwargs["skip"] is None
1085
1082
  assert isinstance(kwargs["callbacks"], type(flow_dep.callback_class()))
1086
1083
 
cumulusci/cumulusci.yml CHANGED
@@ -749,6 +749,10 @@ tasks:
749
749
  options:
750
750
  level: info
751
751
  group: Utilities
752
+ configure_env:
753
+ description: Get or set environment variables.
754
+ class_path: cumulusci.tasks.utility.env_management.EnvManagement
755
+ group: Utilities
752
756
  download_extract:
753
757
  description: Downloads files and folders from a VCS repository.
754
758
  class_path: cumulusci.tasks.vcs.download_extract.DownloadExtract
@@ -0,0 +1,190 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ from datetime import date
5
+ from pathlib import Path
6
+ from typing import Any, List, Optional
7
+
8
+ from pydantic import BaseModel, validator
9
+
10
+ from cumulusci.core.config import BaseProjectConfig, OrgConfig, TaskConfig
11
+ from cumulusci.core.tasks import BaseTask
12
+ from cumulusci.utils.options import CCIOptions, Field
13
+ from cumulusci.vcs.bootstrap import get_repo_from_url
14
+
15
+
16
+ class EnvManagementOption(CCIOptions):
17
+ name: str = Field(
18
+ ...,
19
+ description="The name of the environment variable to get the value from the environment",
20
+ )
21
+ default: Any = Field(
22
+ default=None,
23
+ description="The default value of the environment variable. Defaults to None",
24
+ )
25
+ datatype: str = Field(
26
+ default="string",
27
+ description="The datatype of the environment variable. Defaults to string. Valid values are string, bool, int, float, date, list, dict, path, directory, filename, vcs_repo",
28
+ )
29
+ set: bool = Field(
30
+ default=False,
31
+ description="If True, sets the value of the environment variable if it is not already set. Defaults to False",
32
+ )
33
+
34
+ @validator("datatype")
35
+ def validate_datatype(cls, v):
36
+ if v not in [
37
+ "string",
38
+ "bool",
39
+ "int",
40
+ "float",
41
+ "date",
42
+ "list",
43
+ "dict",
44
+ "path",
45
+ "directory",
46
+ "filename",
47
+ "vcs_repo",
48
+ ]:
49
+ raise ValueError(f"Invalid datatype: {v}")
50
+ return v
51
+
52
+ def formated_value(
53
+ self,
54
+ task_values: dict[str, Any],
55
+ project_config: Optional[BaseProjectConfig],
56
+ org_config: Optional[OrgConfig],
57
+ logger: logging.Logger,
58
+ ) -> None:
59
+ value = os.getenv(self.name, self.default)
60
+ datatype = self.datatype or "string"
61
+
62
+ try:
63
+ if self.name not in task_values:
64
+ match datatype:
65
+ case "string":
66
+ task_values[self.name] = str(value)
67
+ case "bool":
68
+ v = DummyValidatorModel(b=value).b
69
+ task_values[self.name] = v
70
+ case "int":
71
+ v = DummyValidatorModel(i=value).i
72
+ task_values[self.name] = v
73
+ case "float":
74
+ v = DummyValidatorModel(f=value).f
75
+ task_values[self.name] = v
76
+ case "date":
77
+ v = DummyValidatorModel(d=date.fromisoformat(str(value))).d
78
+ task_values[self.name] = v
79
+ case "list":
80
+ v = value if isinstance(value, list) else value.split(",")
81
+ task_values[self.name] = v
82
+ case "dict":
83
+ v = value if isinstance(value, dict) else json.loads(str(value))
84
+ task_values[self.name] = v
85
+ case "path":
86
+ v = Path(str(value))
87
+ task_values[self.name] = v.absolute()
88
+ case "directory":
89
+ v = Path(str(value)).parent.absolute()
90
+ task_values[self.name] = v.absolute()
91
+ case "filename":
92
+ v = Path(str(value)).name
93
+ task_values[self.name] = v
94
+ case "vcs_repo":
95
+ task_config = TaskConfig(
96
+ {"options": {"url": self.default, "name": self.name}}
97
+ )
98
+ task = VcsRemoteBranch(project_config, task_config, org_config)
99
+ result = task()
100
+ task_values[self.name] = result["url"]
101
+ task_values[f"{self.name}_BRANCH"] = result["branch"]
102
+ case _:
103
+ raise ValueError(f"Invalid datatype: {datatype}")
104
+ else:
105
+ logger.info(f"Variable {self.name} already set. Skipping.")
106
+
107
+ except Exception as e:
108
+ raise ValueError(
109
+ f"Formatting Error: {value} for datatype: {datatype} - {e}"
110
+ )
111
+
112
+ if self.set and self.name not in os.environ:
113
+ os.environ[self.name] = str(task_values[self.name])
114
+
115
+ if (
116
+ self.set
117
+ and self.datatype == "vcs_repo"
118
+ and f"{self.name}_BRANCH" not in os.environ
119
+ ):
120
+ os.environ[f"{self.name}_BRANCH"] = str(task_values[f"{self.name}_BRANCH"])
121
+
122
+
123
+ class DummyValidatorModel(BaseModel):
124
+ b: Optional[bool]
125
+ i: Optional[int]
126
+ f: Optional[float]
127
+ d: Optional[date]
128
+
129
+
130
+ class EnvManagement(BaseTask):
131
+ class Options(CCIOptions):
132
+ envs: List[EnvManagementOption] = Field(
133
+ default=[],
134
+ description="A list of environment variables definitions.",
135
+ )
136
+
137
+ parsed_options: Options
138
+
139
+ def _run_task(self):
140
+ self.return_values = {}
141
+
142
+ for env_option in self.parsed_options.envs:
143
+ env_option.formated_value(
144
+ self.return_values, self.project_config, self.org_config, self.logger
145
+ )
146
+
147
+ return self.return_values
148
+
149
+
150
+ class VcsRemoteBranch(BaseTask):
151
+ class Options(CCIOptions):
152
+ url: str = Field(
153
+ ...,
154
+ description="Gets if the remote branch name exist with the same name in the remote repository.",
155
+ )
156
+ name: str = Field(
157
+ ...,
158
+ description="The name of the environment variable.",
159
+ )
160
+
161
+ parsed_options: Options
162
+
163
+ def _run_task(self):
164
+ self.return_values = {}
165
+
166
+ # Get current branch name. Based on Local Git Branch if not available from Environment Variable
167
+ local_branch = os.getenv(
168
+ f"{self.parsed_options.name}_BRANCH", self.project_config.repo_branch
169
+ )
170
+
171
+ # Get repository URL from Environment Variable
172
+ self.return_values["url"] = os.getenv(
173
+ self.parsed_options.name, self.parsed_options.url
174
+ )
175
+
176
+ repo = get_repo_from_url(self.project_config, self.return_values["url"])
177
+
178
+ try:
179
+ branch = repo.branch(local_branch)
180
+ self.logger.info(
181
+ f"Branch {local_branch} found in repository {self.return_values['url']}."
182
+ )
183
+ self.return_values["branch"] = branch.name
184
+ except Exception:
185
+ self.logger.warning(
186
+ f"Branch {local_branch} not found in repository {self.return_values['url']}. Using default branch {repo.default_branch}"
187
+ )
188
+ self.return_values["branch"] = repo.default_branch
189
+
190
+ return self.return_values
@@ -0,0 +1,238 @@
1
+ import os
2
+ import unittest
3
+ from datetime import date
4
+ from pathlib import Path
5
+ from unittest.mock import Mock, patch
6
+
7
+ from cumulusci.core.config import TaskConfig
8
+ from cumulusci.tasks.utility.env_management import (
9
+ EnvManagement,
10
+ EnvManagementOption,
11
+ VcsRemoteBranch,
12
+ )
13
+ from cumulusci.tests.util import create_project_config
14
+
15
+
16
+ class TestVcsRemoteBranch(unittest.TestCase):
17
+ def test_run_task_branch_exists(self):
18
+ project_config = create_project_config()
19
+ project_config.repo_info["branch"] = "feature/branch-1"
20
+ task_config = TaskConfig(
21
+ {
22
+ "options": {
23
+ "url": "https://github.com/TestOwner/TestRepo",
24
+ "name": "VCS_URL",
25
+ }
26
+ }
27
+ )
28
+
29
+ with patch(
30
+ "cumulusci.tasks.utility.env_management.get_repo_from_url"
31
+ ) as get_repo_mock:
32
+ repo_mock = Mock()
33
+ branch_mock = Mock()
34
+ branch_mock.name = "feature/branch-1"
35
+ repo_mock.branch.return_value = branch_mock
36
+ get_repo_mock.return_value = repo_mock
37
+
38
+ task = VcsRemoteBranch(project_config, task_config)
39
+ task()
40
+ self.assertEqual(
41
+ task.return_values["url"], "https://github.com/TestOwner/TestRepo"
42
+ )
43
+ self.assertEqual(task.return_values["branch"], "feature/branch-1")
44
+ repo_mock.branch.assert_called_once_with("feature/branch-1")
45
+
46
+ def test_run_task_branch_not_exist(self):
47
+ project_config = create_project_config()
48
+ project_config.repo_info["branch"] = "feature/branch-1"
49
+ task_config = TaskConfig(
50
+ {
51
+ "options": {
52
+ "url": "https://github.com/TestOwner/TestRepo",
53
+ "name": "VCS_URL",
54
+ }
55
+ }
56
+ )
57
+
58
+ with patch(
59
+ "cumulusci.tasks.utility.env_management.get_repo_from_url"
60
+ ) as get_repo_mock:
61
+ repo_mock = Mock()
62
+ repo_mock.branch.side_effect = Exception("Branch not found")
63
+ repo_mock.default_branch = "main"
64
+ get_repo_mock.return_value = repo_mock
65
+
66
+ task = VcsRemoteBranch(project_config, task_config)
67
+ task()
68
+ self.assertEqual(
69
+ task.return_values["url"], "https://github.com/TestOwner/TestRepo"
70
+ )
71
+ self.assertEqual(task.return_values["branch"], "main")
72
+ repo_mock.branch.assert_called_once_with("feature/branch-1")
73
+
74
+
75
+ class TestEnvManagement(unittest.TestCase):
76
+ @classmethod
77
+ def setUpClass(cls):
78
+ cls.project_config = create_project_config()
79
+ cls.org_config = Mock()
80
+
81
+ def test_init_options(self):
82
+ task_config = TaskConfig(
83
+ {
84
+ "options": {
85
+ "envs": [
86
+ {"name": "MY_VAR", "default": "default_val"},
87
+ {"name": "MY_BOOL", "datatype": "bool", "default": "true"},
88
+ ]
89
+ }
90
+ }
91
+ )
92
+ task = EnvManagement(self.project_config, task_config)
93
+ self.assertEqual(len(task.parsed_options.envs), 2)
94
+ self.assertIsInstance(task.parsed_options.envs[0], EnvManagementOption)
95
+
96
+ @patch.dict(os.environ, {}, clear=True)
97
+ def test_run_task_get_values(self):
98
+ os.environ["MY_VAR"] = "env_val"
99
+ os.environ["MY_INT"] = "123"
100
+ os.environ["MY_DATE"] = "2023-10-26"
101
+ os.environ["MY_LIST"] = "a,b,c"
102
+
103
+ task_config = TaskConfig(
104
+ {
105
+ "options": {
106
+ "envs": [
107
+ {"name": "MY_VAR"},
108
+ {"name": "MY_INT", "datatype": "int"},
109
+ {"name": "MY_DATE", "datatype": "date"},
110
+ {"name": "MY_LIST", "datatype": "list"},
111
+ {"name": "NOT_SET", "default": "default_val"},
112
+ ]
113
+ }
114
+ }
115
+ )
116
+ task = EnvManagement(self.project_config, task_config, self.org_config)
117
+ result = task()
118
+
119
+ self.assertEqual(
120
+ result,
121
+ {
122
+ "MY_VAR": "env_val",
123
+ "MY_INT": 123,
124
+ "MY_DATE": date(2023, 10, 26),
125
+ "MY_LIST": ["a", "b", "c"],
126
+ "NOT_SET": "default_val",
127
+ },
128
+ )
129
+ self.assertNotIn("NOT_SET", os.environ)
130
+
131
+ @patch.dict(os.environ, {}, clear=True)
132
+ def test_run_task_set_values(self):
133
+ os.environ["EXISTING_VAR"] = "original_value"
134
+ task_config = TaskConfig(
135
+ {
136
+ "options": {
137
+ "envs": [
138
+ {"name": "NEW_VAR", "default": "new_value", "set": True},
139
+ {
140
+ "name": "EXISTING_VAR",
141
+ "default": "new_default",
142
+ "set": True,
143
+ },
144
+ ]
145
+ }
146
+ }
147
+ )
148
+ task = EnvManagement(self.project_config, task_config, self.org_config)
149
+ task()
150
+
151
+ self.assertEqual(os.environ.get("NEW_VAR"), "new_value")
152
+ self.assertEqual(os.environ.get("EXISTING_VAR"), "original_value")
153
+
154
+ def test_datatype_validation(self):
155
+ with self.assertRaises(ValueError):
156
+ EnvManagementOption(name="test", datatype="invalid")
157
+
158
+ @patch.dict(os.environ, {}, clear=True)
159
+ def _test_datatype_conversion(self, datatype, env_value, expected_value):
160
+ os.environ["TEST_VAR"] = str(env_value)
161
+ task_config = TaskConfig(
162
+ {"options": {"envs": [{"name": "TEST_VAR", "datatype": datatype}]}}
163
+ )
164
+ task = EnvManagement(self.project_config, task_config, self.org_config)
165
+ result = task()
166
+ self.assertEqual(result["TEST_VAR"], expected_value)
167
+
168
+ def test_datatypes(self):
169
+ test_cases = [
170
+ ("string", "hello", "hello"),
171
+ ("bool", "true", True),
172
+ ("bool", "0", False),
173
+ ("int", "42", 42),
174
+ ("float", "3.14", 3.14),
175
+ ("date", "2024-01-01", date(2024, 1, 1)),
176
+ ("list", "one,two", ["one", "two"]),
177
+ ("dict", '{"key": "value"}', {"key": "value"}),
178
+ ("path", "/tmp/test", Path("/tmp/test").absolute()),
179
+ ("directory", "/tmp/test/file.txt", Path("/tmp/test").absolute()),
180
+ ("filename", "/tmp/test/file.txt", "file.txt"),
181
+ ]
182
+ for datatype, env_value, expected_value in test_cases:
183
+ with self.subTest(datatype=datatype):
184
+ self._test_datatype_conversion(datatype, env_value, expected_value)
185
+
186
+ def test_formatting_error(self):
187
+ os.environ["TEST_VAR"] = "not-an-int"
188
+ task_config = TaskConfig(
189
+ {"options": {"envs": [{"name": "TEST_VAR", "datatype": "int"}]}}
190
+ )
191
+ task = EnvManagement(self.project_config, task_config, self.org_config)
192
+ with self.assertRaises(ValueError):
193
+ task()
194
+
195
+ @patch("cumulusci.tasks.utility.env_management.VcsRemoteBranch")
196
+ def test_vcs_repo_datatype(self, vcs_mock):
197
+ vcs_instance_mock = vcs_mock.return_value
198
+ vcs_instance_mock.return_value = {
199
+ "url": "https://github.com/TestOwner/TestRepo",
200
+ "branch": "my-feature-branch",
201
+ }
202
+
203
+ task_config = TaskConfig(
204
+ {
205
+ "options": {
206
+ "envs": [
207
+ {
208
+ "name": "VCS_URL",
209
+ "datatype": "vcs_repo",
210
+ "default": "https://github.com/TestOwner/TestRepo",
211
+ }
212
+ ]
213
+ }
214
+ }
215
+ )
216
+ task = EnvManagement(self.project_config, task_config, self.org_config)
217
+ result = task()
218
+
219
+ self.assertEqual(result["VCS_URL"], "https://github.com/TestOwner/TestRepo")
220
+ self.assertEqual(result["VCS_URL_BRANCH"], "my-feature-branch")
221
+ vcs_mock.assert_called_once()
222
+
223
+ @patch.dict(os.environ, {}, clear=True)
224
+ def test_run_task_with_default_types(self):
225
+ task_config = TaskConfig(
226
+ {
227
+ "options": {
228
+ "envs": [
229
+ {"name": "MY_LIST", "datatype": "list", "default": ["a", "b"]},
230
+ {"name": "MY_DICT", "datatype": "dict", "default": {"x": 1}},
231
+ ]
232
+ }
233
+ }
234
+ )
235
+ task = EnvManagement(self.project_config, task_config, self.org_config)
236
+ result = task()
237
+ self.assertEqual(result["MY_LIST"], ["a", "b"])
238
+ self.assertEqual(result["MY_DICT"], {"x": 1})
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cumulusci-plus
3
- Version: 5.0.15
3
+ Version: 5.0.17
4
4
  Summary: Build and release tools for Salesforce developers
5
5
  Project-URL: Homepage, https://github.com/jorgesolebur/CumulusCI
6
6
  Project-URL: Changelog, https://cumulusci.readthedocs.io/en/stable/history.html
@@ -127,7 +127,7 @@ license](https://github.com/SFDO-Tooling/CumulusCI/blob/main/LICENSE)
127
127
  and is not covered by the Salesforce Master Subscription Agreement.
128
128
 
129
129
  <!-- Changelog -->
130
- ## v5.0.15 (2025-08-03)
130
+ ## v5.0.17 (2025-08-21)
131
131
 
132
132
  <!-- Release notes generated using configuration in .github/release.yml at main -->
133
133
 
@@ -135,6 +135,6 @@ and is not covered by the Salesforce Master Subscription Agreement.
135
135
 
136
136
  ### Changes
137
137
 
138
- - Add flow options to all supported flows by [@rupeshjSFDC](https://github.com/rupeshjSFDC) in [#55](https://github.com/jorgesolebur/CumulusCI/pull/55)
138
+ - Add custom data type vcs_repo. by [@rupeshjSFDC](https://github.com/rupeshjSFDC) in [#59](https://github.com/jorgesolebur/CumulusCI/pull/59)
139
139
 
140
- **Full Changelog**: https://github.com/jorgesolebur/CumulusCI/compare/v5.0.14...v5.0.15
140
+ **Full Changelog**: https://github.com/jorgesolebur/CumulusCI/compare/v5.0.16...v5.0.17
@@ -1,10 +1,10 @@
1
- cumulusci/__about__.py,sha256=CSa0T2mpxiHKM0FVjjxfKMSOMTpwIW3bbcCOvuh7XIE,23
1
+ cumulusci/__about__.py,sha256=x4NllGN1pNABBzi1vkIo-rQ2JSdgVyJUAI6uFT43BEA,23
2
2
  cumulusci/__init__.py,sha256=jdanFQ_i8vbdO7Eltsf4pOfvV4mwa_Osyc4gxWKJ8ng,764
3
3
  cumulusci/__main__.py,sha256=kgRH-n5AJrH_daCK_EJwH7azAUxdXEmpi-r-dPGMR6Y,43
4
4
  cumulusci/conftest.py,sha256=AIL98BDwNAQtdo8YFmLKwav0tmrQ5dpbw1cX2FyGouQ,5108
5
- cumulusci/cumulusci.yml,sha256=qJscpADSMzcxNYYapfbsZO2LXqjjyv7QLs62Pn2qALI,72445
5
+ cumulusci/cumulusci.yml,sha256=h4Ywck_HV5PXqjdTvA6jMm8dxPLZi2tmFdwGmssnhdo,72617
6
6
  cumulusci/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- cumulusci/cli/cci.py,sha256=ZSebArvNvIcHdoUQiNXhCpJroTn9MXE8xiBW3h1WLoQ,10548
7
+ cumulusci/cli/cci.py,sha256=yAq8jFoGde6g_1TeAAjzZYsk77itiONCQGBFe3g3nOs,11836
8
8
  cumulusci/cli/error.py,sha256=znj0YN8D2Grozm1u7mZAsJlmmdGebbuy0c1ofQluL4Q,4410
9
9
  cumulusci/cli/flow.py,sha256=rN_9WL2Z6dcx-oRngChIgei3E5Qmg3XVzk5ND1o0i3s,6171
10
10
  cumulusci/cli/logger.py,sha256=bpzSD0Bm0BAwdNbVR6yZXMREh2vm7jOytZevEaNoVR4,2267
@@ -73,7 +73,7 @@ cumulusci/core/dependencies/resolvers.py,sha256=xWVijK6Eu-WFyGnQPFANLkZFTjq4NQYh
73
73
  cumulusci/core/dependencies/utils.py,sha256=y54fWqLZ8IUIV9i1b6WcDXIJsK0ygny5DTsZCXbgeoM,369
74
74
  cumulusci/core/dependencies/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
75
  cumulusci/core/dependencies/tests/conftest.py,sha256=rd0ZqpV6GTk7IRtPYJ7jyLWHM1g_xtZ4PABuvkN-TZY,11004
76
- cumulusci/core/dependencies/tests/test_dependencies.py,sha256=MiKet_2xCMhzyo3oz_DzBnwVBjfCetmbmpBVQVKMEpg,40705
76
+ cumulusci/core/dependencies/tests/test_dependencies.py,sha256=1nE1dkhwOSWyIP3KWyy5GJrUa-DMDOGcoWxcoSRV3D0,40572
77
77
  cumulusci/core/dependencies/tests/test_github.py,sha256=_JiUh-xgGmnsA2X_57i3gOwcDv_xBj66mdhitcZ-APU,2765
78
78
  cumulusci/core/dependencies/tests/test_resolvers.py,sha256=kLnvs0QTDKQwkxZ_lAAcetWZUNwBoP-TJYp9fdgRbHU,36271
79
79
  cumulusci/core/keychain/__init__.py,sha256=UbuaIrKZSczVVqbG_7BHFTkQukbqinGJlJlQGIpIsOI,595
@@ -611,6 +611,8 @@ cumulusci/tasks/tests/test_pushfails.py,sha256=9JG9D0iD4dR-1fKheaRN7BEy3lzzuOKeR
611
611
  cumulusci/tasks/tests/test_salesforce.py,sha256=yCGtuHapxyAEmXQhuF2g2fh2naknTu7Md4OfEJQvGAA,2594
612
612
  cumulusci/tasks/tests/test_sfdx.py,sha256=oUbHo28d796m5RuskXMLitJw2rCLjjXIfxggzr4gsso,3545
613
613
  cumulusci/tasks/tests/test_util.py,sha256=D1T0QnvPTS0PHeZWo2xiVgE1jVTYcLzGTGHwEIoVmxk,7296
614
+ cumulusci/tasks/utility/env_management.py,sha256=nJyv7KFtaBaAUK_40h-FpyfHTd7xSfEFkm9cj-tOhqc,6715
615
+ cumulusci/tasks/utility/tests/test_env_management.py,sha256=fw34meWGOe1YYZO449MMCi2O7BgSaOA_I_wScrIr1Uk,8702
614
616
  cumulusci/tasks/vcs/__init__.py,sha256=ZzpMZnhooXZ6r_ywBVTS3UNw9uMcXW6h33LylRqTDK0,700
615
617
  cumulusci/tasks/vcs/commit_status.py,sha256=hgPUVHeQyIfMsCuwrw2RI-ufnbjdRARc6HI3BEgdcxI,2332
616
618
  cumulusci/tasks/vcs/create_commit_status.py,sha256=kr_OFM3J32jxusS1og-K91Kl1F_Nr7SlevRsJKBhifs,1565
@@ -736,9 +738,9 @@ cumulusci/vcs/tests/dummy_service.py,sha256=RltOUpMIhSDNrfxk0LhLqlH4ppC0sK6NC2cO
736
738
  cumulusci/vcs/tests/test_vcs_base.py,sha256=9mp6uZ3lTxY4onjUNCucp9N9aB3UylKS7_2Zu_hdAZw,24331
737
739
  cumulusci/vcs/tests/test_vcs_bootstrap.py,sha256=N0NA48-rGNIIjY3Z7PtVnNwHObSlEGDk2K55TQGI8g4,27954
738
740
  cumulusci/vcs/utils/__init__.py,sha256=py4fEcHM7Vd0M0XWznOlywxaeCtG3nEVGmELmEKVGU8,869
739
- cumulusci_plus-5.0.15.dist-info/METADATA,sha256=4e1vAn_cj3AsDwpFBVZR4E_d8UL6CWwI9lBBZgBrtZg,5760
740
- cumulusci_plus-5.0.15.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
741
- cumulusci_plus-5.0.15.dist-info/entry_points.txt,sha256=nTtu04b9iLXhzADcTrb5PwmdXE6e2MTUAMh9OK6Z2pg,80
742
- cumulusci_plus-5.0.15.dist-info/licenses/AUTHORS.rst,sha256=PvewjKImdKPhhJ6xR2EEZ4T7GbpY2ZeAeyWm2aLtiMQ,676
743
- cumulusci_plus-5.0.15.dist-info/licenses/LICENSE,sha256=NFsF_s7RVXk2dU6tmRAN8wF45pnD98VZ5IwqOsyBcaU,1499
744
- cumulusci_plus-5.0.15.dist-info/RECORD,,
741
+ cumulusci_plus-5.0.17.dist-info/METADATA,sha256=hO8FAqzbbv4M0ugIDh2QHRGmg7WArDbaCBGrPgW4Vto,5751
742
+ cumulusci_plus-5.0.17.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
743
+ cumulusci_plus-5.0.17.dist-info/entry_points.txt,sha256=nTtu04b9iLXhzADcTrb5PwmdXE6e2MTUAMh9OK6Z2pg,80
744
+ cumulusci_plus-5.0.17.dist-info/licenses/AUTHORS.rst,sha256=PvewjKImdKPhhJ6xR2EEZ4T7GbpY2ZeAeyWm2aLtiMQ,676
745
+ cumulusci_plus-5.0.17.dist-info/licenses/LICENSE,sha256=NFsF_s7RVXk2dU6tmRAN8wF45pnD98VZ5IwqOsyBcaU,1499
746
+ cumulusci_plus-5.0.17.dist-info/RECORD,,