Skip to content

Commit 1c68976

Browse files
authored
add missing unpackaged metadata option (#3991)
Add support for the Salesforce "unpackaged Metadata" option when creating package versions. https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_dev2gp_unpackaged_md.htm Fixes #3989
1 parent bd76695 commit 1c68976

2 files changed

Lines changed: 135 additions & 1 deletion

File tree

cumulusci/tasks/create_package_version.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,11 @@ class CreatePackageVersion(BaseSalesforceApiTask):
150150
"description": "If True, create unlocked packages for unpackaged metadata in this project and dependencies. "
151151
"Defaults to False."
152152
},
153+
"unpackaged_metadata_path": {
154+
"description": "Path to a directory of metadata to include as unpackaged metadata "
155+
"in the package version. This metadata is deployed to the build org during package "
156+
"version creation but is not included in the package itself."
157+
},
153158
}
154159

155160
def _init_options(self, kwargs):
@@ -194,6 +199,14 @@ def _init_options(self, kwargs):
194199
self.options["create_unlocked_dependency_packages"] = process_bool_arg(
195200
self.options.get("create_unlocked_dependency_packages") or False
196201
)
202+
if "unpackaged_metadata_path" in self.options:
203+
unpackaged_metadata_path = pathlib.Path(
204+
self.options["unpackaged_metadata_path"]
205+
)
206+
if not unpackaged_metadata_path.is_dir():
207+
raise TaskOptionsError(
208+
f"unpackaged_metadata_path does not exist or is not a directory: {unpackaged_metadata_path}"
209+
)
197210

198211
def _init_task(self):
199212
self.tooling = get_simple_salesforce_connection(
@@ -429,8 +442,27 @@ def _create_version_request(
429442
"settings.zip", settings_zip_builder.as_bytes()
430443
)
431444

432-
# Add the dependencies for the package
445+
# Add unpackaged metadata if provided (main package only)
433446
is_dependency = package_config is not self.package_config
447+
if "unpackaged_metadata_path" in self.options and not is_dependency:
448+
with convert_sfdx_source(
449+
pathlib.Path(self.options["unpackaged_metadata_path"]),
450+
None,
451+
self.logger,
452+
) as unpackaged_path:
453+
unpackaged_metadata_zip_builder = MetadataPackageZipBuilder(
454+
path=unpackaged_path,
455+
context=self.context,
456+
)
457+
version_info.writestr(
458+
"unpackaged-metadata.zip",
459+
unpackaged_metadata_zip_builder.as_bytes(),
460+
)
461+
package_descriptor["unpackagedMetadata"] = {
462+
"path": "unpackaged-metadata.zip"
463+
}
464+
465+
# Add the dependencies for the package
434466
if (
435467
not (package_config.org_dependent or skip_validation)
436468
and not is_dependency

cumulusci/tasks/tests/test_create_package_version.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import base64
12
import io
23
import json
34
import os
@@ -181,6 +182,22 @@ def test_validate_uninstall_script(self):
181182
package_type=PackageTypeEnum.unlocked, uninstall_script="Uninstall"
182183
) # type: ignore
183184

185+
def test_unpackaged_metadata_path__missing(self, project_config, org_config):
186+
with pytest.raises(TaskOptionsError, match="unpackaged_metadata_path"):
187+
CreatePackageVersion(
188+
project_config,
189+
TaskConfig(
190+
{
191+
"options": {
192+
"package_type": "Managed",
193+
"package_name": "Test Package",
194+
"unpackaged_metadata_path": "/nonexistent/path",
195+
}
196+
}
197+
),
198+
org_config,
199+
)
200+
184201

185202
class TestCreatePackageVersion:
186203
devhub_base_url = (
@@ -831,3 +848,88 @@ def test_prepare_cci_dependencies(self, task):
831848
assert task._prepare_cci_dependencies(
832849
{"ids": [{"subscriberPackageVersionId": "04t000000000000"}]}
833850
) == [{"version_id": "04t000000000000"}]
851+
852+
@responses.activate
853+
def test_create_version_request__with_unpackaged_metadata(self, get_task, tmp_path):
854+
"""unpackaged-metadata.zip and unpackagedMetadata descriptor key are included."""
855+
unpackaged_path = tmp_path / "unpackaged_metadata"
856+
unpackaged_path.mkdir()
857+
(unpackaged_path / "package.xml").write_text(
858+
'<?xml version="1.0" encoding="utf-8"?>'
859+
'<Package xmlns="http://soap.sforce.com/2006/04/metadata"></Package>'
860+
)
861+
862+
task = get_task(
863+
{
864+
"package_type": "Managed",
865+
"package_name": "Test Package",
866+
"unpackaged_metadata_path": str(unpackaged_path),
867+
}
868+
)
869+
870+
responses.add( # query for existing Package2VersionCreateRequest
871+
"GET",
872+
f"{self.devhub_base_url}/tooling/query/",
873+
json={"size": 0, "records": []},
874+
)
875+
responses.add( # query for base version number
876+
"GET",
877+
f"{self.devhub_base_url}/tooling/query/",
878+
json={"size": 0, "records": []},
879+
)
880+
responses.add( # create Package2VersionCreateRequest
881+
"POST",
882+
f"{self.devhub_base_url}/tooling/sobjects/Package2VersionCreateRequest/",
883+
json={"id": "08c000000000001AAA"},
884+
)
885+
886+
with mock.patch.object(task, "_get_dependencies", return_value=[]):
887+
result = task._create_version_request(
888+
"0Ho6g000000fy4ZCAQ",
889+
task.package_config,
890+
BasePackageZipBuilder(),
891+
)
892+
assert result == "08c000000000001AAA"
893+
894+
post_body = json.loads(responses.calls[-1].request.body)
895+
version_info_zip = zipfile.ZipFile(
896+
io.BytesIO(base64.b64decode(post_body["VersionInfo"]))
897+
)
898+
assert "unpackaged-metadata.zip" in version_info_zip.namelist()
899+
descriptor = json.loads(version_info_zip.read("package2-descriptor.json"))
900+
assert descriptor["unpackagedMetadata"] == {"path": "unpackaged-metadata.zip"}
901+
902+
@responses.activate
903+
def test_create_version_request__without_unpackaged_metadata(self, task):
904+
"""unpackaged-metadata.zip is absent when the option is not set."""
905+
responses.add(
906+
"GET",
907+
f"{self.devhub_base_url}/tooling/query/",
908+
json={"size": 0, "records": []},
909+
)
910+
responses.add(
911+
"GET",
912+
f"{self.devhub_base_url}/tooling/query/",
913+
json={"size": 0, "records": []},
914+
)
915+
responses.add(
916+
"POST",
917+
f"{self.devhub_base_url}/tooling/sobjects/Package2VersionCreateRequest/",
918+
json={"id": "08c000000000001AAA"},
919+
)
920+
921+
with mock.patch.object(task, "_get_dependencies", return_value=[]):
922+
result = task._create_version_request(
923+
"0Ho6g000000fy4ZCAQ",
924+
task.package_config,
925+
BasePackageZipBuilder(),
926+
)
927+
assert result == "08c000000000001AAA"
928+
929+
post_body = json.loads(responses.calls[-1].request.body)
930+
version_info_zip = zipfile.ZipFile(
931+
io.BytesIO(base64.b64decode(post_body["VersionInfo"]))
932+
)
933+
assert "unpackaged-metadata.zip" not in version_info_zip.namelist()
934+
descriptor = json.loads(version_info_zip.read("package2-descriptor.json"))
935+
assert "unpackagedMetadata" not in descriptor

0 commit comments

Comments
 (0)