cumulusci-plus 5.0.21__py3-none-any.whl → 5.0.35__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.
Files changed (121) hide show
  1. cumulusci/__about__.py +1 -1
  2. cumulusci/cli/logger.py +2 -2
  3. cumulusci/cli/service.py +20 -0
  4. cumulusci/cli/task.py +17 -0
  5. cumulusci/cli/tests/test_error.py +3 -1
  6. cumulusci/cli/tests/test_flow.py +279 -2
  7. cumulusci/cli/tests/test_service.py +15 -12
  8. cumulusci/cli/tests/test_task.py +88 -2
  9. cumulusci/cli/tests/utils.py +1 -4
  10. cumulusci/core/config/base_task_flow_config.py +26 -1
  11. cumulusci/core/config/project_config.py +2 -20
  12. cumulusci/core/config/tests/test_config_expensive.py +9 -3
  13. cumulusci/core/config/universal_config.py +3 -4
  14. cumulusci/core/dependencies/base.py +1 -1
  15. cumulusci/core/dependencies/dependencies.py +1 -1
  16. cumulusci/core/dependencies/github.py +1 -2
  17. cumulusci/core/dependencies/resolvers.py +1 -1
  18. cumulusci/core/dependencies/tests/test_dependencies.py +1 -1
  19. cumulusci/core/dependencies/tests/test_resolvers.py +1 -1
  20. cumulusci/core/flowrunner.py +90 -6
  21. cumulusci/core/github.py +1 -1
  22. cumulusci/core/sfdx.py +3 -1
  23. cumulusci/core/source_transforms/tests/test_transforms.py +1 -1
  24. cumulusci/core/source_transforms/transforms.py +1 -1
  25. cumulusci/core/tasks.py +13 -2
  26. cumulusci/core/tests/test_flowrunner.py +100 -0
  27. cumulusci/core/tests/test_tasks.py +65 -0
  28. cumulusci/core/utils.py +3 -1
  29. cumulusci/core/versions.py +1 -1
  30. cumulusci/cumulusci.yml +55 -0
  31. cumulusci/oauth/client.py +1 -1
  32. cumulusci/plugins/plugin_base.py +5 -3
  33. cumulusci/robotframework/pageobjects/ObjectManagerPageObject.py +1 -1
  34. cumulusci/salesforce_api/rest_deploy.py +1 -1
  35. cumulusci/schema/cumulusci.jsonschema.json +64 -0
  36. cumulusci/tasks/apex/anon.py +1 -1
  37. cumulusci/tasks/apex/testrunner.py +416 -142
  38. cumulusci/tasks/apex/tests/test_apex_tasks.py +917 -1
  39. cumulusci/tasks/bulkdata/extract.py +0 -1
  40. cumulusci/tasks/bulkdata/extract_dataset_utils/extract_yml.py +1 -1
  41. cumulusci/tasks/bulkdata/extract_dataset_utils/synthesize_extract_declarations.py +1 -1
  42. cumulusci/tasks/bulkdata/extract_dataset_utils/tests/test_extract_yml.py +1 -1
  43. cumulusci/tasks/bulkdata/generate_and_load_data.py +136 -12
  44. cumulusci/tasks/bulkdata/mapping_parser.py +139 -44
  45. cumulusci/tasks/bulkdata/select_utils.py +1 -1
  46. cumulusci/tasks/bulkdata/snowfakery.py +100 -25
  47. cumulusci/tasks/bulkdata/tests/test_generate_and_load.py +159 -0
  48. cumulusci/tasks/bulkdata/tests/test_load.py +0 -2
  49. cumulusci/tasks/bulkdata/tests/test_mapping_parser.py +763 -1
  50. cumulusci/tasks/bulkdata/tests/test_select_utils.py +26 -0
  51. cumulusci/tasks/bulkdata/tests/test_snowfakery.py +133 -0
  52. cumulusci/tasks/create_package_version.py +190 -16
  53. cumulusci/tasks/datadictionary.py +1 -1
  54. cumulusci/tasks/metadata_etl/base.py +7 -3
  55. cumulusci/tasks/metadata_etl/layouts.py +1 -1
  56. cumulusci/tasks/metadata_etl/permissions.py +1 -1
  57. cumulusci/tasks/metadata_etl/remote_site_settings.py +2 -2
  58. cumulusci/tasks/push/README.md +15 -17
  59. cumulusci/tasks/release_notes/README.md +13 -13
  60. cumulusci/tasks/release_notes/generator.py +13 -8
  61. cumulusci/tasks/robotframework/tests/test_robotframework.py +6 -1
  62. cumulusci/tasks/salesforce/Deploy.py +53 -2
  63. cumulusci/tasks/salesforce/SfPackageCommands.py +363 -0
  64. cumulusci/tasks/salesforce/__init__.py +1 -0
  65. cumulusci/tasks/salesforce/assign_ps_psg.py +448 -0
  66. cumulusci/tasks/salesforce/composite.py +1 -1
  67. cumulusci/tasks/salesforce/custom_settings_wait.py +1 -1
  68. cumulusci/tasks/salesforce/enable_prediction.py +5 -1
  69. cumulusci/tasks/salesforce/getPackageVersion.py +89 -0
  70. cumulusci/tasks/salesforce/sourcetracking.py +1 -1
  71. cumulusci/tasks/salesforce/tests/test_Deploy.py +316 -1
  72. cumulusci/tasks/salesforce/tests/test_SfPackageCommands.py +554 -0
  73. cumulusci/tasks/salesforce/tests/test_assign_ps_psg.py +1055 -0
  74. cumulusci/tasks/salesforce/tests/test_getPackageVersion.py +651 -0
  75. cumulusci/tasks/salesforce/tests/test_update_dependencies.py +1 -1
  76. cumulusci/tasks/salesforce/tests/test_update_external_credential.py +912 -0
  77. cumulusci/tasks/salesforce/tests/test_update_named_credential.py +1042 -0
  78. cumulusci/tasks/salesforce/update_dependencies.py +2 -2
  79. cumulusci/tasks/salesforce/update_external_credential.py +562 -0
  80. cumulusci/tasks/salesforce/update_named_credential.py +441 -0
  81. cumulusci/tasks/salesforce/update_profile.py +17 -13
  82. cumulusci/tasks/salesforce/users/permsets.py +62 -5
  83. cumulusci/tasks/salesforce/users/tests/test_permsets.py +237 -11
  84. cumulusci/tasks/sfdmu/__init__.py +0 -0
  85. cumulusci/tasks/sfdmu/sfdmu.py +363 -0
  86. cumulusci/tasks/sfdmu/tests/__init__.py +1 -0
  87. cumulusci/tasks/sfdmu/tests/test_runner.py +212 -0
  88. cumulusci/tasks/sfdmu/tests/test_sfdmu.py +1012 -0
  89. cumulusci/tasks/tests/test_create_package_version.py +716 -1
  90. cumulusci/tasks/tests/test_util.py +42 -0
  91. cumulusci/tasks/util.py +37 -1
  92. cumulusci/tasks/utility/copyContents.py +402 -0
  93. cumulusci/tasks/utility/credentialManager.py +256 -0
  94. cumulusci/tasks/utility/directoryRecreator.py +30 -0
  95. cumulusci/tasks/utility/env_management.py +1 -1
  96. cumulusci/tasks/utility/secretsToEnv.py +135 -0
  97. cumulusci/tasks/utility/tests/test_copyContents.py +1719 -0
  98. cumulusci/tasks/utility/tests/test_credentialManager.py +564 -0
  99. cumulusci/tasks/utility/tests/test_directoryRecreator.py +439 -0
  100. cumulusci/tasks/utility/tests/test_secretsToEnv.py +1091 -0
  101. cumulusci/tests/test_integration_infrastructure.py +3 -1
  102. cumulusci/tests/test_utils.py +70 -6
  103. cumulusci/utils/__init__.py +54 -9
  104. cumulusci/utils/classutils.py +5 -2
  105. cumulusci/utils/http/tests/cassettes/ManualEditTestCompositeParallelSalesforce.test_http_headers.yaml +31 -30
  106. cumulusci/utils/options.py +23 -1
  107. cumulusci/utils/parallel/task_worker_queues/parallel_worker.py +1 -1
  108. cumulusci/utils/yaml/cumulusci_yml.py +7 -3
  109. cumulusci/utils/yaml/model_parser.py +2 -2
  110. cumulusci/utils/yaml/tests/test_cumulusci_yml.py +1 -1
  111. cumulusci/utils/yaml/tests/test_model_parser.py +3 -3
  112. cumulusci/vcs/base.py +23 -15
  113. cumulusci/vcs/bootstrap.py +5 -4
  114. cumulusci/vcs/utils/list_modified_files.py +189 -0
  115. cumulusci/vcs/utils/tests/test_list_modified_files.py +588 -0
  116. {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.35.dist-info}/METADATA +12 -10
  117. {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.35.dist-info}/RECORD +121 -96
  118. {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.35.dist-info}/WHEEL +0 -0
  119. {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.35.dist-info}/entry_points.txt +0 -0
  120. {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.35.dist-info}/licenses/AUTHORS.rst +0 -0
  121. {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.35.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,189 @@
1
+ """Utility functions and task for working with git modified files."""
2
+
3
+ import os
4
+ import subprocess
5
+ from typing import Optional, Set
6
+
7
+ from cumulusci.core.tasks import BaseTask
8
+ from cumulusci.utils.options import CCIOptions, Field, ListOfStringsOption
9
+
10
+
11
+ class ListModifiedFiles(BaseTask):
12
+ """Task to list modified files in a git repository.
13
+
14
+ This task compares the current working directory against a base reference
15
+ (branch, tag, or commit) and lists all modified files, optionally filtered
16
+ to package directories.
17
+ """
18
+
19
+ class Options(CCIOptions):
20
+ base_ref: Optional[str] = Field(
21
+ None,
22
+ description="Git reference (branch, tag, or commit) to compare against. "
23
+ "If not set, uses the default branch of the repository.",
24
+ )
25
+ file_extensions: Optional[ListOfStringsOption] = Field(
26
+ None,
27
+ description="List of file extensions to extract. If not set, all file extensions are extracted. Example: ['cls', 'flow', 'trigger']",
28
+ )
29
+ directories: ListOfStringsOption = Field(
30
+ ["force-app", "src"],
31
+ description="List of directories to extract. If not set, only the default package directory is extracted. Example: ['force-app', 'src']",
32
+ )
33
+
34
+ parsed_options: Options
35
+
36
+ def _init_options(self, kwargs):
37
+ super(ListModifiedFiles, self)._init_options(kwargs)
38
+
39
+ if self.parsed_options.base_ref is None:
40
+ self.parsed_options.base_ref = (
41
+ self.project_config.project__git__default_branch or "main"
42
+ )
43
+
44
+ def _run_task(self):
45
+ """Run the task to list modified files."""
46
+ self.return_values = {
47
+ "files": set(),
48
+ "file_names": set(),
49
+ }
50
+
51
+ # Check if the current base folder has git
52
+ if self.project_config.get_repo() is None:
53
+ self.logger.info("No git repository found.")
54
+ return
55
+
56
+ # Get changed files
57
+ changed_files = self._get_git_changed_files()
58
+
59
+ if changed_files is None:
60
+ self.logger.warning(
61
+ f"Could not determine git changes against {self.parsed_options.base_ref}."
62
+ )
63
+ return
64
+
65
+ if not changed_files:
66
+ self.logger.info("No files changed.")
67
+ return
68
+
69
+ # Filter to directories if requested
70
+ changed_files = self._filter_package_changed_files(changed_files)
71
+
72
+ if not changed_files:
73
+ self.logger.info(
74
+ f"No changed files found in directories: {', '.join(self.parsed_options.directories)}."
75
+ )
76
+ return
77
+
78
+ self.return_values["files"] = sorted(changed_files)
79
+
80
+ # Extract file names if requested
81
+ file_names = set()
82
+ if self.parsed_options.file_extensions is not None:
83
+ file_names = self._extract_file_names_from_files(changed_files)
84
+ if file_names:
85
+ self.logger.info(
86
+ f"Found {len(file_names)} affected file(s): {', '.join(sorted(file_names))}"
87
+ )
88
+ self.return_values["file_names"] = file_names
89
+ else:
90
+ self.logger.info("No file names found in changed files.")
91
+
92
+ # Log file list if not too many
93
+ if len(changed_files) > 0 and len(changed_files) <= 20:
94
+ self.logger.info("Changed files:")
95
+ for file_path in changed_files:
96
+ self.logger.info(f" {file_path}")
97
+ else:
98
+ self.logger.info(f" ... and {len(changed_files) - 20} more files")
99
+
100
+ if len(file_names) > 0 and len(file_names) <= 20:
101
+ self.logger.info("Selected file names:")
102
+ for file_name in file_names:
103
+ self.logger.info(f" {file_name}")
104
+ else:
105
+ self.logger.info(f" ... and {len(file_names) - 20} more file names")
106
+
107
+ def _get_git_changed_files(self) -> Optional[Set[str]]:
108
+ """Get list of changed files using git diff.
109
+ Returns:
110
+ Set of changed file paths, or None if git command failed
111
+ """
112
+ try:
113
+ result = subprocess.run(
114
+ ["git", "diff", "--name-only", self.parsed_options.base_ref],
115
+ capture_output=True,
116
+ text=True,
117
+ check=False,
118
+ )
119
+
120
+ if result.returncode != 0:
121
+ self.logger.warning(
122
+ f"Git diff failed with return code {result.returncode}: {result.stderr}"
123
+ )
124
+ return None
125
+
126
+ return set([f.strip() for f in result.stdout.splitlines() if f.strip()])
127
+
128
+ except FileNotFoundError:
129
+ self.logger.warning(
130
+ "Git command not found. Cannot determine changed files."
131
+ )
132
+ return set()
133
+ except Exception as e:
134
+ self.logger.warning(f"Error running git diff: {str(e)}")
135
+ return set()
136
+
137
+ def _filter_package_changed_files(self, changed_files: Set[str]) -> Set[str]:
138
+ """Filter changed files to only include those in the package directories."""
139
+ package_dir = os.path.basename(
140
+ os.path.normpath(self.project_config.default_package_path)
141
+ )
142
+
143
+ if package_dir not in self.parsed_options.directories:
144
+ self.parsed_options.directories.append(package_dir)
145
+
146
+ filtered_files = set()
147
+ for file_path in changed_files:
148
+ # Check if file is in any of the package directories
149
+ for pkg_dir in self.parsed_options.directories:
150
+ if file_path.startswith(pkg_dir + "/") or file_path.startswith(
151
+ pkg_dir + "\\"
152
+ ):
153
+ filtered_files.add(file_path)
154
+ break
155
+
156
+ return set(filtered_files)
157
+
158
+ def _extract_file_names_from_files(self, changed_files: Set[str]) -> Set[str]:
159
+ """Extract file names from changed file paths based on specified extensions.
160
+
161
+ Args:
162
+ changed_files: List of changed file paths
163
+
164
+ Returns:
165
+ Set of file names found in changed files matching the specified extensions
166
+ """
167
+ file_names = set()
168
+
169
+ for file_path in changed_files:
170
+ # Check if file path ends with any of the specified extensions
171
+ # Handle both "cls" and ".cls" formats
172
+ matched_extension = None
173
+ for ext in self.parsed_options.file_extensions:
174
+ # Normalize extension to have a dot prefix
175
+ normalized_ext = ext if ext.startswith(".") else f".{ext}"
176
+ if file_path.endswith(normalized_ext):
177
+ matched_extension = normalized_ext
178
+ break
179
+
180
+ if matched_extension:
181
+ # Extract file name from path
182
+ # Examples: force-app/main/default/classes/MyClass.cls -> MyClass
183
+ # src/classes/MyClass.cls -> MyClass
184
+ file_name = os.path.basename(file_path)
185
+ file_name = file_name.replace(matched_extension, "")
186
+ if file_name:
187
+ file_names.add(file_name)
188
+
189
+ return file_names