splent-cli 1.13.0__tar.gz → 1.15.0__tar.gz
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.
- {splent_cli-1.13.0/src/splent_cli.egg-info → splent_cli-1.15.0}/PKG-INFO +1 -1
- {splent_cli-1.13.0 → splent_cli-1.15.0}/pyproject.toml +1 -1
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/cli.py +8 -2
- splent_cli-1.15.0/src/splent_cli/commands/check/check_contracts.py +206 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_clean.py +5 -35
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_clone.py +2 -35
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_contract.py +44 -1
- splent_cli-1.15.0/src/splent_cli/commands/feature/feature_info.py +214 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_install.py +5 -24
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_outdated.py +5 -28
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_release.py +251 -4
- splent_cli-1.15.0/src/splent_cli/commands/feature/feature_search.py +238 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_upgrade.py +9 -32
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_versions.py +13 -83
- splent_cli-1.15.0/src/splent_cli/commands/marketplace/marketplace_index.py +192 -0
- splent_cli-1.15.0/src/splent_cli/commands/spl/spl_publish.py +434 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/uvl/uvl_utils.py +13 -2
- splent_cli-1.15.0/src/splent_cli/services/marketplace.py +559 -0
- splent_cli-1.15.0/src/splent_cli/services/registry.py +242 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/services/release.py +3 -34
- splent_cli-1.15.0/src/splent_cli/utils/archetype.py +52 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0/src/splent_cli.egg-info}/PKG-INFO +1 -1
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli.egg-info/SOURCES.txt +7 -0
- splent_cli-1.13.0/src/splent_cli/commands/feature/feature_search.py +0 -144
- {splent_cli-1.13.0 → splent_cli-1.15.0}/LICENSE +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/README.md +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/setup.cfg +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/check_deps.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/check_env.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/check_features.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/check_github.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/check_infra.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/check_product.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/clear/__init__.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/command/__init__.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/command/command_create.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/database/db_console.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/database/db_dump.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/database/db_reset.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/database/db_seed.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/database/db_status.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/doctor.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/export/__init__.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/export/export_puml.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_attach.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_compile.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_detach.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_refine.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_remove.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_test.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_unlock.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/lint.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/locust.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_build.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_clean.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_commands.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_config.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_configure.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_console.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_containers.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_create.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_derive.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_down.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_drift.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_env.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_missing.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_release.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_restart.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_routes.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_run.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_select.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_signals.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_test.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_up.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/product/product_validate.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/release/release_core.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/__init__.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/tokens_setup.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/commands/version.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/services/compose.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/services/context.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/services/preflight.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/utils/cache_utils.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/utils/contract_freshness.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/utils/feature_utils.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/utils/git_url.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/utils/integrity.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/utils/io_utils.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/utils/lifecycle.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/utils/manifest.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/utils/proc.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli/utils/template_drift.py +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli.egg-info/requires.txt +0 -0
- {splent_cli-1.13.0 → splent_cli-1.15.0}/src/splent_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: splent_cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.15.0
|
|
4
4
|
Summary: SPLENT-CLI is a CLI to be able to work on your development more easily.
|
|
5
5
|
Author-email: DiversoLab <diversolab@us.es>
|
|
6
6
|
Project-URL: Homepage, https://github.com/diverso-lab/splent_cli
|
|
@@ -3,9 +3,7 @@ import os
|
|
|
3
3
|
import click
|
|
4
4
|
from dotenv import load_dotenv
|
|
5
5
|
|
|
6
|
-
from splent_cli.utils.dynamic_imports import get_app
|
|
7
6
|
from splent_cli.utils.command_loader import load_commands
|
|
8
|
-
from splent_cli.utils.db_utils import check_db_connection
|
|
9
7
|
|
|
10
8
|
load_dotenv()
|
|
11
9
|
|
|
@@ -40,6 +38,8 @@ class SPLENTCLI(click.Group):
|
|
|
40
38
|
|
|
41
39
|
self._feature_cmds_cache: dict[str, click.BaseCommand] = {}
|
|
42
40
|
try:
|
|
41
|
+
from splent_cli.utils.dynamic_imports import get_app
|
|
42
|
+
|
|
43
43
|
app = get_app()
|
|
44
44
|
with app.app_context():
|
|
45
45
|
registry = app.extensions.get("splent_feature_commands", {})
|
|
@@ -84,6 +84,9 @@ class SPLENTCLI(click.Group):
|
|
|
84
84
|
command = self.get_command(ctx, cmd_name)
|
|
85
85
|
|
|
86
86
|
if command and getattr(command, "requires_app", False):
|
|
87
|
+
from splent_cli.utils.dynamic_imports import get_app
|
|
88
|
+
from splent_cli.utils.db_utils import check_db_connection
|
|
89
|
+
|
|
87
90
|
app = get_app()
|
|
88
91
|
if getattr(command, "requires_db", False):
|
|
89
92
|
if not check_db_connection(app):
|
|
@@ -109,6 +112,9 @@ class SPLENTCLI(click.Group):
|
|
|
109
112
|
"🧬 SPL & Variability": [
|
|
110
113
|
cmd for cmd in all_cmds if cmd.startswith(("spl:", "uvl:"))
|
|
111
114
|
],
|
|
115
|
+
"🛒 Marketplace": [
|
|
116
|
+
cmd for cmd in all_cmds if cmd.startswith("marketplace:")
|
|
117
|
+
],
|
|
112
118
|
"🧱 Database": [cmd for cmd in all_cmds if cmd.startswith("db:")],
|
|
113
119
|
"💾 Cache": [cmd for cmd in all_cmds if cmd.startswith("cache:")],
|
|
114
120
|
"🔍 Checks": [cmd for cmd in all_cmds if cmd.startswith("check:")],
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""
|
|
2
|
+
check:contracts — Verify that feature contracts and SPL UVL models agree.
|
|
3
|
+
|
|
4
|
+
The dependency truth must not be split: a feature's requires.features (in
|
|
5
|
+
[tool.splent.contract]) and the SPL's UVL implications (A => B) describe
|
|
6
|
+
the same reality from two sides. This check reports:
|
|
7
|
+
|
|
8
|
+
- contract dependencies with no matching UVL constraint (ERROR: the SPL
|
|
9
|
+
would let a product select A without B and break at runtime), and
|
|
10
|
+
- UVL implications not backed by any contract (WARNING: the dependency
|
|
11
|
+
is invisible to feature:install and the marketplace).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import click
|
|
17
|
+
import tomllib
|
|
18
|
+
|
|
19
|
+
from splent_cli.services import context, marketplace
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _snapshot_version_key(pyproject_path: Path) -> tuple:
|
|
23
|
+
"""Semver sort key for a cache snapshot dir like splent_feature_x@v1.10.0."""
|
|
24
|
+
import re
|
|
25
|
+
|
|
26
|
+
_, _, version = pyproject_path.parent.name.partition("@")
|
|
27
|
+
m = re.match(r"v?(\d+)\.(\d+)\.(\d+)", version)
|
|
28
|
+
if not m:
|
|
29
|
+
return (-1, -1, -1)
|
|
30
|
+
return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _local_contract_requires(workspace: str, package: str) -> list[str] | None:
|
|
34
|
+
"""requires.features of a feature found locally (workspace root, else the
|
|
35
|
+
newest cache snapshot by semver). None when not available locally."""
|
|
36
|
+
candidates = [Path(workspace) / package / "pyproject.toml"]
|
|
37
|
+
cache = Path(workspace) / ".splent_cache" / "features"
|
|
38
|
+
if cache.is_dir():
|
|
39
|
+
candidates.extend(
|
|
40
|
+
sorted(
|
|
41
|
+
cache.glob(f"*/{package}@*/pyproject.toml"),
|
|
42
|
+
key=_snapshot_version_key,
|
|
43
|
+
reverse=True,
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
for path in candidates:
|
|
48
|
+
if not path.is_file():
|
|
49
|
+
continue
|
|
50
|
+
try:
|
|
51
|
+
data = tomllib.loads(path.read_text())
|
|
52
|
+
except (OSError, tomllib.TOMLDecodeError):
|
|
53
|
+
continue
|
|
54
|
+
requires = (
|
|
55
|
+
data.get("tool", {})
|
|
56
|
+
.get("splent", {})
|
|
57
|
+
.get("contract", {})
|
|
58
|
+
.get("requires", {})
|
|
59
|
+
)
|
|
60
|
+
return {
|
|
61
|
+
"features": requires.get("features", []),
|
|
62
|
+
"optional": requires.get("features_optional", []),
|
|
63
|
+
}
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@click.command(
|
|
68
|
+
"check:contracts",
|
|
69
|
+
short_help="Check that feature contracts and SPL UVL constraints agree.",
|
|
70
|
+
)
|
|
71
|
+
@click.option("--spl", "spl_name", default=None, help="Check a single SPL.")
|
|
72
|
+
def check_contracts(spl_name):
|
|
73
|
+
"""
|
|
74
|
+
Cross-check requires.features in every contract against the UVL
|
|
75
|
+
constraints of each SPL in splent_catalog.
|
|
76
|
+
|
|
77
|
+
\b
|
|
78
|
+
Examples:
|
|
79
|
+
splent check:contracts
|
|
80
|
+
splent check:contracts --spl cms_spl
|
|
81
|
+
"""
|
|
82
|
+
workspace = str(context.workspace())
|
|
83
|
+
catalog = Path(workspace) / "splent_catalog"
|
|
84
|
+
|
|
85
|
+
if spl_name:
|
|
86
|
+
spl_dirs = [catalog / spl_name]
|
|
87
|
+
if not spl_dirs[0].is_dir():
|
|
88
|
+
click.secho(f"❌ SPL not found: {spl_dirs[0]}", fg="red")
|
|
89
|
+
raise SystemExit(1)
|
|
90
|
+
else:
|
|
91
|
+
spl_dirs = sorted(d for d in catalog.glob("*") if (d / "metadata.toml").is_file())
|
|
92
|
+
|
|
93
|
+
if not spl_dirs:
|
|
94
|
+
click.secho("❌ No SPLs found in splent_catalog.", fg="red")
|
|
95
|
+
raise SystemExit(1)
|
|
96
|
+
|
|
97
|
+
total_errors = 0
|
|
98
|
+
total_warnings = 0
|
|
99
|
+
|
|
100
|
+
for spl_dir in spl_dirs:
|
|
101
|
+
spl_errors = 0
|
|
102
|
+
spl_warnings = 0
|
|
103
|
+
uvl_path = spl_dir / f"{spl_dir.name}.uvl"
|
|
104
|
+
click.echo()
|
|
105
|
+
click.echo(click.style(f" check:contracts — {spl_dir.name}", bold=True))
|
|
106
|
+
click.echo(click.style(f" {'─' * 60}", fg="bright_black"))
|
|
107
|
+
|
|
108
|
+
if not uvl_path.is_file():
|
|
109
|
+
click.secho(
|
|
110
|
+
f" ⚠ UVL not on disk ({uvl_path.name}) — run: splent spl:fetch "
|
|
111
|
+
f"{spl_dir.name}",
|
|
112
|
+
fg="yellow",
|
|
113
|
+
)
|
|
114
|
+
total_warnings += 1
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
model = marketplace.parse_uvl_structure(uvl_path.read_text())
|
|
118
|
+
shorts = model["features"] # short -> {package, org, ...}
|
|
119
|
+
implications = {(a, b) for a, b in model["constraints"]}
|
|
120
|
+
|
|
121
|
+
contracts: dict[str, list[str] | None] = {}
|
|
122
|
+
for short, meta in shorts.items():
|
|
123
|
+
contracts[short] = _local_contract_requires(workspace, meta["package"])
|
|
124
|
+
|
|
125
|
+
missing_local = sorted(s for s, req in contracts.items() if req is None)
|
|
126
|
+
|
|
127
|
+
# 1. Contract deps that the UVL does not enforce.
|
|
128
|
+
for short, req in sorted(contracts.items()):
|
|
129
|
+
for dep in (req or {}).get("features", []):
|
|
130
|
+
if dep not in shorts:
|
|
131
|
+
# Dependency on a feature outside this SPL: the SPL simply
|
|
132
|
+
# cannot offer this feature safely without it.
|
|
133
|
+
click.secho(
|
|
134
|
+
f" ✗ {short} requires '{dep}' (contract), but '{dep}' "
|
|
135
|
+
f"is not a feature of {spl_dir.name}",
|
|
136
|
+
fg="red",
|
|
137
|
+
)
|
|
138
|
+
spl_errors += 1
|
|
139
|
+
elif (short, dep) not in implications:
|
|
140
|
+
click.secho(
|
|
141
|
+
f" ✗ {short} requires '{dep}' (contract), but the UVL "
|
|
142
|
+
f"has no '{short} => {dep}' constraint",
|
|
143
|
+
fg="red",
|
|
144
|
+
)
|
|
145
|
+
click.echo(
|
|
146
|
+
click.style(
|
|
147
|
+
f" fix: add '{short} => {dep}' to the UVL "
|
|
148
|
+
f"constraints (splent spl:add-constraints "
|
|
149
|
+
f"{spl_dir.name} is interactive)",
|
|
150
|
+
fg="bright_black",
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
spl_errors += 1
|
|
154
|
+
|
|
155
|
+
# 2. UVL implications with no contract behind them.
|
|
156
|
+
for a, b in sorted(implications):
|
|
157
|
+
req = contracts.get(a)
|
|
158
|
+
if a not in shorts or b not in shorts:
|
|
159
|
+
continue # constraint over infra features (redis, nginx…)
|
|
160
|
+
if req is None:
|
|
161
|
+
continue # feature not local — cannot verify
|
|
162
|
+
if b in req["optional"]:
|
|
163
|
+
continue # SPL hardening a soft (graceful) dependency — fine
|
|
164
|
+
if b not in req["features"]:
|
|
165
|
+
click.secho(
|
|
166
|
+
f" ⚠ UVL says '{a} => {b}' but {a}'s contract does not "
|
|
167
|
+
f"require '{b}'",
|
|
168
|
+
fg="yellow",
|
|
169
|
+
)
|
|
170
|
+
click.echo(
|
|
171
|
+
click.style(
|
|
172
|
+
f" fix: add \"{b}\" to requires.features_manual in "
|
|
173
|
+
f"{shorts[a]['package']}/pyproject.toml (preserved on "
|
|
174
|
+
"regeneration), or confirm it is SPL-level policy",
|
|
175
|
+
fg="bright_black",
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
spl_warnings += 1
|
|
179
|
+
|
|
180
|
+
if missing_local:
|
|
181
|
+
click.echo(
|
|
182
|
+
click.style(
|
|
183
|
+
f" ℹ not local (skipped): {', '.join(missing_local)}",
|
|
184
|
+
fg="bright_black",
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if not spl_errors and not spl_warnings:
|
|
189
|
+
click.secho(" ✅ Contracts and UVL agree.", fg="green")
|
|
190
|
+
total_errors += spl_errors
|
|
191
|
+
total_warnings += spl_warnings
|
|
192
|
+
|
|
193
|
+
click.echo()
|
|
194
|
+
if total_errors or total_warnings:
|
|
195
|
+
click.echo(
|
|
196
|
+
click.style(
|
|
197
|
+
f" {total_errors} error(s), {total_warnings} warning(s).",
|
|
198
|
+
bold=True,
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
click.echo()
|
|
202
|
+
if total_errors:
|
|
203
|
+
raise SystemExit(1)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
cli_command = check_contracts
|
|
@@ -12,20 +12,16 @@ from pathlib import Path
|
|
|
12
12
|
import click
|
|
13
13
|
|
|
14
14
|
from splent_cli.services import context
|
|
15
|
+
from splent_cli.utils.archetype import ( # noqa: F401 (re-exported for callers)
|
|
16
|
+
ARCHETYPES,
|
|
17
|
+
ARCHETYPE_LABELS,
|
|
18
|
+
detect_archetype as _detect_archetype,
|
|
19
|
+
)
|
|
15
20
|
from splent_cli.utils.feature_utils import normalize_namespace
|
|
16
21
|
|
|
17
22
|
|
|
18
23
|
DEFAULT_NAMESPACE = os.getenv("SPLENT_DEFAULT_NAMESPACE", "splent_io")
|
|
19
24
|
|
|
20
|
-
ARCHETYPES = ("full", "light", "config", "service")
|
|
21
|
-
|
|
22
|
-
ARCHETYPE_LABELS = {
|
|
23
|
-
"full": "Complete domain feature (models, routes, services, templates, migrations)",
|
|
24
|
-
"light": "Lightweight UI feature (routes, hooks, templates)",
|
|
25
|
-
"config": "Infrastructure feature (config.py only)",
|
|
26
|
-
"service": "Backend service (services, config, commands, signals)",
|
|
27
|
-
}
|
|
28
|
-
|
|
29
25
|
# Files that are ALWAYS kept regardless of archetype
|
|
30
26
|
PROTECTED = {"__init__.py"}
|
|
31
27
|
|
|
@@ -115,32 +111,6 @@ def _is_stub_dir(dirpath, dirname):
|
|
|
115
111
|
return False
|
|
116
112
|
|
|
117
113
|
|
|
118
|
-
# ── Archetype detection ──────────────────────────────────────────────
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def _detect_archetype(src_dir):
|
|
122
|
-
"""Detect the real archetype from source file contents."""
|
|
123
|
-
models = _read(os.path.join(src_dir, "models.py"))
|
|
124
|
-
routes = _read(os.path.join(src_dir, "routes.py"))
|
|
125
|
-
services = _read(os.path.join(src_dir, "services.py"))
|
|
126
|
-
|
|
127
|
-
has_models = models and re.search(r"db\.Column", models)
|
|
128
|
-
has_routes = routes and re.search(r"@\w+\.route\s*\(", routes)
|
|
129
|
-
has_services = services and re.search(
|
|
130
|
-
r"def\s+(?!__)\w+\s*\(.*\):\s*\n\s+(?!pass\b|\.\.\.|\s*#)",
|
|
131
|
-
services,
|
|
132
|
-
re.MULTILINE,
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
if has_models:
|
|
136
|
-
return "full"
|
|
137
|
-
if has_routes:
|
|
138
|
-
return "light"
|
|
139
|
-
if has_services:
|
|
140
|
-
return "service"
|
|
141
|
-
return "config"
|
|
142
|
-
|
|
143
|
-
|
|
144
114
|
# ── Expected files per archetype ─────────────────────────────────────
|
|
145
115
|
|
|
146
116
|
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import re
|
|
3
3
|
import shutil
|
|
4
|
-
import requests
|
|
5
4
|
import click
|
|
6
|
-
from splent_cli.services import context
|
|
5
|
+
from splent_cli.services import context, registry
|
|
7
6
|
from splent_cli.utils.cache_utils import make_feature_readonly
|
|
8
7
|
from splent_cli.utils.feature_utils import normalize_namespace
|
|
9
8
|
from splent_cli.utils.proc import require_tool
|
|
@@ -17,38 +16,6 @@ DEFAULT_NAMESPACE = os.getenv("SPLENT_DEFAULT_NAMESPACE", "splent_io")
|
|
|
17
16
|
# =====================================================================
|
|
18
17
|
|
|
19
18
|
|
|
20
|
-
def _get_latest_tag(namespace, repo) -> str | None:
|
|
21
|
-
"""Fetch the highest semver tag from GitHub. Returns None if unreachable or no tags."""
|
|
22
|
-
api_url = f"https://api.github.com/repos/{namespace}/{repo}/tags?per_page=100"
|
|
23
|
-
# Authenticate when a token is present so version resolution also works for
|
|
24
|
-
# PRIVATE feature repos (an unauthenticated call 404s on those) and to raise
|
|
25
|
-
# the API rate limit.
|
|
26
|
-
headers = {}
|
|
27
|
-
token = os.getenv("GITHUB_TOKEN")
|
|
28
|
-
if token:
|
|
29
|
-
headers["Authorization"] = f"Bearer {token}"
|
|
30
|
-
try:
|
|
31
|
-
r = requests.get(api_url, headers=headers, timeout=5)
|
|
32
|
-
r.raise_for_status()
|
|
33
|
-
tags = r.json()
|
|
34
|
-
if not tags:
|
|
35
|
-
return None
|
|
36
|
-
versions = []
|
|
37
|
-
for tag in tags:
|
|
38
|
-
name = tag.get("name", "")
|
|
39
|
-
m = re.match(r"v?(\d+)\.(\d+)\.(\d+)", name)
|
|
40
|
-
if m:
|
|
41
|
-
versions.append(
|
|
42
|
-
(int(m.group(1)), int(m.group(2)), int(m.group(3)), name)
|
|
43
|
-
)
|
|
44
|
-
if not versions:
|
|
45
|
-
return tags[0]["name"]
|
|
46
|
-
versions.sort(reverse=True)
|
|
47
|
-
return versions[0][3]
|
|
48
|
-
except (requests.RequestException, KeyError, IndexError, ValueError):
|
|
49
|
-
return None
|
|
50
|
-
|
|
51
|
-
|
|
52
19
|
def _parse_full_name(full_name: str):
|
|
53
20
|
"""
|
|
54
21
|
full_name = <namespace>/<repo>[@version]
|
|
@@ -104,7 +71,7 @@ def feature_clone(full_name):
|
|
|
104
71
|
click.echo(
|
|
105
72
|
f"🔍 No version provided → fetching latest tag for {namespace}/{repo}..."
|
|
106
73
|
)
|
|
107
|
-
version =
|
|
74
|
+
version = registry.latest_semver_tag(namespace, repo)
|
|
108
75
|
if not version:
|
|
109
76
|
click.secho(
|
|
110
77
|
f"❌ Could not fetch tags for {namespace}/{repo}. Is the repo reachable and does it have tags?",
|
|
@@ -90,6 +90,9 @@ def _read_current_contract(pyproject_path: Path) -> dict:
|
|
|
90
90
|
raw = data.get("tool", {}).get("splent", {}).get("contract", {})
|
|
91
91
|
ext = raw.get("extensible", {})
|
|
92
92
|
return {
|
|
93
|
+
"archetype": raw.get("archetype"),
|
|
94
|
+
"category": raw.get("category"),
|
|
95
|
+
"tags": raw.get("tags", []),
|
|
93
96
|
"routes": raw.get("provides", {}).get("routes", []),
|
|
94
97
|
"blueprints": raw.get("provides", {}).get("blueprints", []),
|
|
95
98
|
"models": raw.get("provides", {}).get("models", []),
|
|
@@ -100,6 +103,12 @@ def _read_current_contract(pyproject_path: Path) -> dict:
|
|
|
100
103
|
"translations": raw.get("provides", {}).get("translations", []),
|
|
101
104
|
"docker": raw.get("provides", {}).get("docker", []),
|
|
102
105
|
"requires_features": raw.get("requires", {}).get("features", []),
|
|
106
|
+
"requires_features_manual": raw.get("requires", {}).get(
|
|
107
|
+
"features_manual", []
|
|
108
|
+
),
|
|
109
|
+
"requires_features_optional": raw.get("requires", {}).get(
|
|
110
|
+
"features_optional", []
|
|
111
|
+
),
|
|
103
112
|
"env_vars": raw.get("requires", {}).get("env_vars", []),
|
|
104
113
|
"requires_signals": raw.get("requires", {}).get("signals", []),
|
|
105
114
|
"extensible_services": ext.get("services", []),
|
|
@@ -135,6 +144,10 @@ def _print_contract(contract: dict, feature_name: str) -> None:
|
|
|
135
144
|
return "[" + ", ".join(f'"{i}"' for i in items) + "]"
|
|
136
145
|
|
|
137
146
|
click.echo()
|
|
147
|
+
if contract.get("archetype"):
|
|
148
|
+
click.echo(click.style(" [tool.splent.contract]", fg="bright_black"))
|
|
149
|
+
click.echo(f" archetype = \"{contract['archetype']}\"")
|
|
150
|
+
click.echo()
|
|
138
151
|
click.echo(click.style(" [tool.splent.contract.provides]", fg="bright_black"))
|
|
139
152
|
click.echo(f" routes = {_fmt(contract['routes'])}")
|
|
140
153
|
click.echo(f" blueprints = {_fmt(contract['blueprints'])}")
|
|
@@ -148,6 +161,9 @@ def _print_contract(contract: dict, feature_name: str) -> None:
|
|
|
148
161
|
click.echo()
|
|
149
162
|
click.echo(click.style(" [tool.splent.contract.requires]", fg="bright_black"))
|
|
150
163
|
click.echo(f" features = {_fmt(contract['requires_features'])}")
|
|
164
|
+
click.echo(
|
|
165
|
+
f" features_optional = {_fmt(contract.get('requires_features_optional', []))}"
|
|
166
|
+
)
|
|
151
167
|
click.echo(f" env_vars = {_fmt(contract['env_vars'])}")
|
|
152
168
|
click.echo(f" signals = {_fmt(contract.get('requires_signals', []))}")
|
|
153
169
|
click.echo()
|
|
@@ -190,6 +206,7 @@ def _print_diff(current: dict, inferred: dict) -> bool:
|
|
|
190
206
|
("services", "services"),
|
|
191
207
|
("docker", "docker"),
|
|
192
208
|
("requires_features", "requires.features"),
|
|
209
|
+
("requires_features_optional", "requires.features_optional"),
|
|
193
210
|
("env_vars", "requires.env_vars"),
|
|
194
211
|
("signals", "provides.signals"),
|
|
195
212
|
("translations", "provides.translations"),
|
|
@@ -205,6 +222,17 @@ def _print_diff(current: dict, inferred: dict) -> bool:
|
|
|
205
222
|
]
|
|
206
223
|
|
|
207
224
|
diff_lines = []
|
|
225
|
+
|
|
226
|
+
# archetype is a scalar, not a list — compare it directly
|
|
227
|
+
old_arch = current.get("archetype")
|
|
228
|
+
new_arch = inferred.get("archetype")
|
|
229
|
+
if new_arch and old_arch != new_arch:
|
|
230
|
+
if old_arch:
|
|
231
|
+
diff_lines.append(
|
|
232
|
+
click.style(f" - archetype: {old_arch}", fg="red")
|
|
233
|
+
)
|
|
234
|
+
diff_lines.append(click.style(f" + archetype: {new_arch}", fg="green"))
|
|
235
|
+
|
|
208
236
|
for key, label in FIELDS:
|
|
209
237
|
diff_lines.extend(
|
|
210
238
|
_diff_field(label, current.get(key, []), inferred.get(key, []))
|
|
@@ -268,6 +296,22 @@ def feature_contract(feature_ref, write):
|
|
|
268
296
|
click.echo(" 🔍 Scanning source code…")
|
|
269
297
|
inferred = infer_contract(str(cache_path), ns, name)
|
|
270
298
|
|
|
299
|
+
# Deps declared in requires.features_manual are preserved by
|
|
300
|
+
# write_contract; merge them here too so the dry-run diff shows exactly
|
|
301
|
+
# what --write would produce (including real drops of stale inferred deps).
|
|
302
|
+
current = _read_current_contract(pyproject_path)
|
|
303
|
+
manual = sorted(set(current.get("requires_features_manual", [])))
|
|
304
|
+
if manual:
|
|
305
|
+
inferred["requires_features"] = sorted(
|
|
306
|
+
set(inferred["requires_features"]) | set(manual)
|
|
307
|
+
)
|
|
308
|
+
click.echo(
|
|
309
|
+
click.style(
|
|
310
|
+
" Preserving requires.features_manual: ", fg="bright_black"
|
|
311
|
+
)
|
|
312
|
+
+ ", ".join(manual)
|
|
313
|
+
)
|
|
314
|
+
|
|
271
315
|
# Show inferred contract
|
|
272
316
|
click.echo(click.style(" Inferred contract:", bold=True))
|
|
273
317
|
_print_contract(inferred, name)
|
|
@@ -280,7 +324,6 @@ def feature_contract(feature_ref, write):
|
|
|
280
324
|
inferred["docker_networks"] = dc.get("networks", [])
|
|
281
325
|
|
|
282
326
|
# Compare with current
|
|
283
|
-
current = _read_current_contract(pyproject_path)
|
|
284
327
|
has_changes = _print_diff(current, inferred)
|
|
285
328
|
|
|
286
329
|
if not has_changes:
|