workato-platform-cli 1.0.0rc5.dev5__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.
- workato_platform_cli/__init__.py +135 -0
- workato_platform_cli/_version.py +34 -0
- workato_platform_cli/cli/__init__.py +126 -0
- workato_platform_cli/cli/commands/__init__.py +0 -0
- workato_platform_cli/cli/commands/api_clients.py +627 -0
- workato_platform_cli/cli/commands/api_collections.py +497 -0
- workato_platform_cli/cli/commands/assets.py +82 -0
- workato_platform_cli/cli/commands/connections.py +1205 -0
- workato_platform_cli/cli/commands/connectors/__init__.py +0 -0
- workato_platform_cli/cli/commands/connectors/command.py +178 -0
- workato_platform_cli/cli/commands/connectors/connector_manager.py +351 -0
- workato_platform_cli/cli/commands/data_tables.py +345 -0
- workato_platform_cli/cli/commands/guide.py +315 -0
- workato_platform_cli/cli/commands/init.py +229 -0
- workato_platform_cli/cli/commands/profiles.py +364 -0
- workato_platform_cli/cli/commands/projects/__init__.py +0 -0
- workato_platform_cli/cli/commands/projects/command.py +513 -0
- workato_platform_cli/cli/commands/projects/project_manager.py +338 -0
- workato_platform_cli/cli/commands/properties.py +174 -0
- workato_platform_cli/cli/commands/pull.py +327 -0
- workato_platform_cli/cli/commands/push/__init__.py +0 -0
- workato_platform_cli/cli/commands/push/command.py +320 -0
- workato_platform_cli/cli/commands/recipes/__init__.py +0 -0
- workato_platform_cli/cli/commands/recipes/command.py +847 -0
- workato_platform_cli/cli/commands/recipes/validator.py +1740 -0
- workato_platform_cli/cli/commands/workspace.py +73 -0
- workato_platform_cli/cli/containers.py +80 -0
- workato_platform_cli/cli/resources/data/connection-data.json +7364 -0
- workato_platform_cli/cli/resources/data/picklist-data.json +3706 -0
- workato_platform_cli/cli/resources/docs/README.md +178 -0
- workato_platform_cli/cli/resources/docs/actions.md +452 -0
- workato_platform_cli/cli/resources/docs/block-structure.md +424 -0
- workato_platform_cli/cli/resources/docs/connections-parameters.md +11946 -0
- workato_platform_cli/cli/resources/docs/data-mapping.md +779 -0
- workato_platform_cli/cli/resources/docs/formulas/array-list-formulas.md +1276 -0
- workato_platform_cli/cli/resources/docs/formulas/conditions.md +102 -0
- workato_platform_cli/cli/resources/docs/formulas/date-formulas.md +798 -0
- workato_platform_cli/cli/resources/docs/formulas/number-formulas.md +507 -0
- workato_platform_cli/cli/resources/docs/formulas/other-formulas.md +419 -0
- workato_platform_cli/cli/resources/docs/formulas/string-formulas.md +1353 -0
- workato_platform_cli/cli/resources/docs/formulas.md +214 -0
- workato_platform_cli/cli/resources/docs/naming-conventions.md +163 -0
- workato_platform_cli/cli/resources/docs/recipe-deployment-workflow.md +352 -0
- workato_platform_cli/cli/resources/docs/recipe-fundamentals.md +179 -0
- workato_platform_cli/cli/resources/docs/triggers.md +360 -0
- workato_platform_cli/cli/utils/__init__.py +10 -0
- workato_platform_cli/cli/utils/config/__init__.py +33 -0
- workato_platform_cli/cli/utils/config/manager.py +1001 -0
- workato_platform_cli/cli/utils/config/models.py +89 -0
- workato_platform_cli/cli/utils/config/profiles.py +491 -0
- workato_platform_cli/cli/utils/config/workspace.py +113 -0
- workato_platform_cli/cli/utils/exception_handler.py +531 -0
- workato_platform_cli/cli/utils/gitignore.py +32 -0
- workato_platform_cli/cli/utils/ignore_patterns.py +44 -0
- workato_platform_cli/cli/utils/spinner.py +63 -0
- workato_platform_cli/cli/utils/version_checker.py +237 -0
- workato_platform_cli/client/__init__.py +0 -0
- workato_platform_cli/client/workato_api/__init__.py +202 -0
- workato_platform_cli/client/workato_api/api/__init__.py +15 -0
- workato_platform_cli/client/workato_api/api/api_platform_api.py +2875 -0
- workato_platform_cli/client/workato_api/api/connections_api.py +1807 -0
- workato_platform_cli/client/workato_api/api/connectors_api.py +840 -0
- workato_platform_cli/client/workato_api/api/data_tables_api.py +604 -0
- workato_platform_cli/client/workato_api/api/export_api.py +621 -0
- workato_platform_cli/client/workato_api/api/folders_api.py +621 -0
- workato_platform_cli/client/workato_api/api/packages_api.py +1197 -0
- workato_platform_cli/client/workato_api/api/projects_api.py +590 -0
- workato_platform_cli/client/workato_api/api/properties_api.py +620 -0
- workato_platform_cli/client/workato_api/api/recipes_api.py +1379 -0
- workato_platform_cli/client/workato_api/api/users_api.py +285 -0
- workato_platform_cli/client/workato_api/api_client.py +807 -0
- workato_platform_cli/client/workato_api/api_response.py +21 -0
- workato_platform_cli/client/workato_api/configuration.py +601 -0
- workato_platform_cli/client/workato_api/docs/APIPlatformApi.md +844 -0
- workato_platform_cli/client/workato_api/docs/ApiClient.md +46 -0
- workato_platform_cli/client/workato_api/docs/ApiClientApiCollectionsInner.md +30 -0
- workato_platform_cli/client/workato_api/docs/ApiClientApiPoliciesInner.md +30 -0
- workato_platform_cli/client/workato_api/docs/ApiClientCreateRequest.md +46 -0
- workato_platform_cli/client/workato_api/docs/ApiClientListResponse.md +32 -0
- workato_platform_cli/client/workato_api/docs/ApiClientResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/ApiCollection.md +38 -0
- workato_platform_cli/client/workato_api/docs/ApiCollectionCreateRequest.md +32 -0
- workato_platform_cli/client/workato_api/docs/ApiEndpoint.md +41 -0
- workato_platform_cli/client/workato_api/docs/ApiKey.md +36 -0
- workato_platform_cli/client/workato_api/docs/ApiKeyCreateRequest.md +32 -0
- workato_platform_cli/client/workato_api/docs/ApiKeyListResponse.md +32 -0
- workato_platform_cli/client/workato_api/docs/ApiKeyResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/Asset.md +39 -0
- workato_platform_cli/client/workato_api/docs/AssetReference.md +37 -0
- workato_platform_cli/client/workato_api/docs/Connection.md +44 -0
- workato_platform_cli/client/workato_api/docs/ConnectionCreateRequest.md +35 -0
- workato_platform_cli/client/workato_api/docs/ConnectionUpdateRequest.md +34 -0
- workato_platform_cli/client/workato_api/docs/ConnectionsApi.md +526 -0
- workato_platform_cli/client/workato_api/docs/ConnectorAction.md +33 -0
- workato_platform_cli/client/workato_api/docs/ConnectorVersion.md +32 -0
- workato_platform_cli/client/workato_api/docs/ConnectorsApi.md +249 -0
- workato_platform_cli/client/workato_api/docs/CreateExportManifestRequest.md +29 -0
- workato_platform_cli/client/workato_api/docs/CreateFolderRequest.md +30 -0
- workato_platform_cli/client/workato_api/docs/CustomConnector.md +35 -0
- workato_platform_cli/client/workato_api/docs/CustomConnectorCodeResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/CustomConnectorCodeResponseData.md +29 -0
- workato_platform_cli/client/workato_api/docs/CustomConnectorListResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/DataTable.md +34 -0
- workato_platform_cli/client/workato_api/docs/DataTableColumn.md +37 -0
- workato_platform_cli/client/workato_api/docs/DataTableColumnRequest.md +37 -0
- workato_platform_cli/client/workato_api/docs/DataTableCreateRequest.md +31 -0
- workato_platform_cli/client/workato_api/docs/DataTableCreateResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/DataTableListResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/DataTableRelation.md +30 -0
- workato_platform_cli/client/workato_api/docs/DataTablesApi.md +172 -0
- workato_platform_cli/client/workato_api/docs/DeleteProject403Response.md +29 -0
- workato_platform_cli/client/workato_api/docs/Error.md +29 -0
- workato_platform_cli/client/workato_api/docs/ExportApi.md +175 -0
- workato_platform_cli/client/workato_api/docs/ExportManifestRequest.md +35 -0
- workato_platform_cli/client/workato_api/docs/ExportManifestResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/ExportManifestResponseResult.md +36 -0
- workato_platform_cli/client/workato_api/docs/Folder.md +35 -0
- workato_platform_cli/client/workato_api/docs/FolderAssetsResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/FolderAssetsResponseResult.md +29 -0
- workato_platform_cli/client/workato_api/docs/FolderCreationResponse.md +35 -0
- workato_platform_cli/client/workato_api/docs/FoldersApi.md +176 -0
- workato_platform_cli/client/workato_api/docs/ImportResults.md +32 -0
- workato_platform_cli/client/workato_api/docs/OAuthUrlResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/OAuthUrlResponseData.md +29 -0
- workato_platform_cli/client/workato_api/docs/OpenApiSpec.md +30 -0
- workato_platform_cli/client/workato_api/docs/PackageDetailsResponse.md +35 -0
- workato_platform_cli/client/workato_api/docs/PackageDetailsResponseRecipeStatusInner.md +30 -0
- workato_platform_cli/client/workato_api/docs/PackageResponse.md +33 -0
- workato_platform_cli/client/workato_api/docs/PackagesApi.md +364 -0
- workato_platform_cli/client/workato_api/docs/PicklistRequest.md +30 -0
- workato_platform_cli/client/workato_api/docs/PicklistResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/PlatformConnector.md +36 -0
- workato_platform_cli/client/workato_api/docs/PlatformConnectorListResponse.md +32 -0
- workato_platform_cli/client/workato_api/docs/Project.md +32 -0
- workato_platform_cli/client/workato_api/docs/ProjectsApi.md +173 -0
- workato_platform_cli/client/workato_api/docs/PropertiesApi.md +186 -0
- workato_platform_cli/client/workato_api/docs/Recipe.md +58 -0
- workato_platform_cli/client/workato_api/docs/RecipeConfigInner.md +33 -0
- workato_platform_cli/client/workato_api/docs/RecipeConnectionUpdateRequest.md +30 -0
- workato_platform_cli/client/workato_api/docs/RecipeListResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/RecipeStartResponse.md +31 -0
- workato_platform_cli/client/workato_api/docs/RecipesApi.md +367 -0
- workato_platform_cli/client/workato_api/docs/RuntimeUserConnectionCreateRequest.md +34 -0
- workato_platform_cli/client/workato_api/docs/RuntimeUserConnectionResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/RuntimeUserConnectionResponseData.md +30 -0
- workato_platform_cli/client/workato_api/docs/SuccessResponse.md +29 -0
- workato_platform_cli/client/workato_api/docs/UpsertProjectPropertiesRequest.md +29 -0
- workato_platform_cli/client/workato_api/docs/User.md +48 -0
- workato_platform_cli/client/workato_api/docs/UsersApi.md +84 -0
- workato_platform_cli/client/workato_api/docs/ValidationError.md +30 -0
- workato_platform_cli/client/workato_api/docs/ValidationErrorErrorsValue.md +28 -0
- workato_platform_cli/client/workato_api/exceptions.py +216 -0
- workato_platform_cli/client/workato_api/models/__init__.py +83 -0
- workato_platform_cli/client/workato_api/models/api_client.py +185 -0
- workato_platform_cli/client/workato_api/models/api_client_api_collections_inner.py +89 -0
- workato_platform_cli/client/workato_api/models/api_client_api_policies_inner.py +89 -0
- workato_platform_cli/client/workato_api/models/api_client_create_request.py +138 -0
- workato_platform_cli/client/workato_api/models/api_client_list_response.py +101 -0
- workato_platform_cli/client/workato_api/models/api_client_response.py +91 -0
- workato_platform_cli/client/workato_api/models/api_collection.py +110 -0
- workato_platform_cli/client/workato_api/models/api_collection_create_request.py +97 -0
- workato_platform_cli/client/workato_api/models/api_endpoint.py +117 -0
- workato_platform_cli/client/workato_api/models/api_key.py +102 -0
- workato_platform_cli/client/workato_api/models/api_key_create_request.py +93 -0
- workato_platform_cli/client/workato_api/models/api_key_list_response.py +101 -0
- workato_platform_cli/client/workato_api/models/api_key_response.py +91 -0
- workato_platform_cli/client/workato_api/models/asset.py +124 -0
- workato_platform_cli/client/workato_api/models/asset_reference.py +110 -0
- workato_platform_cli/client/workato_api/models/connection.py +173 -0
- workato_platform_cli/client/workato_api/models/connection_create_request.py +99 -0
- workato_platform_cli/client/workato_api/models/connection_update_request.py +97 -0
- workato_platform_cli/client/workato_api/models/connector_action.py +100 -0
- workato_platform_cli/client/workato_api/models/connector_version.py +99 -0
- workato_platform_cli/client/workato_api/models/create_export_manifest_request.py +91 -0
- workato_platform_cli/client/workato_api/models/create_folder_request.py +89 -0
- workato_platform_cli/client/workato_api/models/custom_connector.py +117 -0
- workato_platform_cli/client/workato_api/models/custom_connector_code_response.py +91 -0
- workato_platform_cli/client/workato_api/models/custom_connector_code_response_data.py +87 -0
- workato_platform_cli/client/workato_api/models/custom_connector_list_response.py +95 -0
- workato_platform_cli/client/workato_api/models/data_table.py +107 -0
- workato_platform_cli/client/workato_api/models/data_table_column.py +125 -0
- workato_platform_cli/client/workato_api/models/data_table_column_request.py +130 -0
- workato_platform_cli/client/workato_api/models/data_table_create_request.py +99 -0
- workato_platform_cli/client/workato_api/models/data_table_create_response.py +91 -0
- workato_platform_cli/client/workato_api/models/data_table_list_response.py +95 -0
- workato_platform_cli/client/workato_api/models/data_table_relation.py +90 -0
- workato_platform_cli/client/workato_api/models/delete_project403_response.py +87 -0
- workato_platform_cli/client/workato_api/models/error.py +87 -0
- workato_platform_cli/client/workato_api/models/export_manifest_request.py +107 -0
- workato_platform_cli/client/workato_api/models/export_manifest_response.py +91 -0
- workato_platform_cli/client/workato_api/models/export_manifest_response_result.py +112 -0
- workato_platform_cli/client/workato_api/models/folder.py +110 -0
- workato_platform_cli/client/workato_api/models/folder_assets_response.py +91 -0
- workato_platform_cli/client/workato_api/models/folder_assets_response_result.py +95 -0
- workato_platform_cli/client/workato_api/models/folder_creation_response.py +110 -0
- workato_platform_cli/client/workato_api/models/import_results.py +93 -0
- workato_platform_cli/client/workato_api/models/o_auth_url_response.py +91 -0
- workato_platform_cli/client/workato_api/models/o_auth_url_response_data.py +87 -0
- workato_platform_cli/client/workato_api/models/open_api_spec.py +96 -0
- workato_platform_cli/client/workato_api/models/package_details_response.py +126 -0
- workato_platform_cli/client/workato_api/models/package_details_response_recipe_status_inner.py +99 -0
- workato_platform_cli/client/workato_api/models/package_response.py +109 -0
- workato_platform_cli/client/workato_api/models/picklist_request.py +89 -0
- workato_platform_cli/client/workato_api/models/picklist_response.py +88 -0
- workato_platform_cli/client/workato_api/models/platform_connector.py +116 -0
- workato_platform_cli/client/workato_api/models/platform_connector_list_response.py +101 -0
- workato_platform_cli/client/workato_api/models/project.py +93 -0
- workato_platform_cli/client/workato_api/models/recipe.py +174 -0
- workato_platform_cli/client/workato_api/models/recipe_config_inner.py +100 -0
- workato_platform_cli/client/workato_api/models/recipe_connection_update_request.py +89 -0
- workato_platform_cli/client/workato_api/models/recipe_list_response.py +95 -0
- workato_platform_cli/client/workato_api/models/recipe_start_response.py +91 -0
- workato_platform_cli/client/workato_api/models/runtime_user_connection_create_request.py +97 -0
- workato_platform_cli/client/workato_api/models/runtime_user_connection_response.py +91 -0
- workato_platform_cli/client/workato_api/models/runtime_user_connection_response_data.py +89 -0
- workato_platform_cli/client/workato_api/models/success_response.py +87 -0
- workato_platform_cli/client/workato_api/models/upsert_project_properties_request.py +88 -0
- workato_platform_cli/client/workato_api/models/user.py +151 -0
- workato_platform_cli/client/workato_api/models/validation_error.py +102 -0
- workato_platform_cli/client/workato_api/models/validation_error_errors_value.py +143 -0
- workato_platform_cli/client/workato_api/rest.py +213 -0
- workato_platform_cli/client/workato_api/test/__init__.py +0 -0
- workato_platform_cli/client/workato_api/test/test_api_client.py +94 -0
- workato_platform_cli/client/workato_api/test/test_api_client_api_collections_inner.py +52 -0
- workato_platform_cli/client/workato_api/test/test_api_client_api_policies_inner.py +52 -0
- workato_platform_cli/client/workato_api/test/test_api_client_create_request.py +75 -0
- workato_platform_cli/client/workato_api/test/test_api_client_list_response.py +114 -0
- workato_platform_cli/client/workato_api/test/test_api_client_response.py +104 -0
- workato_platform_cli/client/workato_api/test/test_api_collection.py +72 -0
- workato_platform_cli/client/workato_api/test/test_api_collection_create_request.py +57 -0
- workato_platform_cli/client/workato_api/test/test_api_endpoint.py +75 -0
- workato_platform_cli/client/workato_api/test/test_api_key.py +64 -0
- workato_platform_cli/client/workato_api/test/test_api_key_create_request.py +56 -0
- workato_platform_cli/client/workato_api/test/test_api_key_list_response.py +78 -0
- workato_platform_cli/client/workato_api/test/test_api_key_response.py +68 -0
- workato_platform_cli/client/workato_api/test/test_api_platform_api.py +101 -0
- workato_platform_cli/client/workato_api/test/test_asset.py +67 -0
- workato_platform_cli/client/workato_api/test/test_asset_reference.py +62 -0
- workato_platform_cli/client/workato_api/test/test_connection.py +81 -0
- workato_platform_cli/client/workato_api/test/test_connection_create_request.py +59 -0
- workato_platform_cli/client/workato_api/test/test_connection_update_request.py +56 -0
- workato_platform_cli/client/workato_api/test/test_connections_api.py +73 -0
- workato_platform_cli/client/workato_api/test/test_connector_action.py +59 -0
- workato_platform_cli/client/workato_api/test/test_connector_version.py +58 -0
- workato_platform_cli/client/workato_api/test/test_connectors_api.py +52 -0
- workato_platform_cli/client/workato_api/test/test_create_export_manifest_request.py +88 -0
- workato_platform_cli/client/workato_api/test/test_create_folder_request.py +53 -0
- workato_platform_cli/client/workato_api/test/test_custom_connector.py +76 -0
- workato_platform_cli/client/workato_api/test/test_custom_connector_code_response.py +54 -0
- workato_platform_cli/client/workato_api/test/test_custom_connector_code_response_data.py +52 -0
- workato_platform_cli/client/workato_api/test/test_custom_connector_list_response.py +82 -0
- workato_platform_cli/client/workato_api/test/test_data_table.py +88 -0
- workato_platform_cli/client/workato_api/test/test_data_table_column.py +72 -0
- workato_platform_cli/client/workato_api/test/test_data_table_column_request.py +64 -0
- workato_platform_cli/client/workato_api/test/test_data_table_create_request.py +82 -0
- workato_platform_cli/client/workato_api/test/test_data_table_create_response.py +90 -0
- workato_platform_cli/client/workato_api/test/test_data_table_list_response.py +94 -0
- workato_platform_cli/client/workato_api/test/test_data_table_relation.py +54 -0
- workato_platform_cli/client/workato_api/test/test_data_tables_api.py +45 -0
- workato_platform_cli/client/workato_api/test/test_delete_project403_response.py +51 -0
- workato_platform_cli/client/workato_api/test/test_error.py +52 -0
- workato_platform_cli/client/workato_api/test/test_export_api.py +45 -0
- workato_platform_cli/client/workato_api/test/test_export_manifest_request.py +69 -0
- workato_platform_cli/client/workato_api/test/test_export_manifest_response.py +68 -0
- workato_platform_cli/client/workato_api/test/test_export_manifest_response_result.py +66 -0
- workato_platform_cli/client/workato_api/test/test_folder.py +64 -0
- workato_platform_cli/client/workato_api/test/test_folder_assets_response.py +80 -0
- workato_platform_cli/client/workato_api/test/test_folder_assets_response_result.py +78 -0
- workato_platform_cli/client/workato_api/test/test_folder_creation_response.py +64 -0
- workato_platform_cli/client/workato_api/test/test_folders_api.py +45 -0
- workato_platform_cli/client/workato_api/test/test_import_results.py +58 -0
- workato_platform_cli/client/workato_api/test/test_o_auth_url_response.py +54 -0
- workato_platform_cli/client/workato_api/test/test_o_auth_url_response_data.py +52 -0
- workato_platform_cli/client/workato_api/test/test_open_api_spec.py +54 -0
- workato_platform_cli/client/workato_api/test/test_package_details_response.py +64 -0
- workato_platform_cli/client/workato_api/test/test_package_details_response_recipe_status_inner.py +52 -0
- workato_platform_cli/client/workato_api/test/test_package_response.py +58 -0
- workato_platform_cli/client/workato_api/test/test_packages_api.py +59 -0
- workato_platform_cli/client/workato_api/test/test_picklist_request.py +53 -0
- workato_platform_cli/client/workato_api/test/test_picklist_response.py +52 -0
- workato_platform_cli/client/workato_api/test/test_platform_connector.py +94 -0
- workato_platform_cli/client/workato_api/test/test_platform_connector_list_response.py +106 -0
- workato_platform_cli/client/workato_api/test/test_project.py +57 -0
- workato_platform_cli/client/workato_api/test/test_projects_api.py +45 -0
- workato_platform_cli/client/workato_api/test/test_properties_api.py +45 -0
- workato_platform_cli/client/workato_api/test/test_recipe.py +124 -0
- workato_platform_cli/client/workato_api/test/test_recipe_config_inner.py +55 -0
- workato_platform_cli/client/workato_api/test/test_recipe_connection_update_request.py +54 -0
- workato_platform_cli/client/workato_api/test/test_recipe_list_response.py +134 -0
- workato_platform_cli/client/workato_api/test/test_recipe_start_response.py +54 -0
- workato_platform_cli/client/workato_api/test/test_recipes_api.py +59 -0
- workato_platform_cli/client/workato_api/test/test_runtime_user_connection_create_request.py +59 -0
- workato_platform_cli/client/workato_api/test/test_runtime_user_connection_response.py +56 -0
- workato_platform_cli/client/workato_api/test/test_runtime_user_connection_response_data.py +54 -0
- workato_platform_cli/client/workato_api/test/test_success_response.py +52 -0
- workato_platform_cli/client/workato_api/test/test_upsert_project_properties_request.py +52 -0
- workato_platform_cli/client/workato_api/test/test_user.py +85 -0
- workato_platform_cli/client/workato_api/test/test_users_api.py +38 -0
- workato_platform_cli/client/workato_api/test/test_validation_error.py +52 -0
- workato_platform_cli/client/workato_api/test/test_validation_error_errors_value.py +50 -0
- workato_platform_cli/client/workato_api_README.md +205 -0
- workato_platform_cli-1.0.0rc5.dev5.dist-info/METADATA +185 -0
- workato_platform_cli-1.0.0rc5.dev5.dist-info/RECORD +306 -0
- workato_platform_cli-1.0.0rc5.dev5.dist-info/WHEEL +4 -0
- workato_platform_cli-1.0.0rc5.dev5.dist-info/entry_points.txt +2 -0
- workato_platform_cli-1.0.0rc5.dev5.dist-info/licenses/LICENSE +7 -0
|
@@ -0,0 +1,1001 @@
|
|
|
1
|
+
"""Main configuration manager with simplified workspace rules."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
from urllib.parse import urlparse
|
|
9
|
+
|
|
10
|
+
import asyncclick as click
|
|
11
|
+
import certifi
|
|
12
|
+
import inquirer
|
|
13
|
+
|
|
14
|
+
from workato_platform_cli import Workato
|
|
15
|
+
from workato_platform_cli.cli.commands.projects.project_manager import ProjectManager
|
|
16
|
+
from workato_platform_cli.client.workato_api.configuration import Configuration
|
|
17
|
+
from workato_platform_cli.client.workato_api.models.project import Project
|
|
18
|
+
|
|
19
|
+
from .models import AVAILABLE_REGIONS, ConfigData, ProfileData, ProjectInfo, RegionInfo
|
|
20
|
+
from .profiles import ProfileManager, _validate_url_security
|
|
21
|
+
from .workspace import WorkspaceManager
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ConfigManager:
|
|
25
|
+
"""Simplified configuration manager with clear workspace rules"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, config_dir: Path | None = None, skip_validation: bool = False):
|
|
28
|
+
start_path = config_dir or Path.cwd()
|
|
29
|
+
self.workspace_manager = WorkspaceManager(start_path)
|
|
30
|
+
|
|
31
|
+
# If explicit config_dir provided, use it directly
|
|
32
|
+
if config_dir:
|
|
33
|
+
self.config_dir = config_dir
|
|
34
|
+
else:
|
|
35
|
+
# Find nearest .workatoenv file and use that directory as config directory
|
|
36
|
+
nearest_config = self.workspace_manager.find_nearest_workatoenv()
|
|
37
|
+
self.config_dir = nearest_config or start_path
|
|
38
|
+
|
|
39
|
+
self.profile_manager = ProfileManager()
|
|
40
|
+
|
|
41
|
+
# Validate credentials unless skipped
|
|
42
|
+
if not skip_validation:
|
|
43
|
+
self._validate_credentials_or_exit()
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
async def initialize(
|
|
47
|
+
cls,
|
|
48
|
+
config_dir: Path | None = None,
|
|
49
|
+
profile_name: str | None = None,
|
|
50
|
+
region: str | None = None,
|
|
51
|
+
api_token: str | None = None,
|
|
52
|
+
api_url: str | None = None,
|
|
53
|
+
project_name: str | None = None,
|
|
54
|
+
project_id: int | None = None,
|
|
55
|
+
output_mode: str = "table",
|
|
56
|
+
non_interactive: bool = False,
|
|
57
|
+
) -> "ConfigManager":
|
|
58
|
+
"""Initialize workspace with interactive or non-interactive setup"""
|
|
59
|
+
if output_mode == "table":
|
|
60
|
+
if non_interactive:
|
|
61
|
+
click.echo("🚀 Welcome to Workato CLI (Non-interactive mode)")
|
|
62
|
+
else:
|
|
63
|
+
click.echo("🚀 Welcome to Workato CLI")
|
|
64
|
+
click.echo()
|
|
65
|
+
|
|
66
|
+
# Create manager without validation for setup
|
|
67
|
+
manager = cls(config_dir, skip_validation=True)
|
|
68
|
+
|
|
69
|
+
# Validate we're not in a project directory
|
|
70
|
+
manager.workspace_manager.validate_not_in_project()
|
|
71
|
+
|
|
72
|
+
if non_interactive and (profile_name or (region and api_token)):
|
|
73
|
+
# Non-interactive setup
|
|
74
|
+
await manager._setup_non_interactive(
|
|
75
|
+
profile_name=profile_name,
|
|
76
|
+
region=region,
|
|
77
|
+
api_token=api_token,
|
|
78
|
+
api_url=api_url,
|
|
79
|
+
project_name=project_name,
|
|
80
|
+
project_id=project_id,
|
|
81
|
+
)
|
|
82
|
+
else:
|
|
83
|
+
# Run setup flow
|
|
84
|
+
await manager._run_setup_flow()
|
|
85
|
+
|
|
86
|
+
return manager
|
|
87
|
+
|
|
88
|
+
async def _run_setup_flow(self) -> None:
|
|
89
|
+
"""Run the complete setup flow"""
|
|
90
|
+
workspace_root = self.workspace_manager.find_workspace_root()
|
|
91
|
+
self.config_dir = workspace_root
|
|
92
|
+
|
|
93
|
+
click.echo(f"📁 Workspace root: {workspace_root}")
|
|
94
|
+
click.echo()
|
|
95
|
+
|
|
96
|
+
# Step 1: Profile setup
|
|
97
|
+
profile_name = await self._setup_profile()
|
|
98
|
+
|
|
99
|
+
# Step 2: Project setup
|
|
100
|
+
await self._setup_project(profile_name, workspace_root)
|
|
101
|
+
|
|
102
|
+
# Step 3: Create workspace files
|
|
103
|
+
self._create_workspace_files(workspace_root)
|
|
104
|
+
|
|
105
|
+
click.echo("🎉 Configuration complete!")
|
|
106
|
+
|
|
107
|
+
async def _setup_non_interactive(
|
|
108
|
+
self,
|
|
109
|
+
profile_name: str | None = None,
|
|
110
|
+
region: str | None = None,
|
|
111
|
+
api_token: str | None = None,
|
|
112
|
+
api_url: str | None = None,
|
|
113
|
+
project_name: str | None = None,
|
|
114
|
+
project_id: int | None = None,
|
|
115
|
+
) -> None:
|
|
116
|
+
"""Perform all setup actions non-interactively"""
|
|
117
|
+
|
|
118
|
+
workspace_root = self.workspace_manager.find_workspace_root()
|
|
119
|
+
self.config_dir = workspace_root
|
|
120
|
+
|
|
121
|
+
current_profile_name = self.profile_manager.get_current_profile_name(
|
|
122
|
+
profile_name
|
|
123
|
+
)
|
|
124
|
+
if not current_profile_name:
|
|
125
|
+
raise click.ClickException("Profile name is required")
|
|
126
|
+
|
|
127
|
+
profile = self.profile_manager.get_profile(current_profile_name)
|
|
128
|
+
if profile and profile.region:
|
|
129
|
+
region = profile.region
|
|
130
|
+
api_token, api_url = self.profile_manager.resolve_environment_variables(
|
|
131
|
+
project_profile_override=current_profile_name
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Map region to URL
|
|
135
|
+
if region == "custom":
|
|
136
|
+
if not api_url:
|
|
137
|
+
raise click.ClickException("--api-url is required when region=custom")
|
|
138
|
+
region_info = RegionInfo(region="custom", name="Custom URL", url=api_url)
|
|
139
|
+
else:
|
|
140
|
+
if region not in AVAILABLE_REGIONS:
|
|
141
|
+
raise click.ClickException(f"Invalid region: {region}")
|
|
142
|
+
region_info = AVAILABLE_REGIONS[region]
|
|
143
|
+
|
|
144
|
+
# Test authentication and get workspace info
|
|
145
|
+
api_config = Configuration(
|
|
146
|
+
access_token=api_token, host=region_info.url, ssl_ca_cert=certifi.where()
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
async with Workato(configuration=api_config) as workato_api_client:
|
|
150
|
+
user_info = await workato_api_client.users_api.get_workspace_details()
|
|
151
|
+
|
|
152
|
+
# Create and save profile
|
|
153
|
+
if not region_info.url:
|
|
154
|
+
raise click.ClickException("Region URL is required")
|
|
155
|
+
profile_data = ProfileData(
|
|
156
|
+
region=region_info.region,
|
|
157
|
+
region_url=region_info.url,
|
|
158
|
+
workspace_id=user_info.id,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
self.profile_manager.set_profile(current_profile_name, profile_data, api_token)
|
|
162
|
+
self.profile_manager.set_current_profile(current_profile_name)
|
|
163
|
+
|
|
164
|
+
# Get API client for project operations
|
|
165
|
+
api_config = Configuration(
|
|
166
|
+
access_token=api_token, host=region_info.url, ssl_ca_cert=certifi.where()
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
async with Workato(configuration=api_config) as workato_api_client:
|
|
170
|
+
project_manager = ProjectManager(workato_api_client=workato_api_client)
|
|
171
|
+
|
|
172
|
+
selected_project = None
|
|
173
|
+
|
|
174
|
+
if project_name:
|
|
175
|
+
selected_project = await project_manager.create_project(project_name)
|
|
176
|
+
elif project_id:
|
|
177
|
+
# Use existing project
|
|
178
|
+
projects = await project_manager.get_all_projects()
|
|
179
|
+
selected_project = next(
|
|
180
|
+
(p for p in projects if p.id == project_id),
|
|
181
|
+
None,
|
|
182
|
+
)
|
|
183
|
+
if not selected_project:
|
|
184
|
+
raise click.ClickException(
|
|
185
|
+
f"Project with ID {project_id} not found"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if not selected_project:
|
|
189
|
+
raise click.ClickException("No project selected")
|
|
190
|
+
|
|
191
|
+
# Check if this specific project already exists locally in the workspace
|
|
192
|
+
local_projects = self._find_all_projects(workspace_root)
|
|
193
|
+
existing_local_path = None
|
|
194
|
+
|
|
195
|
+
for project_path_candidate, _ in local_projects:
|
|
196
|
+
try:
|
|
197
|
+
project_config_manager = ConfigManager(
|
|
198
|
+
project_path_candidate, skip_validation=True
|
|
199
|
+
)
|
|
200
|
+
config_data = project_config_manager.load_config()
|
|
201
|
+
if config_data.project_id == selected_project.id:
|
|
202
|
+
existing_local_path = project_path_candidate
|
|
203
|
+
break
|
|
204
|
+
except (json.JSONDecodeError, OSError):
|
|
205
|
+
continue
|
|
206
|
+
|
|
207
|
+
# In non-interactive mode, fail if project already exists
|
|
208
|
+
if existing_local_path:
|
|
209
|
+
relative_path = existing_local_path.relative_to(workspace_root)
|
|
210
|
+
raise click.ClickException(
|
|
211
|
+
f"Project '{selected_project.name}' (ID: {selected_project.id}) "
|
|
212
|
+
f"already exists locally at: {relative_path}. "
|
|
213
|
+
f"Cannot reinitialize in non-interactive mode."
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# Project doesn't exist locally - create new directory
|
|
217
|
+
current_dir = Path.cwd().resolve()
|
|
218
|
+
project_path = current_dir / selected_project.name
|
|
219
|
+
|
|
220
|
+
# Create project directory
|
|
221
|
+
project_path.mkdir(parents=True, exist_ok=True)
|
|
222
|
+
|
|
223
|
+
# Save workspace config (with project_path)
|
|
224
|
+
relative_project_path = str(project_path.relative_to(workspace_root))
|
|
225
|
+
workspace_config = ConfigData(
|
|
226
|
+
project_id=selected_project.id,
|
|
227
|
+
project_name=selected_project.name,
|
|
228
|
+
project_path=relative_project_path,
|
|
229
|
+
folder_id=selected_project.folder_id,
|
|
230
|
+
profile=profile_name,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
self.save_config(workspace_config)
|
|
234
|
+
|
|
235
|
+
# Save project config (without project_path)
|
|
236
|
+
project_config_manager = ConfigManager(project_path, skip_validation=True)
|
|
237
|
+
project_config = ConfigData(
|
|
238
|
+
project_id=selected_project.id,
|
|
239
|
+
project_name=selected_project.name,
|
|
240
|
+
project_path=None, # No project_path in project directory
|
|
241
|
+
folder_id=selected_project.folder_id,
|
|
242
|
+
profile=profile_name,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
project_config_manager.save_config(project_config)
|
|
246
|
+
|
|
247
|
+
# Step 3: Create workspace files
|
|
248
|
+
self._create_workspace_files(workspace_root)
|
|
249
|
+
|
|
250
|
+
async def _setup_profile(self) -> str:
|
|
251
|
+
"""Setup or select profile"""
|
|
252
|
+
click.echo("📋 Step 1: Configure profile")
|
|
253
|
+
|
|
254
|
+
existing_profiles = self.profile_manager.list_profiles()
|
|
255
|
+
profile_name: str | None = None
|
|
256
|
+
|
|
257
|
+
if existing_profiles:
|
|
258
|
+
choices = list(existing_profiles.keys()) + ["Create new profile"]
|
|
259
|
+
questions = [
|
|
260
|
+
inquirer.List(
|
|
261
|
+
"profile_choice",
|
|
262
|
+
message="Select a profile",
|
|
263
|
+
choices=choices,
|
|
264
|
+
)
|
|
265
|
+
]
|
|
266
|
+
|
|
267
|
+
answers: dict[str, str] = inquirer.prompt(questions)
|
|
268
|
+
if not answers:
|
|
269
|
+
click.echo("❌ No profile selected")
|
|
270
|
+
sys.exit(1)
|
|
271
|
+
|
|
272
|
+
if answers["profile_choice"] == "Create new profile":
|
|
273
|
+
profile_name = (
|
|
274
|
+
await click.prompt("Enter new profile name", type=str)
|
|
275
|
+
).strip()
|
|
276
|
+
if not profile_name:
|
|
277
|
+
click.echo("❌ Profile name cannot be empty")
|
|
278
|
+
sys.exit(1)
|
|
279
|
+
await self._create_new_profile(profile_name)
|
|
280
|
+
else:
|
|
281
|
+
profile_name = answers["profile_choice"]
|
|
282
|
+
else:
|
|
283
|
+
profile_name = (
|
|
284
|
+
await click.prompt("Enter profile name", default="default", type=str)
|
|
285
|
+
).strip()
|
|
286
|
+
if not profile_name:
|
|
287
|
+
click.echo("❌ Profile name cannot be empty")
|
|
288
|
+
sys.exit(1)
|
|
289
|
+
await self._create_new_profile(profile_name)
|
|
290
|
+
|
|
291
|
+
# Set as current profile
|
|
292
|
+
self.profile_manager.set_current_profile(profile_name)
|
|
293
|
+
click.echo(f"✅ Profile: {profile_name}")
|
|
294
|
+
|
|
295
|
+
return profile_name
|
|
296
|
+
|
|
297
|
+
async def _create_new_profile(self, profile_name: str) -> None:
|
|
298
|
+
"""Create a new profile interactively"""
|
|
299
|
+
# AVAILABLE_REGIONS and RegionInfo already imported at top
|
|
300
|
+
|
|
301
|
+
# Region selection
|
|
302
|
+
click.echo("📍 Select your Workato region")
|
|
303
|
+
regions = list(AVAILABLE_REGIONS.values())
|
|
304
|
+
choices = []
|
|
305
|
+
|
|
306
|
+
for region in regions:
|
|
307
|
+
if region.region == "custom":
|
|
308
|
+
choice_text = "Custom URL"
|
|
309
|
+
else:
|
|
310
|
+
choice_text = f"{region.name} ({region.url})"
|
|
311
|
+
choices.append(choice_text)
|
|
312
|
+
|
|
313
|
+
questions = [
|
|
314
|
+
inquirer.List(
|
|
315
|
+
"region",
|
|
316
|
+
message="Select your Workato region",
|
|
317
|
+
choices=choices,
|
|
318
|
+
),
|
|
319
|
+
]
|
|
320
|
+
|
|
321
|
+
answers = inquirer.prompt(questions)
|
|
322
|
+
if not answers:
|
|
323
|
+
click.echo("❌ Setup cancelled")
|
|
324
|
+
sys.exit(1)
|
|
325
|
+
|
|
326
|
+
selected_index = choices.index(answers["region"])
|
|
327
|
+
selected_region = regions[selected_index]
|
|
328
|
+
|
|
329
|
+
# Handle custom URL
|
|
330
|
+
if selected_region.region == "custom":
|
|
331
|
+
custom_url = await click.prompt(
|
|
332
|
+
"Enter your custom Workato base URL",
|
|
333
|
+
type=str,
|
|
334
|
+
default="https://www.workato.com",
|
|
335
|
+
)
|
|
336
|
+
selected_region = RegionInfo(
|
|
337
|
+
region="custom", name="Custom URL", url=custom_url
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
# Get API token
|
|
341
|
+
click.echo("🔐 Enter your API token")
|
|
342
|
+
token = await click.prompt("Enter your Workato API token", hide_input=True)
|
|
343
|
+
if not token.strip():
|
|
344
|
+
click.echo("❌ No token provided")
|
|
345
|
+
sys.exit(1)
|
|
346
|
+
|
|
347
|
+
# Test authentication and get workspace info
|
|
348
|
+
api_config = Configuration(
|
|
349
|
+
access_token=token, host=selected_region.url, ssl_ca_cert=certifi.where()
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
async with Workato(configuration=api_config) as workato_api_client:
|
|
353
|
+
user_info = await workato_api_client.users_api.get_workspace_details()
|
|
354
|
+
|
|
355
|
+
# Create and save profile
|
|
356
|
+
if not selected_region.url:
|
|
357
|
+
raise click.ClickException("Region URL is required")
|
|
358
|
+
profile_data = ProfileData(
|
|
359
|
+
region=selected_region.region,
|
|
360
|
+
region_url=selected_region.url,
|
|
361
|
+
workspace_id=user_info.id,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
self.profile_manager.set_profile(profile_name, profile_data, token)
|
|
365
|
+
click.echo(f"✅ Authenticated as: {user_info.name}")
|
|
366
|
+
|
|
367
|
+
async def _setup_project(self, profile_name: str, workspace_root: Path) -> None:
|
|
368
|
+
"""Setup project interactively"""
|
|
369
|
+
click.echo("📁 Step 2: Setup project")
|
|
370
|
+
|
|
371
|
+
# Get API client for project operations
|
|
372
|
+
api_token, api_host = self.profile_manager.resolve_environment_variables(
|
|
373
|
+
profile_name
|
|
374
|
+
)
|
|
375
|
+
api_config = Configuration(
|
|
376
|
+
access_token=api_token, host=api_host, ssl_ca_cert=certifi.where()
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
async with Workato(configuration=api_config) as workato_api_client:
|
|
380
|
+
project_manager = ProjectManager(workato_api_client=workato_api_client)
|
|
381
|
+
|
|
382
|
+
# Get available projects
|
|
383
|
+
projects = await project_manager.get_all_projects()
|
|
384
|
+
choices = ["Create new project"]
|
|
385
|
+
|
|
386
|
+
if projects:
|
|
387
|
+
project_choices = [(f"{p.name} (ID: {p.id})", p) for p in projects]
|
|
388
|
+
choices.extend([choice[0] for choice in project_choices])
|
|
389
|
+
|
|
390
|
+
questions = [
|
|
391
|
+
inquirer.List(
|
|
392
|
+
"project",
|
|
393
|
+
message="Select a project",
|
|
394
|
+
choices=choices,
|
|
395
|
+
)
|
|
396
|
+
]
|
|
397
|
+
|
|
398
|
+
answers = inquirer.prompt(questions)
|
|
399
|
+
if not answers:
|
|
400
|
+
click.echo("❌ No project selected")
|
|
401
|
+
sys.exit(1)
|
|
402
|
+
|
|
403
|
+
selected_project = None
|
|
404
|
+
|
|
405
|
+
if answers["project"] == "Create new project":
|
|
406
|
+
project_name = await click.prompt("Enter project name", type=str)
|
|
407
|
+
if not project_name or not project_name.strip():
|
|
408
|
+
click.echo("❌ Project name cannot be empty")
|
|
409
|
+
sys.exit(1)
|
|
410
|
+
|
|
411
|
+
click.echo(f"🔨 Creating project: {project_name}")
|
|
412
|
+
selected_project = await project_manager.create_project(project_name)
|
|
413
|
+
click.echo(f"✅ Created project: {selected_project.name}")
|
|
414
|
+
else:
|
|
415
|
+
# Find selected existing project
|
|
416
|
+
for choice_text, project in [(choices[0], None)] + project_choices:
|
|
417
|
+
if choice_text == answers["project"] and project:
|
|
418
|
+
selected_project = project
|
|
419
|
+
break
|
|
420
|
+
|
|
421
|
+
if not selected_project:
|
|
422
|
+
click.echo("❌ No project selected")
|
|
423
|
+
sys.exit(1)
|
|
424
|
+
|
|
425
|
+
# Check if this specific project already exists locally in the workspace
|
|
426
|
+
local_projects = self._find_all_projects(workspace_root)
|
|
427
|
+
existing_local_path = None
|
|
428
|
+
|
|
429
|
+
for project_path_candidate, _ in local_projects:
|
|
430
|
+
try:
|
|
431
|
+
project_config_manager = ConfigManager(
|
|
432
|
+
project_path_candidate, skip_validation=True
|
|
433
|
+
)
|
|
434
|
+
config_data = project_config_manager.load_config()
|
|
435
|
+
if config_data.project_id == selected_project.id:
|
|
436
|
+
existing_local_path = project_path_candidate
|
|
437
|
+
break
|
|
438
|
+
except (json.JSONDecodeError, OSError):
|
|
439
|
+
continue
|
|
440
|
+
|
|
441
|
+
# If project exists locally, prompt for reinitialization
|
|
442
|
+
if existing_local_path:
|
|
443
|
+
relative_path = existing_local_path.relative_to(workspace_root)
|
|
444
|
+
click.echo(
|
|
445
|
+
f"Project '{selected_project.name}' (ID: {selected_project.id}) "
|
|
446
|
+
f"already exists locally at: {relative_path}"
|
|
447
|
+
)
|
|
448
|
+
if not click.confirm(
|
|
449
|
+
"Reinitialize this project? "
|
|
450
|
+
"This may overwrite or delete local files.",
|
|
451
|
+
default=False,
|
|
452
|
+
):
|
|
453
|
+
click.echo("❌ Initialization cancelled")
|
|
454
|
+
sys.exit(1)
|
|
455
|
+
# Use existing path instead of creating new one
|
|
456
|
+
project_path = existing_local_path
|
|
457
|
+
else:
|
|
458
|
+
# Project doesn't exist locally - create new directory
|
|
459
|
+
current_dir = Path.cwd().resolve()
|
|
460
|
+
project_path = current_dir / selected_project.name
|
|
461
|
+
|
|
462
|
+
# Validate project path
|
|
463
|
+
try:
|
|
464
|
+
self.workspace_manager.validate_project_path(
|
|
465
|
+
project_path, workspace_root
|
|
466
|
+
)
|
|
467
|
+
except ValueError as e:
|
|
468
|
+
click.echo(f"❌ {e}")
|
|
469
|
+
sys.exit(1)
|
|
470
|
+
|
|
471
|
+
# Check if project directory already exists and is non-empty
|
|
472
|
+
if not project_path.exists():
|
|
473
|
+
pass # Directory doesn't exist, we can proceed
|
|
474
|
+
else:
|
|
475
|
+
try:
|
|
476
|
+
existing_files = list(project_path.iterdir())
|
|
477
|
+
if not existing_files:
|
|
478
|
+
pass # Directory is empty, we can proceed
|
|
479
|
+
else:
|
|
480
|
+
# Directory has files - check if it's the same Workato project
|
|
481
|
+
existing_project_id = self._get_existing_project_id(
|
|
482
|
+
project_path
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
if existing_project_id == selected_project.id:
|
|
486
|
+
# Same project ID - allow reconfiguration
|
|
487
|
+
click.echo(
|
|
488
|
+
f"🔄 Reconfiguring existing project: "
|
|
489
|
+
f"{selected_project.name}"
|
|
490
|
+
)
|
|
491
|
+
elif existing_project_id:
|
|
492
|
+
# Different project ID - block it
|
|
493
|
+
self._handle_different_project_error(
|
|
494
|
+
project_path, existing_project_id, selected_project
|
|
495
|
+
)
|
|
496
|
+
else:
|
|
497
|
+
# Not a Workato project - block it
|
|
498
|
+
self._handle_non_empty_directory_error(
|
|
499
|
+
project_path, workspace_root, existing_files
|
|
500
|
+
)
|
|
501
|
+
except OSError:
|
|
502
|
+
pass # If we can't read the directory, let mkdir handle it
|
|
503
|
+
|
|
504
|
+
# Create project directory
|
|
505
|
+
project_path.mkdir(parents=True, exist_ok=True)
|
|
506
|
+
click.echo(
|
|
507
|
+
f"✅ Project directory: {project_path.relative_to(workspace_root)}"
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
# Save workspace config (with project_path)
|
|
511
|
+
relative_project_path = str(project_path.relative_to(workspace_root))
|
|
512
|
+
workspace_config = ConfigData(
|
|
513
|
+
project_id=selected_project.id,
|
|
514
|
+
project_name=selected_project.name,
|
|
515
|
+
project_path=relative_project_path,
|
|
516
|
+
folder_id=selected_project.folder_id,
|
|
517
|
+
profile=profile_name,
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
self.save_config(workspace_config)
|
|
521
|
+
|
|
522
|
+
# Save project config (without project_path)
|
|
523
|
+
project_config_manager = ConfigManager(project_path, skip_validation=True)
|
|
524
|
+
project_config = ConfigData(
|
|
525
|
+
project_id=selected_project.id,
|
|
526
|
+
project_name=selected_project.name,
|
|
527
|
+
project_path=None, # No project_path in project directory
|
|
528
|
+
folder_id=selected_project.folder_id,
|
|
529
|
+
profile=profile_name,
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
project_config_manager.save_config(project_config)
|
|
533
|
+
|
|
534
|
+
click.echo(f"✅ Project: {selected_project.name}")
|
|
535
|
+
|
|
536
|
+
def _create_workspace_files(self, workspace_root: Path) -> None:
|
|
537
|
+
"""Create workspace .gitignore and .workato-ignore files"""
|
|
538
|
+
# Create .gitignore entry for .workatoenv
|
|
539
|
+
gitignore_file = workspace_root / ".gitignore"
|
|
540
|
+
workatoenv_entry = ".workatoenv"
|
|
541
|
+
|
|
542
|
+
existing_lines = []
|
|
543
|
+
if gitignore_file.exists():
|
|
544
|
+
with open(gitignore_file) as f:
|
|
545
|
+
existing_lines = [line.rstrip("\n") for line in f.readlines()]
|
|
546
|
+
|
|
547
|
+
if workatoenv_entry not in existing_lines:
|
|
548
|
+
with open(gitignore_file, "a") as f:
|
|
549
|
+
if existing_lines and existing_lines[-1] != "":
|
|
550
|
+
f.write("\n")
|
|
551
|
+
f.write(f"{workatoenv_entry}\n")
|
|
552
|
+
|
|
553
|
+
# Create .workato-ignore file
|
|
554
|
+
workato_ignore_file = workspace_root / ".workato-ignore"
|
|
555
|
+
if not workato_ignore_file.exists():
|
|
556
|
+
workato_ignore_content = """# Workato CLI ignore patterns
|
|
557
|
+
# Files matching these patterns will be preserved during 'workato pull'
|
|
558
|
+
# and excluded from 'workato push' operations
|
|
559
|
+
|
|
560
|
+
# Configuration files
|
|
561
|
+
.workatoenv
|
|
562
|
+
|
|
563
|
+
# Git files
|
|
564
|
+
.git
|
|
565
|
+
.gitignore
|
|
566
|
+
.gitattributes
|
|
567
|
+
.gitmodules
|
|
568
|
+
|
|
569
|
+
# Python files and virtual environments
|
|
570
|
+
*.py
|
|
571
|
+
*.pyc
|
|
572
|
+
*.pyo
|
|
573
|
+
*.pyd
|
|
574
|
+
__pycache__/
|
|
575
|
+
*.egg-info/
|
|
576
|
+
.venv/
|
|
577
|
+
venv/
|
|
578
|
+
.env
|
|
579
|
+
|
|
580
|
+
# Node.js files
|
|
581
|
+
node_modules/
|
|
582
|
+
package.json
|
|
583
|
+
package-lock.json
|
|
584
|
+
yarn.lock
|
|
585
|
+
.npmrc
|
|
586
|
+
|
|
587
|
+
# Development tools
|
|
588
|
+
.pytest_cache/
|
|
589
|
+
.mypy_cache/
|
|
590
|
+
.ruff_cache/
|
|
591
|
+
htmlcov/
|
|
592
|
+
.coverage
|
|
593
|
+
.tox/
|
|
594
|
+
|
|
595
|
+
# IDE and editor files
|
|
596
|
+
.vscode/
|
|
597
|
+
.idea/
|
|
598
|
+
*.swp
|
|
599
|
+
*.swo
|
|
600
|
+
*~
|
|
601
|
+
|
|
602
|
+
# Build artifacts
|
|
603
|
+
dist/
|
|
604
|
+
build/
|
|
605
|
+
*.egg
|
|
606
|
+
|
|
607
|
+
# Documentation and project files
|
|
608
|
+
*.md
|
|
609
|
+
LICENSE
|
|
610
|
+
.editorconfig
|
|
611
|
+
pyproject.toml
|
|
612
|
+
setup.py
|
|
613
|
+
setup.cfg
|
|
614
|
+
Makefile
|
|
615
|
+
Dockerfile
|
|
616
|
+
docker-compose.yml
|
|
617
|
+
|
|
618
|
+
# OS files
|
|
619
|
+
.DS_Store
|
|
620
|
+
Thumbs.db
|
|
621
|
+
|
|
622
|
+
# Add your own patterns below
|
|
623
|
+
"""
|
|
624
|
+
with open(workato_ignore_file, "w") as f:
|
|
625
|
+
f.write(workato_ignore_content)
|
|
626
|
+
|
|
627
|
+
# Configuration file management
|
|
628
|
+
|
|
629
|
+
def load_config(self) -> ConfigData:
|
|
630
|
+
"""Load configuration from .workatoenv file"""
|
|
631
|
+
config_file = self.config_dir / ".workatoenv"
|
|
632
|
+
|
|
633
|
+
if not config_file.exists():
|
|
634
|
+
return ConfigData.model_construct()
|
|
635
|
+
|
|
636
|
+
try:
|
|
637
|
+
with open(config_file) as f:
|
|
638
|
+
data = json.load(f)
|
|
639
|
+
return ConfigData.model_validate(data)
|
|
640
|
+
except (json.JSONDecodeError, ValueError):
|
|
641
|
+
return ConfigData.model_construct()
|
|
642
|
+
|
|
643
|
+
def save_config(self, config_data: ConfigData) -> None:
|
|
644
|
+
"""Save configuration to .workatoenv file"""
|
|
645
|
+
config_file = self.config_dir / ".workatoenv"
|
|
646
|
+
|
|
647
|
+
with open(config_file, "w") as f:
|
|
648
|
+
json.dump(config_data.model_dump(exclude_none=True), f, indent=2)
|
|
649
|
+
|
|
650
|
+
def save_project_info(self, project_info: ProjectInfo) -> None:
|
|
651
|
+
"""Save project information to configuration"""
|
|
652
|
+
config_data = self.load_config()
|
|
653
|
+
config_data.project_id = project_info.id
|
|
654
|
+
config_data.project_name = project_info.name
|
|
655
|
+
config_data.folder_id = project_info.folder_id
|
|
656
|
+
self.save_config(config_data)
|
|
657
|
+
|
|
658
|
+
# Project and workspace detection methods
|
|
659
|
+
|
|
660
|
+
def get_workspace_root(self) -> Path:
|
|
661
|
+
"""Get workspace root directory"""
|
|
662
|
+
return self.workspace_manager.find_workspace_root()
|
|
663
|
+
|
|
664
|
+
def get_project_directory(self) -> Path | None:
|
|
665
|
+
"""Get project directory from closest .workatoenv config"""
|
|
666
|
+
# Load config from closest .workatoenv file (already found in __init__)
|
|
667
|
+
config_data = self.load_config()
|
|
668
|
+
|
|
669
|
+
if not config_data.project_id:
|
|
670
|
+
return None
|
|
671
|
+
|
|
672
|
+
if not config_data.project_path:
|
|
673
|
+
# No project_path means this .workatoenv IS in the project directory
|
|
674
|
+
# Update workspace root to select this project as current
|
|
675
|
+
self._update_workspace_selection()
|
|
676
|
+
return self.config_dir
|
|
677
|
+
|
|
678
|
+
# Has project_path, so this is a workspace config - resolve relative path
|
|
679
|
+
workspace_root = self.config_dir
|
|
680
|
+
project_dir = workspace_root / config_data.project_path
|
|
681
|
+
|
|
682
|
+
if project_dir.exists():
|
|
683
|
+
return project_dir.resolve()
|
|
684
|
+
else:
|
|
685
|
+
# Current selection is invalid - let user choose from available projects
|
|
686
|
+
return self._handle_invalid_project_selection(workspace_root, config_data)
|
|
687
|
+
|
|
688
|
+
def _update_workspace_selection(self) -> None:
|
|
689
|
+
"""Update workspace root config to select current project"""
|
|
690
|
+
workspace_root = self.workspace_manager.find_workspace_root()
|
|
691
|
+
if not workspace_root or workspace_root == self.config_dir:
|
|
692
|
+
return # Already at workspace root or no workspace found
|
|
693
|
+
|
|
694
|
+
# Load current project config
|
|
695
|
+
project_config = self.load_config()
|
|
696
|
+
if not project_config.project_id:
|
|
697
|
+
return
|
|
698
|
+
|
|
699
|
+
# Calculate relative path from workspace root to current project
|
|
700
|
+
try:
|
|
701
|
+
relative_path = self.config_dir.relative_to(workspace_root)
|
|
702
|
+
except ValueError:
|
|
703
|
+
return # Current dir not within workspace
|
|
704
|
+
|
|
705
|
+
# Update workspace config
|
|
706
|
+
workspace_manager = ConfigManager(workspace_root, skip_validation=True)
|
|
707
|
+
workspace_config = workspace_manager.load_config()
|
|
708
|
+
workspace_config.project_id = project_config.project_id
|
|
709
|
+
workspace_config.project_name = project_config.project_name
|
|
710
|
+
workspace_config.project_path = str(relative_path)
|
|
711
|
+
workspace_config.folder_id = project_config.folder_id
|
|
712
|
+
workspace_config.profile = project_config.profile
|
|
713
|
+
workspace_manager.save_config(workspace_config)
|
|
714
|
+
|
|
715
|
+
click.echo(f"✅ Selected '{project_config.project_name}' as current project")
|
|
716
|
+
|
|
717
|
+
def _handle_invalid_project_selection(
|
|
718
|
+
self, workspace_root: Path, current_config: ConfigData
|
|
719
|
+
) -> Path | None:
|
|
720
|
+
"""Handle case where current project selection is invalid"""
|
|
721
|
+
click.echo(
|
|
722
|
+
f"⚠️ Configured project directory does not exist: "
|
|
723
|
+
f"{current_config.project_path}"
|
|
724
|
+
)
|
|
725
|
+
click.echo(f" Project: {current_config.project_name}")
|
|
726
|
+
click.echo()
|
|
727
|
+
|
|
728
|
+
# Find all available projects in workspace hierarchy
|
|
729
|
+
available_projects = self._find_all_projects(workspace_root)
|
|
730
|
+
|
|
731
|
+
if not available_projects:
|
|
732
|
+
click.echo("❌ No projects found in workspace")
|
|
733
|
+
click.echo("💡 Run 'workato init' to create a new project")
|
|
734
|
+
return None
|
|
735
|
+
|
|
736
|
+
# Prepare choices for inquirer
|
|
737
|
+
choices = []
|
|
738
|
+
for project_path, project_name in available_projects:
|
|
739
|
+
rel_path = project_path.relative_to(workspace_root)
|
|
740
|
+
choice_text = f"{project_name} ({rel_path})"
|
|
741
|
+
choices.append(choice_text)
|
|
742
|
+
|
|
743
|
+
# Let user select
|
|
744
|
+
try:
|
|
745
|
+
questions = [
|
|
746
|
+
inquirer.List(
|
|
747
|
+
"project",
|
|
748
|
+
message="Select a project to use",
|
|
749
|
+
choices=choices,
|
|
750
|
+
)
|
|
751
|
+
]
|
|
752
|
+
|
|
753
|
+
answers = inquirer.prompt(questions)
|
|
754
|
+
if not answers:
|
|
755
|
+
return None
|
|
756
|
+
|
|
757
|
+
# Find selected project
|
|
758
|
+
selected_index = choices.index(answers["project"])
|
|
759
|
+
selected_path, selected_name = available_projects[selected_index]
|
|
760
|
+
|
|
761
|
+
# Load selected project config to get full details
|
|
762
|
+
selected_manager = ConfigManager(selected_path, skip_validation=True)
|
|
763
|
+
selected_config = selected_manager.load_config()
|
|
764
|
+
|
|
765
|
+
# Update workspace config
|
|
766
|
+
workspace_manager = ConfigManager(workspace_root, skip_validation=True)
|
|
767
|
+
workspace_config = workspace_manager.load_config()
|
|
768
|
+
workspace_config.project_id = selected_config.project_id
|
|
769
|
+
workspace_config.project_name = selected_config.project_name
|
|
770
|
+
workspace_config.project_path = str(
|
|
771
|
+
selected_path.relative_to(workspace_root)
|
|
772
|
+
)
|
|
773
|
+
workspace_config.folder_id = selected_config.folder_id
|
|
774
|
+
workspace_config.profile = selected_config.profile
|
|
775
|
+
workspace_manager.save_config(workspace_config)
|
|
776
|
+
|
|
777
|
+
click.echo(f"✅ Selected '{selected_name}' as current project")
|
|
778
|
+
return selected_path
|
|
779
|
+
|
|
780
|
+
except (ImportError, KeyboardInterrupt):
|
|
781
|
+
click.echo("❌ Project selection cancelled")
|
|
782
|
+
return None
|
|
783
|
+
|
|
784
|
+
def _find_all_projects(self, workspace_root: Path) -> list[tuple[Path, str]]:
|
|
785
|
+
"""Find all project directories in workspace hierarchy"""
|
|
786
|
+
projects = []
|
|
787
|
+
|
|
788
|
+
# Recursively search for .workatoenv files without project_path
|
|
789
|
+
for workatoenv_file in workspace_root.rglob(".workatoenv"):
|
|
790
|
+
try:
|
|
791
|
+
with open(workatoenv_file) as f:
|
|
792
|
+
data = json.load(f)
|
|
793
|
+
# Project config has project_id but no project_path
|
|
794
|
+
if (
|
|
795
|
+
"project_id" in data
|
|
796
|
+
and data.get("project_id")
|
|
797
|
+
and not data.get("project_path")
|
|
798
|
+
):
|
|
799
|
+
project_dir = workatoenv_file.parent
|
|
800
|
+
project_name = data.get("project_name", project_dir.name)
|
|
801
|
+
projects.append((project_dir, project_name))
|
|
802
|
+
except (json.JSONDecodeError, OSError):
|
|
803
|
+
continue
|
|
804
|
+
|
|
805
|
+
return sorted(projects, key=lambda x: x[1]) # Sort by project name
|
|
806
|
+
|
|
807
|
+
def get_current_project_name(self) -> str | None:
|
|
808
|
+
"""Get current project name"""
|
|
809
|
+
config_data = self.load_config()
|
|
810
|
+
return config_data.project_name
|
|
811
|
+
|
|
812
|
+
def get_project_root(self) -> Path | None:
|
|
813
|
+
"""Get project root (directory containing .workatoenv)"""
|
|
814
|
+
# For compatibility - this is the same as config_dir when in project
|
|
815
|
+
if self.workspace_manager.is_in_project_directory():
|
|
816
|
+
return self.config_dir
|
|
817
|
+
|
|
818
|
+
# If in workspace, return project directory
|
|
819
|
+
return self.get_project_directory()
|
|
820
|
+
|
|
821
|
+
def is_in_project_workspace(self) -> bool:
|
|
822
|
+
"""Check if in a project workspace"""
|
|
823
|
+
return self.get_workspace_root() is not None
|
|
824
|
+
|
|
825
|
+
def _get_existing_project_id(self, project_path: Path) -> int | None:
|
|
826
|
+
"""Get project ID from existing .workatoenv file, if valid."""
|
|
827
|
+
workatoenv_path = project_path / ".workatoenv"
|
|
828
|
+
if not workatoenv_path.exists():
|
|
829
|
+
return None
|
|
830
|
+
|
|
831
|
+
try:
|
|
832
|
+
with open(workatoenv_path) as f:
|
|
833
|
+
data: dict[str, Any] = json.load(f)
|
|
834
|
+
return data.get("project_id")
|
|
835
|
+
except (json.JSONDecodeError, OSError):
|
|
836
|
+
return None
|
|
837
|
+
|
|
838
|
+
def _handle_different_project_error(
|
|
839
|
+
self,
|
|
840
|
+
project_path: Path,
|
|
841
|
+
existing_project_id: int,
|
|
842
|
+
selected_project: Project,
|
|
843
|
+
) -> None:
|
|
844
|
+
"""Handle error when directory contains different Workato project."""
|
|
845
|
+
workatoenv_path = project_path / ".workatoenv"
|
|
846
|
+
try:
|
|
847
|
+
with open(workatoenv_path) as f:
|
|
848
|
+
existing_data = json.load(f)
|
|
849
|
+
existing_name = existing_data.get("project_name", "Unknown")
|
|
850
|
+
except (json.JSONDecodeError, OSError):
|
|
851
|
+
existing_name = "Unknown"
|
|
852
|
+
|
|
853
|
+
click.echo(
|
|
854
|
+
f"❌ Directory contains different Workato project: "
|
|
855
|
+
f"{existing_name} (ID: {existing_project_id})"
|
|
856
|
+
)
|
|
857
|
+
click.echo(
|
|
858
|
+
f" Cannot initialize {selected_project.name} "
|
|
859
|
+
f"(ID: {selected_project.id}) here"
|
|
860
|
+
)
|
|
861
|
+
click.echo("💡 Choose a different directory or project name")
|
|
862
|
+
sys.exit(1)
|
|
863
|
+
|
|
864
|
+
def _handle_non_empty_directory_error(
|
|
865
|
+
self, project_path: Path, workspace_root: Path, existing_files: list
|
|
866
|
+
) -> None:
|
|
867
|
+
"""Handle error when directory is non-empty but not a Workato project."""
|
|
868
|
+
click.echo(
|
|
869
|
+
f"❌ Project directory is not empty: "
|
|
870
|
+
f"{project_path.relative_to(workspace_root)}"
|
|
871
|
+
)
|
|
872
|
+
click.echo(f" Found {len(existing_files)} existing files")
|
|
873
|
+
click.echo("💡 Choose a different project name or clean the directory first")
|
|
874
|
+
sys.exit(1)
|
|
875
|
+
|
|
876
|
+
# Credential management
|
|
877
|
+
|
|
878
|
+
def _validate_credentials_or_exit(self) -> None:
|
|
879
|
+
"""Validate credentials and exit if missing"""
|
|
880
|
+
is_valid, missing_items = self.validate_environment_config()
|
|
881
|
+
if not is_valid:
|
|
882
|
+
click.echo("❌ Missing required credentials:")
|
|
883
|
+
for item in missing_items:
|
|
884
|
+
click.echo(f" • {item}")
|
|
885
|
+
click.echo()
|
|
886
|
+
click.echo("💡 Run 'workato init' to set up authentication")
|
|
887
|
+
sys.exit(1)
|
|
888
|
+
|
|
889
|
+
def validate_environment_config(self) -> tuple[bool, list[str]]:
|
|
890
|
+
"""Validate environment configuration"""
|
|
891
|
+
config_data = self.load_config()
|
|
892
|
+
return self.profile_manager.validate_credentials(config_data.profile)
|
|
893
|
+
|
|
894
|
+
@property
|
|
895
|
+
def api_token(self) -> str | None:
|
|
896
|
+
"""Get API token"""
|
|
897
|
+
config_data = self.load_config()
|
|
898
|
+
api_token, _ = self.profile_manager.resolve_environment_variables(
|
|
899
|
+
config_data.profile
|
|
900
|
+
)
|
|
901
|
+
return api_token
|
|
902
|
+
|
|
903
|
+
@api_token.setter
|
|
904
|
+
def api_token(self, value: str) -> None:
|
|
905
|
+
"""Save API token to current profile"""
|
|
906
|
+
config_data = self.load_config()
|
|
907
|
+
current_profile_name = self.profile_manager.get_current_profile_name(
|
|
908
|
+
config_data.profile
|
|
909
|
+
)
|
|
910
|
+
|
|
911
|
+
if not current_profile_name:
|
|
912
|
+
current_profile_name = "default"
|
|
913
|
+
|
|
914
|
+
# Check if profile exists
|
|
915
|
+
profiles = self.profile_manager.load_profiles()
|
|
916
|
+
if current_profile_name not in profiles.profiles:
|
|
917
|
+
raise ValueError(
|
|
918
|
+
f"Profile '{current_profile_name}' does not exist. "
|
|
919
|
+
"Please run 'workato init' to create a profile first."
|
|
920
|
+
)
|
|
921
|
+
|
|
922
|
+
# Store token in keyring
|
|
923
|
+
success = self.profile_manager._store_token_in_keyring(
|
|
924
|
+
current_profile_name, value
|
|
925
|
+
)
|
|
926
|
+
if not success:
|
|
927
|
+
if self.profile_manager._is_keyring_enabled():
|
|
928
|
+
raise ValueError(
|
|
929
|
+
"Failed to store token in keyring. "
|
|
930
|
+
"Please check your system keyring setup."
|
|
931
|
+
)
|
|
932
|
+
else:
|
|
933
|
+
raise ValueError(
|
|
934
|
+
"Keyring is disabled. "
|
|
935
|
+
"Please set WORKATO_API_TOKEN environment variable instead."
|
|
936
|
+
)
|
|
937
|
+
|
|
938
|
+
click.echo(f"✅ API token saved to profile '{current_profile_name}'")
|
|
939
|
+
|
|
940
|
+
@property
|
|
941
|
+
def api_host(self) -> str | None:
|
|
942
|
+
"""Get API host"""
|
|
943
|
+
config_data = self.load_config()
|
|
944
|
+
_, api_host = self.profile_manager.resolve_environment_variables(
|
|
945
|
+
config_data.profile
|
|
946
|
+
)
|
|
947
|
+
return api_host
|
|
948
|
+
|
|
949
|
+
# Region management methods
|
|
950
|
+
|
|
951
|
+
def validate_region(self, region_code: str) -> bool:
|
|
952
|
+
"""Validate if region code is valid"""
|
|
953
|
+
return region_code.lower() in AVAILABLE_REGIONS
|
|
954
|
+
|
|
955
|
+
def set_region(
|
|
956
|
+
self, region_code: str, custom_url: str | None = None
|
|
957
|
+
) -> tuple[bool, str]:
|
|
958
|
+
"""Set region by updating the current profile"""
|
|
959
|
+
|
|
960
|
+
if region_code.lower() not in AVAILABLE_REGIONS:
|
|
961
|
+
return False, f"Invalid region: {region_code}"
|
|
962
|
+
|
|
963
|
+
region_info = AVAILABLE_REGIONS[region_code.lower()]
|
|
964
|
+
|
|
965
|
+
# Get current profile
|
|
966
|
+
config_data = self.load_config()
|
|
967
|
+
current_profile_name = self.profile_manager.get_current_profile_name(
|
|
968
|
+
config_data.profile
|
|
969
|
+
)
|
|
970
|
+
if not current_profile_name:
|
|
971
|
+
current_profile_name = "default"
|
|
972
|
+
|
|
973
|
+
# Load profiles
|
|
974
|
+
profiles = self.profile_manager.load_profiles()
|
|
975
|
+
if current_profile_name not in profiles.profiles:
|
|
976
|
+
return False, f"Profile '{current_profile_name}' does not exist"
|
|
977
|
+
|
|
978
|
+
# Handle custom region
|
|
979
|
+
if region_code.lower() == "custom":
|
|
980
|
+
if not custom_url:
|
|
981
|
+
return False, "Custom region requires a URL to be provided"
|
|
982
|
+
|
|
983
|
+
# Validate URL security
|
|
984
|
+
is_valid, error_msg = _validate_url_security(custom_url)
|
|
985
|
+
if not is_valid:
|
|
986
|
+
return False, error_msg
|
|
987
|
+
|
|
988
|
+
# Parse URL and keep only scheme + netloc
|
|
989
|
+
parsed = urlparse(custom_url)
|
|
990
|
+
region_url = f"{parsed.scheme}://{parsed.netloc}"
|
|
991
|
+
else:
|
|
992
|
+
region_url = region_info.url or ""
|
|
993
|
+
|
|
994
|
+
# Update profile
|
|
995
|
+
profiles.profiles[current_profile_name].region = region_code.lower()
|
|
996
|
+
profiles.profiles[current_profile_name].region_url = region_url
|
|
997
|
+
|
|
998
|
+
# Save updated profiles
|
|
999
|
+
self.profile_manager.save_profiles(profiles)
|
|
1000
|
+
|
|
1001
|
+
return True, f"{region_info.name} ({region_url})"
|