From 649e30632de625b47744ba9e9384ffc3784f15d0 Mon Sep 17 00:00:00 2001 From: jain-naman-sf Date: Tue, 19 Sep 2023 09:19:06 +0530 Subject: [PATCH 01/11] Adding Functionslity --- cumulusci/cli/org.py | 14 ++++++++++++-- cumulusci/core/config/scratch_org_config.py | 7 +++++++ cumulusci/core/keychain/base_project_keychain.py | 5 ++++- cumulusci/cumulusci.yml | 1 + cumulusci/schema/cumulusci.jsonschema.json | 6 ++++++ cumulusci/utils/yaml/cumulusci_yml.py | 1 + 6 files changed, 31 insertions(+), 3 deletions(-) diff --git a/cumulusci/cli/org.py b/cumulusci/cli/org.py index 4dc5ed3e6f..e16e493ffc 100644 --- a/cumulusci/cli/org.py +++ b/cumulusci/cli/org.py @@ -546,6 +546,7 @@ def org_remove(runtime, org_name, global_org): name="scratch", help="Connects a Salesforce DX Scratch Org to the keychain" ) @click.argument("config_name") +@click.argument("release") @orgname_option_or_argument(required=True) @click.option( "--default", @@ -562,9 +563,18 @@ def org_remove(runtime, org_name, global_org): @click.option( "--no-password", is_flag=True, help="If set, don't set a password for the org" ) +# @click.option( +# "--release",help="If provided specify the release when creating a scratch org" +# ) @pass_runtime(require_keychain=True) -def org_scratch(runtime, config_name, org_name, default, devhub, days, no_password): +def org_scratch( + runtime, config_name, org_name, default, devhub, days, no_password, release +): runtime.check_org_overwrite(org_name) + release_options = ["previous", "preview"] + if release and release not in release_options: + raise click.UsageError("Release options value is not valid.") + # Test for Pydanctic validation of release scratch_configs = runtime.project_config.lookup("orgs__scratch") if not scratch_configs: @@ -579,7 +589,7 @@ def org_scratch(runtime, config_name, org_name, default, devhub, days, no_passwo scratch_config["devhub"] = devhub runtime.keychain.create_scratch_org( - org_name, config_name, days, set_password=not (no_password) + org_name, config_name, days, set_password=not (no_password), release=release ) if default: diff --git a/cumulusci/core/config/scratch_org_config.py b/cumulusci/core/config/scratch_org_config.py index 4f2b5766f2..921d2b0fb0 100644 --- a/cumulusci/core/config/scratch_org_config.py +++ b/cumulusci/core/config/scratch_org_config.py @@ -38,6 +38,10 @@ def scratch_info(self): def days(self) -> int: return self.config.setdefault("days", 1) + @property + def release(self) -> str: + return self.config.setdefault("release", None) + @property def active(self) -> bool: """Check if an org is alive""" @@ -123,6 +127,7 @@ def raise_error() -> NoReturn: def _build_org_create_args(self) -> List[str]: args = ["-f", self.config_file, "-w", "120"] + devhub_username: Optional[str] = self._choose_devhub_username() if devhub_username: args += ["--targetdevhubusername", devhub_username] @@ -132,6 +137,8 @@ def _build_org_create_args(self) -> List[str]: args += ["--noancestors"] if self.days: args += ["--durationdays", str(self.days)] + if self.release: + args += [f"release={self.release}"] if self.sfdx_alias: args += ["-a", self.sfdx_alias] with open(self.config_file, "r") as org_def: diff --git a/cumulusci/core/keychain/base_project_keychain.py b/cumulusci/core/keychain/base_project_keychain.py index 78f40a9035..0ab9a0af8e 100644 --- a/cumulusci/core/keychain/base_project_keychain.py +++ b/cumulusci/core/keychain/base_project_keychain.py @@ -54,7 +54,9 @@ def _validate_key(self): # Orgs # ####################################### - def create_scratch_org(self, org_name, config_name, days=None, set_password=True): + def create_scratch_org( + self, org_name, config_name, days=None, set_password=True, release=None + ): """Adds/Updates a scratch org config to the keychain from a named config""" scratch_config = self.project_config.lookup(f"orgs__scratch__{config_name}") if scratch_config is None: @@ -69,6 +71,7 @@ def create_scratch_org(self, org_name, config_name, days=None, set_password=True scratch_config["scratch"] = True scratch_config.setdefault("namespaced", False) scratch_config["config_name"] = config_name + scratch_config["release"] = release scratch_config[ "sfdx_alias" ] = f"{self.project_config.project__name}__{org_name}" diff --git a/cumulusci/cumulusci.yml b/cumulusci/cumulusci.yml index 57cd1e0220..f8cefe776c 100644 --- a/cumulusci/cumulusci.yml +++ b/cumulusci/cumulusci.yml @@ -641,6 +641,7 @@ tasks: options: name: Release production: True + namespace: foodPackage group: Release Operations upload_user_profile_photo: group: Salesforce Users diff --git a/cumulusci/schema/cumulusci.jsonschema.json b/cumulusci/schema/cumulusci.jsonschema.json index 7d53f2c257..0331eb3c3f 100644 --- a/cumulusci/schema/cumulusci.jsonschema.json +++ b/cumulusci/schema/cumulusci.jsonschema.json @@ -430,6 +430,12 @@ "noancestors": { "title": "Noancestors", "type": "boolean" + }, + "release": { + "title": "Release", + "default": "previous", + "enum": ["previous", "preview"], + "type": "string" } }, "additionalProperties": false diff --git a/cumulusci/utils/yaml/cumulusci_yml.py b/cumulusci/utils/yaml/cumulusci_yml.py index a85cee77bd..d1b9851164 100644 --- a/cumulusci/utils/yaml/cumulusci_yml.py +++ b/cumulusci/utils/yaml/cumulusci_yml.py @@ -150,6 +150,7 @@ class ScratchOrg(CCIDictModel): namespaced: str = None setup_flow: str = None noancestors: bool = None + release: Literal["previous", "preview"] = "previous" class Orgs(CCIDictModel): From 1c07efbddc206ee53c99a4b7819e7aeb2f043edc Mon Sep 17 00:00:00 2001 From: jain-naman-sf Date: Tue, 19 Sep 2023 11:28:40 +0530 Subject: [PATCH 02/11] Wrote Tests --- cumulusci/cli/org.py | 8 +++----- cumulusci/cli/tests/test_org.py | 23 ++++++++++++++++++++++- cumulusci/utils/yaml/cumulusci_yml.py | 2 +- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/cumulusci/cli/org.py b/cumulusci/cli/org.py index e16e493ffc..dd5ebb133a 100644 --- a/cumulusci/cli/org.py +++ b/cumulusci/cli/org.py @@ -546,7 +546,6 @@ def org_remove(runtime, org_name, global_org): name="scratch", help="Connects a Salesforce DX Scratch Org to the keychain" ) @click.argument("config_name") -@click.argument("release") @orgname_option_or_argument(required=True) @click.option( "--default", @@ -563,9 +562,9 @@ def org_remove(runtime, org_name, global_org): @click.option( "--no-password", is_flag=True, help="If set, don't set a password for the org" ) -# @click.option( -# "--release",help="If provided specify the release when creating a scratch org" -# ) +@click.option( + "--release", help="If provided specify the release when creating a scratch org" +) @pass_runtime(require_keychain=True) def org_scratch( runtime, config_name, org_name, default, devhub, days, no_password, release @@ -574,7 +573,6 @@ def org_scratch( release_options = ["previous", "preview"] if release and release not in release_options: raise click.UsageError("Release options value is not valid.") - # Test for Pydanctic validation of release scratch_configs = runtime.project_config.lookup("orgs__scratch") if not scratch_configs: diff --git a/cumulusci/cli/tests/test_org.py b/cumulusci/cli/tests/test_org.py index 01692e22a8..5b55b66f8f 100644 --- a/cumulusci/cli/tests/test_org.py +++ b/cumulusci/cli/tests/test_org.py @@ -1247,14 +1247,35 @@ def test_org_scratch(self): devhub="hub", days=7, no_password=True, + release="previous", ) runtime.check_org_overwrite.assert_called_once() runtime.keychain.create_scratch_org.assert_called_with( - "test", "dev", 7, set_password=False + "test", "dev", 7, set_password=False, release="previous" ) runtime.keychain.set_default_org.assert_called_with("test") + def test_org_scratch_release_invalid(self): + runtime = mock.Mock() + + runtime.project_config.lookup = MockLookup( + orgs__scratch={"dev": {"orgName": "Dev"}} + ) + with pytest.raises(click.UsageError): + run_click_command( + org.org_scratch, + runtime=runtime, + config_name="dev", + org_name="test", + default=True, + devhub="hub", + days=7, + no_password=True, + release="next", + ) + runtime.check_org_overwrite.assert_called_once() + def test_org_scratch__not_default(self): runtime = mock.Mock() runtime.project_config.lookup = MockLookup( diff --git a/cumulusci/utils/yaml/cumulusci_yml.py b/cumulusci/utils/yaml/cumulusci_yml.py index d1b9851164..2a2a63bc86 100644 --- a/cumulusci/utils/yaml/cumulusci_yml.py +++ b/cumulusci/utils/yaml/cumulusci_yml.py @@ -150,7 +150,7 @@ class ScratchOrg(CCIDictModel): namespaced: str = None setup_flow: str = None noancestors: bool = None - release: Literal["previous", "preview"] = "previous" + # release: Literal["previous", "preview"] = "previous" class Orgs(CCIDictModel): From 0047bfb41c62e6e925139cf8dd7786cf039f824d Mon Sep 17 00:00:00 2001 From: jain-naman-sf Date: Tue, 19 Sep 2023 14:30:49 +0530 Subject: [PATCH 03/11] Test Fixes --- cumulusci/cli/tests/test_org.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cumulusci/cli/tests/test_org.py b/cumulusci/cli/tests/test_org.py index 5b55b66f8f..6b2249fd62 100644 --- a/cumulusci/cli/tests/test_org.py +++ b/cumulusci/cli/tests/test_org.py @@ -1291,11 +1291,12 @@ def test_org_scratch__not_default(self): devhub="hub", days=7, no_password=True, + release="previous", ) runtime.check_org_overwrite.assert_called_once() runtime.keychain.create_scratch_org.assert_called_with( - "test", "dev", 7, set_password=False + "test", "dev", 7, set_password=False, release="previous" ) def test_org_scratch_no_configs(self): @@ -1312,6 +1313,7 @@ def test_org_scratch_no_configs(self): devhub="hub", days=7, no_password=True, + release="previous", ) def test_org_scratch_config_not_found(self): @@ -1328,6 +1330,7 @@ def test_org_scratch_config_not_found(self): devhub="hub", days=7, no_password=True, + release="previous", ) def test_org_scratch_delete(self): From 6c6025280324dc35e4dc4713b6f46c6f5241b2ca Mon Sep 17 00:00:00 2001 From: jain-naman-sf Date: Tue, 19 Sep 2023 16:40:39 +0530 Subject: [PATCH 04/11] Cleanup --- cumulusci/cli/org.py | 2 +- cumulusci/cli/tests/test_org.py | 4 ++-- cumulusci/core/config/scratch_org_config.py | 1 - cumulusci/cumulusci.yml | 1 - cumulusci/utils/yaml/cumulusci_yml.py | 3 +-- 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/cumulusci/cli/org.py b/cumulusci/cli/org.py index dd5ebb133a..0df0644f24 100644 --- a/cumulusci/cli/org.py +++ b/cumulusci/cli/org.py @@ -563,7 +563,7 @@ def org_remove(runtime, org_name, global_org): "--no-password", is_flag=True, help="If set, don't set a password for the org" ) @click.option( - "--release", help="If provided specify the release when creating a scratch org" + "--release", help="If provided, specify either previous or preview when creating a scratch org" ) @pass_runtime(require_keychain=True) def org_scratch( diff --git a/cumulusci/cli/tests/test_org.py b/cumulusci/cli/tests/test_org.py index 6b2249fd62..8a83998890 100644 --- a/cumulusci/cli/tests/test_org.py +++ b/cumulusci/cli/tests/test_org.py @@ -1291,12 +1291,12 @@ def test_org_scratch__not_default(self): devhub="hub", days=7, no_password=True, - release="previous", + release=None, ) runtime.check_org_overwrite.assert_called_once() runtime.keychain.create_scratch_org.assert_called_with( - "test", "dev", 7, set_password=False, release="previous" + "test", "dev", 7, set_password=False, release=None ) def test_org_scratch_no_configs(self): diff --git a/cumulusci/core/config/scratch_org_config.py b/cumulusci/core/config/scratch_org_config.py index 921d2b0fb0..17a845fe63 100644 --- a/cumulusci/core/config/scratch_org_config.py +++ b/cumulusci/core/config/scratch_org_config.py @@ -127,7 +127,6 @@ def raise_error() -> NoReturn: def _build_org_create_args(self) -> List[str]: args = ["-f", self.config_file, "-w", "120"] - devhub_username: Optional[str] = self._choose_devhub_username() if devhub_username: args += ["--targetdevhubusername", devhub_username] diff --git a/cumulusci/cumulusci.yml b/cumulusci/cumulusci.yml index f8cefe776c..57cd1e0220 100644 --- a/cumulusci/cumulusci.yml +++ b/cumulusci/cumulusci.yml @@ -641,7 +641,6 @@ tasks: options: name: Release production: True - namespace: foodPackage group: Release Operations upload_user_profile_photo: group: Salesforce Users diff --git a/cumulusci/utils/yaml/cumulusci_yml.py b/cumulusci/utils/yaml/cumulusci_yml.py index 2a2a63bc86..b0aa16d897 100644 --- a/cumulusci/utils/yaml/cumulusci_yml.py +++ b/cumulusci/utils/yaml/cumulusci_yml.py @@ -149,8 +149,7 @@ class ScratchOrg(CCIDictModel): days: int = None namespaced: str = None setup_flow: str = None - noancestors: bool = None - # release: Literal["previous", "preview"] = "previous" + noancestors: bool = None class Orgs(CCIDictModel): From 6879a22b40403a31776668463630c79b90389f6c Mon Sep 17 00:00:00 2001 From: jain-naman-sf Date: Tue, 19 Sep 2023 16:47:01 +0530 Subject: [PATCH 05/11] Cleanup --- cumulusci/cli/org.py | 4 +++- cumulusci/utils/yaml/cumulusci_yml.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cumulusci/cli/org.py b/cumulusci/cli/org.py index 0df0644f24..0340ff6728 100644 --- a/cumulusci/cli/org.py +++ b/cumulusci/cli/org.py @@ -563,13 +563,15 @@ def org_remove(runtime, org_name, global_org): "--no-password", is_flag=True, help="If set, don't set a password for the org" ) @click.option( - "--release", help="If provided, specify either previous or preview when creating a scratch org" + "--release", + help="If provided, specify either previous or preview when creating a scratch org", ) @pass_runtime(require_keychain=True) def org_scratch( runtime, config_name, org_name, default, devhub, days, no_password, release ): runtime.check_org_overwrite(org_name) + release_options = ["previous", "preview"] if release and release not in release_options: raise click.UsageError("Release options value is not valid.") diff --git a/cumulusci/utils/yaml/cumulusci_yml.py b/cumulusci/utils/yaml/cumulusci_yml.py index b0aa16d897..a85cee77bd 100644 --- a/cumulusci/utils/yaml/cumulusci_yml.py +++ b/cumulusci/utils/yaml/cumulusci_yml.py @@ -149,7 +149,7 @@ class ScratchOrg(CCIDictModel): days: int = None namespaced: str = None setup_flow: str = None - noancestors: bool = None + noancestors: bool = None class Orgs(CCIDictModel): From cb511004481341002d92638605d371904543c1e0 Mon Sep 17 00:00:00 2001 From: jain-naman-sf Date: Tue, 19 Sep 2023 16:49:26 +0530 Subject: [PATCH 06/11] Cleanup --- cumulusci/schema/cumulusci.jsonschema.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cumulusci/schema/cumulusci.jsonschema.json b/cumulusci/schema/cumulusci.jsonschema.json index 0331eb3c3f..7d53f2c257 100644 --- a/cumulusci/schema/cumulusci.jsonschema.json +++ b/cumulusci/schema/cumulusci.jsonschema.json @@ -430,12 +430,6 @@ "noancestors": { "title": "Noancestors", "type": "boolean" - }, - "release": { - "title": "Release", - "default": "previous", - "enum": ["previous", "preview"], - "type": "string" } }, "additionalProperties": false From 27c7874501e3d1273705c4938ef2af7dd406a03b Mon Sep 17 00:00:00 2001 From: jain-naman-sf Date: Tue, 19 Sep 2023 16:55:37 +0530 Subject: [PATCH 07/11] Cleamup --- cumulusci/cli/org.py | 1 - cumulusci/cli/tests/test_org.py | 1 - cumulusci/core/keychain/base_project_keychain.py | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/cumulusci/cli/org.py b/cumulusci/cli/org.py index 0340ff6728..3d7514d348 100644 --- a/cumulusci/cli/org.py +++ b/cumulusci/cli/org.py @@ -571,7 +571,6 @@ def org_scratch( runtime, config_name, org_name, default, devhub, days, no_password, release ): runtime.check_org_overwrite(org_name) - release_options = ["previous", "preview"] if release and release not in release_options: raise click.UsageError("Release options value is not valid.") diff --git a/cumulusci/cli/tests/test_org.py b/cumulusci/cli/tests/test_org.py index 8a83998890..bf9656b7cc 100644 --- a/cumulusci/cli/tests/test_org.py +++ b/cumulusci/cli/tests/test_org.py @@ -1281,7 +1281,6 @@ def test_org_scratch__not_default(self): runtime.project_config.lookup = MockLookup( orgs__scratch={"dev": {"orgName": "Dev"}} ) - run_click_command( org.org_scratch, runtime=runtime, diff --git a/cumulusci/core/keychain/base_project_keychain.py b/cumulusci/core/keychain/base_project_keychain.py index 0ab9a0af8e..92b2a1278d 100644 --- a/cumulusci/core/keychain/base_project_keychain.py +++ b/cumulusci/core/keychain/base_project_keychain.py @@ -78,6 +78,7 @@ def create_scratch_org( org_config = ScratchOrgConfig( scratch_config, org_name, keychain=self, global_org=False ) + org_config.save() def set_org(self, org_config, global_org=False, save=True): From dabc98f4016c13f619a0c55c4d9582bf23477070 Mon Sep 17 00:00:00 2001 From: jain-naman-sf Date: Wed, 20 Sep 2023 12:23:58 +0530 Subject: [PATCH 08/11] CleanUp --- cumulusci/core/config/scratch_org_config.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cumulusci/core/config/scratch_org_config.py b/cumulusci/core/config/scratch_org_config.py index 17a845fe63..2e4eab967f 100644 --- a/cumulusci/core/config/scratch_org_config.py +++ b/cumulusci/core/config/scratch_org_config.py @@ -23,6 +23,7 @@ class ScratchOrgConfig(SfdxOrgConfig): instance: str password_failed: bool devhub: str + release: str createable: bool = True @@ -38,10 +39,6 @@ def scratch_info(self): def days(self) -> int: return self.config.setdefault("days", 1) - @property - def release(self) -> str: - return self.config.setdefault("release", None) - @property def active(self) -> bool: """Check if an org is alive""" From 41883e293bc71edb8db0d0e01de38f6cff499af5 Mon Sep 17 00:00:00 2001 From: jain-naman-sf Date: Thu, 21 Sep 2023 12:49:59 +0530 Subject: [PATCH 09/11] Added Pydantic Validator to cumulusci.yml --- cumulusci/core/keychain/base_project_keychain.py | 4 +++- cumulusci/schema/cumulusci.jsonschema.json | 5 +++++ cumulusci/utils/yaml/cumulusci_yml.py | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/cumulusci/core/keychain/base_project_keychain.py b/cumulusci/core/keychain/base_project_keychain.py index 92b2a1278d..561caeaec7 100644 --- a/cumulusci/core/keychain/base_project_keychain.py +++ b/cumulusci/core/keychain/base_project_keychain.py @@ -67,11 +67,13 @@ def create_scratch_org( else: # Use scratch config days or default of 1 day scratch_config.setdefault("days", 1) + if release is not None: + scratch_config["release"] = release scratch_config["set_password"] = bool(set_password) scratch_config["scratch"] = True scratch_config.setdefault("namespaced", False) scratch_config["config_name"] = config_name - scratch_config["release"] = release + scratch_config[ "sfdx_alias" ] = f"{self.project_config.project__name}__{org_name}" diff --git a/cumulusci/schema/cumulusci.jsonschema.json b/cumulusci/schema/cumulusci.jsonschema.json index 7d53f2c257..255d2de4d4 100644 --- a/cumulusci/schema/cumulusci.jsonschema.json +++ b/cumulusci/schema/cumulusci.jsonschema.json @@ -430,6 +430,11 @@ "noancestors": { "title": "Noancestors", "type": "boolean" + }, + "release": { + "title": "Release", + "enum": ["preview", "previous"], + "type": "string" } }, "additionalProperties": false diff --git a/cumulusci/utils/yaml/cumulusci_yml.py b/cumulusci/utils/yaml/cumulusci_yml.py index a85cee77bd..f8498ed9ea 100644 --- a/cumulusci/utils/yaml/cumulusci_yml.py +++ b/cumulusci/utils/yaml/cumulusci_yml.py @@ -150,6 +150,7 @@ class ScratchOrg(CCIDictModel): namespaced: str = None setup_flow: str = None noancestors: bool = None + release: Literal["preview", "previous"] = None class Orgs(CCIDictModel): From c0fe9eef9ca4955b21253efd149a111dbd8d22be Mon Sep 17 00:00:00 2001 From: jain-naman-sf Date: Thu, 21 Sep 2023 20:42:09 +0530 Subject: [PATCH 10/11] Added Tests for remaining files --- cumulusci/core/config/tests/test_config_expensive.py | 2 ++ cumulusci/core/keychain/tests/test_base_project_keychain.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cumulusci/core/config/tests/test_config_expensive.py b/cumulusci/core/config/tests/test_config_expensive.py index c1ea451b06..c83a9b17d7 100644 --- a/cumulusci/core/config/tests/test_config_expensive.py +++ b/cumulusci/core/config/tests/test_config_expensive.py @@ -770,6 +770,7 @@ def test_build_org_create_args(self, scratch_def_file): "sfdx_alias": "project__org", "default": True, "instance": "NA01", + "release": "previous", }, "test", mock_keychain, @@ -786,6 +787,7 @@ def test_build_org_create_args(self, scratch_def_file): "--noancestors", "--durationdays", "1", + "release=previous", "-a", "project__org", "adminEmail=test@example.com", diff --git a/cumulusci/core/keychain/tests/test_base_project_keychain.py b/cumulusci/core/keychain/tests/test_base_project_keychain.py index 06e9757fdc..89cc12318a 100644 --- a/cumulusci/core/keychain/tests/test_base_project_keychain.py +++ b/cumulusci/core/keychain/tests/test_base_project_keychain.py @@ -152,9 +152,10 @@ def test_create_scratch_org(self, key): ) keychain = BaseProjectKeychain(project_config, key) keychain.key = key - keychain.create_scratch_org("test", "dev", days=3) + keychain.create_scratch_org("test", "dev", days=3, release="previous") org_config = keychain.get_org("test").config assert org_config["days"] == 3 + assert org_config["release"] == "previous" def test_load_scratch_orgs(self, keychain): assert list(keychain.orgs) == [] From 8caa3abb8267a1927d598847e7de00f4385a4e00 Mon Sep 17 00:00:00 2001 From: jain-naman-sf Date: Mon, 9 Oct 2023 12:48:55 +0530 Subject: [PATCH 11/11] Changed Error Message --- cumulusci/cli/org.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cumulusci/cli/org.py b/cumulusci/cli/org.py index 3d7514d348..1f8399c76e 100644 --- a/cumulusci/cli/org.py +++ b/cumulusci/cli/org.py @@ -573,7 +573,9 @@ def org_scratch( runtime.check_org_overwrite(org_name) release_options = ["previous", "preview"] if release and release not in release_options: - raise click.UsageError("Release options value is not valid.") + raise click.UsageError( + "Release options value is not valid. Either specify preview or previous." + ) scratch_configs = runtime.project_config.lookup("orgs__scratch") if not scratch_configs: