Repository: SFDO-Tooling/CumulusCI
Pull request #3676 (2023-10-12 07:48:52)

Add clear error message when `create_package_version` run against persistent orgs
The `create_package_version` task throws a `TypeError` exception and shows an unhelpful error message if run against persistent orgs:

```
TypeError: expected str, bytes or os.PathLike object, not NoneType
throws at create_package_version.py(366)_create_version_request()
-> with open(self.org_config.config_file, "r") as f:
```

This fix will detect the error scenario and display a better error message.


Patch:
diff --git a/cumulusci/cli/org.py b/cumulusci/cli/org.py
index e26df3d09..3d2d08fc7 100644
--- a/cumulusci/cli/org.py
+++ b/cumulusci/cli/org.py
@@ -569,7 +569,7 @@ def org_remove(runtime, org_name, global_org):
 )
 @pass_runtime(require_keychain=True)
 def org_scratch(
-    runtime, config_name, org_name, default, devhub, days, no_password, release
+    runtime, config_name, org_name, default, devhub, days, no_password, release=None
 ):
     runtime.check_org_overwrite(org_name)
     release_options = ["previous", "preview"]
diff --git a/cumulusci/tasks/create_package_version.py b/cumulusci/tasks/create_package_version.py
index ac958df95..b4d5a264a 100644
--- a/cumulusci/tasks/create_package_version.py
+++ b/cumulusci/tasks/create_package_version.py
@@ -41,6 +41,10 @@ from cumulusci.utils.salesforce.soql import (
     format_subscriber_package_version_where_clause,
 )
 
+PERSISTENT_ORG_ERROR = """
+Target org scratch org definition file missing. Persistent orgs like a Dev Hub can't be used for 2GP package uploads.
+"""
+
 
 class PackageTypeEnum(StrEnum):
     managed = "Managed"
@@ -84,6 +88,15 @@ class CreatePackageVersion(BaseSalesforceApiTask):
     If a package named ``package_name`` does not yet exist in the Dev Hub, it will be created.
     """
 
+    task_docs = """
+    Facilitates the upload of 2GP (second-generation packaging)
+    package versions using CumulusCI.
+
+    The target org is used both for looking up dependency package IDs and
+    configuring the build org during the package upload. Ensure the specified
+    org is a scratch org with the correct configuration for these purposes.
+    """
+
     api_version = "52.0"
 
     task_options = {
@@ -144,6 +157,9 @@ class CreatePackageVersion(BaseSalesforceApiTask):
     def _init_options(self, kwargs):
         super()._init_options(kwargs)
 
+        if not self.org_config.config_file:
+            raise TaskOptionsError(PERSISTENT_ORG_ERROR)
+
         # Allow these fields to be explicitly set to blanks
         # so that unlocked builds can override an otherwise-configured
         # postinstall script
@@ -388,7 +404,6 @@ class CreatePackageVersion(BaseSalesforceApiTask):
             # Add org shape
             with open(self.org_config.config_file, "r") as f:
                 scratch_org_def = json.load(f)
-
             # See https://github.com/forcedotcom/packaging/blob/main/src/package/packageVersionCreate.ts#L358
             # Note that we handle orgPreferences below by converting to settings,
             # in build_settings_package()


Test patch:
diff --git a/cumulusci/tasks/tests/test_create_package_version.py b/cumulusci/tasks/tests/test_create_package_version.py
index df901a35c..32057828f 100644
--- a/cumulusci/tasks/tests/test_create_package_version.py
+++ b/cumulusci/tasks/tests/test_create_package_version.py
@@ -28,6 +28,7 @@ from cumulusci.core.exceptions import (
 from cumulusci.core.keychain import BaseProjectKeychain
 from cumulusci.salesforce_api.package_zip import BasePackageZipBuilder
 from cumulusci.tasks.create_package_version import (
+    PERSISTENT_ORG_ERROR,
     CreatePackageVersion,
     PackageConfig,
     PackageTypeEnum,
@@ -153,6 +154,14 @@ def mock_get_static_dependencies():
 
 
 class TestPackageConfig:
+    def test_org_config(self, project_config, org_config):
+        org_config.config_file = None
+        with pytest.raises(
+            TaskOptionsError,
+            match=PERSISTENT_ORG_ERROR,
+        ):
+            CreatePackageVersion(project_config, TaskConfig(), org_config)
+
     def test_validate_org_dependent(self):
         with pytest.raises(ValidationError, match="Only unlocked packages"):
             PackageConfig(package_type=PackageTypeEnum.managed, org_dependent=True)  # type: ignore