splent-cli 1.23.0__tar.gz → 1.25.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.23.0/src/splent_cli.egg-info → splent_cli-1.25.0}/PKG-INFO +4 -1
- {splent_cli-1.23.0 → splent_cli-1.25.0}/pyproject.toml +10 -1
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/cli.py +14 -1
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/check/check_env.py +7 -4
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/check/check_features.py +11 -3
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/check/check_github.py +28 -14
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/check/check_infra.py +21 -5
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/check/check_releases.py +16 -12
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/database/db_migrate.py +52 -14
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/database/db_reset.py +1 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/database/db_rollback.py +1 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/database/db_seed.py +1 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/database/db_status.py +4 -2
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/doctor.py +4 -4
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/env/env_set.py +20 -9
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/export/export_puml.py +1 -1
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_attach.py +14 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_contract.py +14 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_create.py +4 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_detach.py +3 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_fork.py +3 -2
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_impact.py +1 -1
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_pip_install.py +14 -4
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_refine.py +125 -24
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_release.py +298 -25
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_remove.py +4 -3
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_rename.py +5 -2
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_test.py +104 -1
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/marketplace/marketplace_index.py +1 -1
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_build.py +16 -8
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_configure.py +1 -1
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_containers.py +4 -1
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_create.py +5 -4
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_deploy.py +32 -27
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_derive.py +31 -7
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_down.py +12 -4
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_drift.py +8 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_env.py +31 -5
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_release.py +22 -9
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_run.py +59 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_shell.py +21 -1
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_sync_template.py +9 -1
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/release/release_core.py +23 -12
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/release/release_resume.py +39 -11
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/spl/spl_add_constraints.py +2 -1
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/spl/spl_add_feature.py +21 -7
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/spl/spl_remove_feature.py +2 -1
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/spl/spl_utils.py +34 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/tokens_setup.py +14 -5
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/services/compose.py +12 -2
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/services/context.py +35 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/services/preflight.py +6 -2
- splent_cli-1.25.0/src/splent_cli/services/pypi_quota.py +538 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/services/registry.py +16 -10
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/services/release.py +281 -70
- splent_cli-1.25.0/src/splent_cli/services/release_dry_run.py +563 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/services/release_gate.py +84 -49
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/services/spl_store.py +86 -3
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/utils/contract_freshness.py +27 -4
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/utils/dynamic_imports.py +50 -5
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/utils/feature_utils.py +63 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/utils/forge.py +25 -4
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/utils/io_utils.py +86 -1
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/utils/template_drift.py +1 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0/src/splent_cli.egg-info}/PKG-INFO +4 -1
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli.egg-info/SOURCES.txt +2 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli.egg-info/requires.txt +3 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/LICENSE +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/README.md +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/setup.cfg +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/check/check_contracts.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/check/check_deps.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/check/check_product.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/clear/__init__.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/command/__init__.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/command/command_create.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/database/db_console.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/database/db_dump.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/export/__init__.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_compile.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_info.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_unlock.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/lint.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/locust.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/marketplace/login.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/marketplace/logout.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/marketplace/whoami.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_clean.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_commands.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_config.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_console.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_missing.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_restart.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_routes.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_select.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_signals.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_test.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_up.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/product/product_validate.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/spl/__init__.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/spl/spl_migrate_catalog.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/spl/spl_pin.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/spl/spl_publish.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/commands/version.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/services/credentials.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/services/index_refresh.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/services/marketplace.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/services/marketplace_api.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/services/marketplace_url.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/utils/archetype.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/utils/cache_utils.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/utils/git_url.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/utils/integrity.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/utils/lifecycle.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/utils/manifest.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli/utils/proc.py +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.23.0 → splent_cli-1.25.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.25.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
|
|
@@ -9,9 +9,12 @@ Requires-Python: >=3.13
|
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Requires-Dist: click==8.1.8
|
|
12
|
+
Requires-Dist: jinja2>=3.1
|
|
12
13
|
Requires-Dist: packaging>=24.0
|
|
14
|
+
Requires-Dist: python-dotenv>=1.0
|
|
13
15
|
Requires-Dist: pyyaml>=6.0
|
|
14
16
|
Requires-Dist: requests>=2.31.0
|
|
17
|
+
Requires-Dist: splent_framework<2,>=1.15.1
|
|
15
18
|
Requires-Dist: tomli_w>=1.0.0
|
|
16
19
|
Provides-Extra: dev
|
|
17
20
|
Requires-Dist: setuptools==80.3.1; extra == "dev"
|
|
@@ -4,18 +4,27 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "splent_cli"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.25.0"
|
|
8
8
|
description = "SPLENT-CLI is a CLI to be able to work on your development more easily."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
11
11
|
authors = [{ name = "DiversoLab", email = "diversolab@us.es" }]
|
|
12
12
|
license-files = ["LICENSE"]
|
|
13
13
|
|
|
14
|
+
# Everything the CLI imports at module level must be listed here (or come
|
|
15
|
+
# with splent_framework, see tests/unit/test_declared_dependencies.py):
|
|
16
|
+
# startup imports every command module, so one undeclared import breaks
|
|
17
|
+
# `splent --help` on a clean `pip install splent_cli`.
|
|
14
18
|
dependencies = [
|
|
15
19
|
"click==8.1.8",
|
|
20
|
+
"jinja2>=3.1",
|
|
16
21
|
"packaging>=24.0",
|
|
22
|
+
"python-dotenv>=1.0",
|
|
17
23
|
"pyyaml>=6.0",
|
|
18
24
|
"requests>=2.31.0",
|
|
25
|
+
# The CLI drives the framework (paths, feature order, migrations, the
|
|
26
|
+
# app factory) and only works with the same major version of it.
|
|
27
|
+
"splent_framework>=1.15.1,<2",
|
|
19
28
|
"tomli_w>=1.0.0",
|
|
20
29
|
]
|
|
21
30
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import os
|
|
2
3
|
|
|
3
4
|
import click
|
|
@@ -37,6 +38,15 @@ class SPLENTCLI(click.Group):
|
|
|
37
38
|
return self._feature_cmds_cache
|
|
38
39
|
|
|
39
40
|
self._feature_cmds_cache: dict[str, click.BaseCommand] = {}
|
|
41
|
+
# Discovery runs before every command, including the ones a product
|
|
42
|
+
# that is not built yet (selected, not configured or derived) runs
|
|
43
|
+
# while getting there. Building its app then fails, and the framework
|
|
44
|
+
# logged that failure with a full traceback on every single command,
|
|
45
|
+
# although nothing was wrong. The failure is expected here and
|
|
46
|
+
# handled below, so the framework's logging is muted for the attempt.
|
|
47
|
+
debug = bool(os.getenv("SPLENT_DEBUG"))
|
|
48
|
+
if not debug:
|
|
49
|
+
logging.disable(logging.CRITICAL)
|
|
40
50
|
try:
|
|
41
51
|
from splent_cli.utils.dynamic_imports import get_app
|
|
42
52
|
|
|
@@ -57,10 +67,13 @@ class SPLENTCLI(click.Group):
|
|
|
57
67
|
group.add_command(cmd)
|
|
58
68
|
self._feature_cmds_cache[group.name] = group
|
|
59
69
|
except Exception as e:
|
|
60
|
-
if
|
|
70
|
+
if debug:
|
|
61
71
|
click.secho(
|
|
62
72
|
f" ⚠ Feature commands not loaded: {e}", fg="yellow", err=True
|
|
63
73
|
)
|
|
74
|
+
finally:
|
|
75
|
+
if not debug:
|
|
76
|
+
logging.disable(logging.NOTSET)
|
|
64
77
|
return self._feature_cmds_cache
|
|
65
78
|
|
|
66
79
|
def get_command(self, ctx, cmd_name):
|
|
@@ -8,6 +8,8 @@ import sys
|
|
|
8
8
|
import click
|
|
9
9
|
import importlib.metadata
|
|
10
10
|
|
|
11
|
+
from splent_cli.utils import forge
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
def _pkg_version(name: str) -> str | None:
|
|
13
15
|
try:
|
|
@@ -88,11 +90,12 @@ def check_env():
|
|
|
88
90
|
f"CLI={'?' if not cli_v else cli_v} / Framework={'?' if not fw_v else fw_v}"
|
|
89
91
|
)
|
|
90
92
|
|
|
91
|
-
# Credentials
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
# Credentials. The forge token is named after the forge in use: asking for
|
|
94
|
+
# GITHUB_TOKEN on a git.splent.io workspace sent people to the wrong place.
|
|
95
|
+
if forge.token():
|
|
96
|
+
_ok(f"{forge.token_env_name()} set ({forge.host()})")
|
|
94
97
|
else:
|
|
95
|
-
_warn("
|
|
98
|
+
_warn(f"{forge.token_env_name()} not set ({forge.host()})")
|
|
96
99
|
|
|
97
100
|
if os.getenv("TWINE_PASSWORD") or os.getenv("PYPI_TOKEN"):
|
|
98
101
|
_ok("PyPI token set")
|
|
@@ -11,6 +11,8 @@ import tomllib
|
|
|
11
11
|
|
|
12
12
|
from splent_cli.services import context
|
|
13
13
|
from splent_cli.utils.feature_utils import normalize_namespace, read_features_from_data
|
|
14
|
+
from splent_cli.utils.lifecycle import state_rank
|
|
15
|
+
from splent_cli.utils.manifest import feature_key, get_feature_state
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
def _pkg_installed(name: str) -> bool:
|
|
@@ -125,11 +127,17 @@ def check_features():
|
|
|
125
127
|
else:
|
|
126
128
|
_fail("No symlink in product/features/")
|
|
127
129
|
|
|
128
|
-
# 3. Pip install
|
|
129
|
-
|
|
130
|
+
# 3. Pip install. Features are installed in the product's web
|
|
131
|
+
# container, not in the CLI's, so the manifest the install script
|
|
132
|
+
# updates is what says so. Checking this interpreter alone flagged
|
|
133
|
+
# every feature of a running product as not installed.
|
|
134
|
+
state = get_feature_state(product_path, feature_key(org_safe, name, version))
|
|
135
|
+
if state_rank(state) >= state_rank("installed"):
|
|
136
|
+
_ok(f"Installed in the product container ({state})")
|
|
137
|
+
elif _pkg_installed(name):
|
|
130
138
|
_ok("pip installed")
|
|
131
139
|
else:
|
|
132
|
-
_warn("Not
|
|
140
|
+
_warn("Not installed yet (product:derive installs it)")
|
|
133
141
|
|
|
134
142
|
# 4. Git state (editable only)
|
|
135
143
|
if not version:
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import os
|
|
2
1
|
import urllib.request
|
|
3
2
|
import urllib.error
|
|
4
3
|
import json
|
|
@@ -7,32 +6,29 @@ import click
|
|
|
7
6
|
from splent_cli.utils import forge
|
|
8
7
|
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def check_github():
|
|
15
|
-
# The command keeps its name, but the forge it checks is whatever the
|
|
16
|
-
# workspace is configured against: github.com by default, a self-hosted
|
|
17
|
-
# Forgejo when SPLENT_FORGE_HOST says so.
|
|
9
|
+
def _check_forge():
|
|
10
|
+
# The forge checked is whatever the workspace is configured against:
|
|
11
|
+
# git.splent.io by default, another Forgejo, Gitea or github.com when
|
|
12
|
+
# SPLENT_FORGE_HOST says so.
|
|
18
13
|
forge_host = forge.host()
|
|
19
14
|
token_var = forge.token_env_name()
|
|
15
|
+
user_var = forge.user_env_name()
|
|
20
16
|
|
|
21
17
|
click.echo(click.style(f"\n{forge_host} Credentials Check\n", fg="cyan", bold=True))
|
|
22
18
|
|
|
23
|
-
user =
|
|
19
|
+
user = (forge.user() or "").strip()
|
|
24
20
|
# Resolved through forge so a self-hosted forge reads SPLENT_FORGE_TOKEN
|
|
25
21
|
# while GitHub keeps reading GITHUB_TOKEN, as it always has.
|
|
26
22
|
token = (forge.token() or "").strip()
|
|
27
23
|
|
|
28
24
|
# --- presence checks ---
|
|
29
25
|
if not user:
|
|
30
|
-
click.echo(click.style("[✖] ", fg="red") + "
|
|
26
|
+
click.echo(click.style("[✖] ", fg="red") + f"{user_var} not set in .env")
|
|
31
27
|
click.secho(
|
|
32
28
|
" Run 'splent tokens:setup' for setup instructions.", fg="bright_black"
|
|
33
29
|
)
|
|
34
30
|
raise SystemExit(1)
|
|
35
|
-
click.echo(click.style("[✔] ", fg="green") + f"
|
|
31
|
+
click.echo(click.style("[✔] ", fg="green") + f"{user_var} = {user}")
|
|
36
32
|
|
|
37
33
|
if not token:
|
|
38
34
|
click.echo(click.style("[✖] ", fg="red") + f"{token_var} not set in .env")
|
|
@@ -98,7 +94,7 @@ def check_github():
|
|
|
98
94
|
if api_login.lower() != user.lower():
|
|
99
95
|
click.echo(
|
|
100
96
|
click.style("[⚠] ", fg="yellow")
|
|
101
|
-
+ f"Token belongs to '{api_login}', but
|
|
97
|
+
+ f"Token belongs to '{api_login}', but {user_var} is '{user}'"
|
|
102
98
|
)
|
|
103
99
|
else:
|
|
104
100
|
click.echo(click.style("[✔] ", fg="green") + f"Authenticated as {api_login}")
|
|
@@ -123,4 +119,22 @@ def check_github():
|
|
|
123
119
|
click.secho(f"Credentials for {forge_host} OK.", fg="green")
|
|
124
120
|
|
|
125
121
|
|
|
126
|
-
|
|
122
|
+
@click.command(
|
|
123
|
+
"check:forge",
|
|
124
|
+
short_help="Verify the git forge credentials (SPLENT_FORGE_USER / token).",
|
|
125
|
+
)
|
|
126
|
+
def check_forge():
|
|
127
|
+
"""Check that the forge user and token in .env work against the forge's
|
|
128
|
+
API (git.splent.io unless SPLENT_FORGE_HOST says otherwise)."""
|
|
129
|
+
_check_forge()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# The name it had while the features lived on github.com. Kept so scripts and
|
|
133
|
+
# older docs keep working, but out of the help listing.
|
|
134
|
+
@click.command("check:github", hidden=True)
|
|
135
|
+
def check_github():
|
|
136
|
+
"""Alias of check:forge."""
|
|
137
|
+
_check_forge()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
cli_commands = [check_forge, check_github]
|
|
@@ -382,17 +382,33 @@ def check_infra():
|
|
|
382
382
|
else:
|
|
383
383
|
_ok(f"No port conflicts ({len(all_ports)} ports declared)")
|
|
384
384
|
|
|
385
|
-
# Check against running containers (query docker ps once, not per-port)
|
|
385
|
+
# Check against running containers (query docker ps once, not per-port).
|
|
386
|
+
# The product's own stacks are not conflicts: with the product up, every
|
|
387
|
+
# declared port is held by exactly the container meant to hold it, and
|
|
388
|
+
# reporting those as warnings made a healthy product look broken.
|
|
389
|
+
own_projects = {compose.project_name(product, env)} | {
|
|
390
|
+
compose.feature_project_name(compose.normalize_feature_ref(f), product, env)
|
|
391
|
+
for f in features
|
|
392
|
+
}
|
|
386
393
|
running_conflicts = []
|
|
387
|
-
ps_result = _run(
|
|
394
|
+
ps_result = _run(
|
|
395
|
+
[
|
|
396
|
+
"docker",
|
|
397
|
+
"ps",
|
|
398
|
+
"--format",
|
|
399
|
+
'{{.ID}}\t{{.Names}}\t{{.Label "com.docker.compose.project"}}\t{{.Ports}}',
|
|
400
|
+
]
|
|
401
|
+
)
|
|
388
402
|
if ps_result.returncode == 0:
|
|
389
403
|
ps_lines = ps_result.stdout.splitlines()
|
|
390
404
|
for port in all_ports:
|
|
391
405
|
for line in ps_lines:
|
|
392
|
-
parts = line.split("\t",
|
|
393
|
-
if len(parts) <
|
|
406
|
+
parts = line.split("\t", 3)
|
|
407
|
+
if len(parts) < 4:
|
|
408
|
+
continue
|
|
409
|
+
cid, name, project, ports_str = parts
|
|
410
|
+
if project in own_projects:
|
|
394
411
|
continue
|
|
395
|
-
cid, name, ports_str = parts
|
|
396
412
|
if f":{port}->" in ports_str:
|
|
397
413
|
running_conflicts.append((port, name))
|
|
398
414
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
check:releases — Report
|
|
2
|
+
check:releases — Report forge/PyPI divergence across the workspace.
|
|
3
3
|
|
|
4
4
|
Answers, without anyone having to read a published index by hand, the two
|
|
5
5
|
questions a two-channel release can go wrong in:
|
|
@@ -28,6 +28,7 @@ import click
|
|
|
28
28
|
|
|
29
29
|
from splent_cli.services import context, registry, release_gate
|
|
30
30
|
from splent_cli.services.release import extract_repo, read_project_name
|
|
31
|
+
from splent_cli.utils import forge
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
CORE_PACKAGES = ("splent_cli", "splent_framework")
|
|
@@ -142,7 +143,7 @@ def discover_packages(
|
|
|
142
143
|
|
|
143
144
|
@click.command(
|
|
144
145
|
"check:releases",
|
|
145
|
-
short_help="Report
|
|
146
|
+
short_help="Report forge/PyPI divergence for every package in the workspace.",
|
|
146
147
|
)
|
|
147
148
|
@click.option("--feature", "only", default=None, help="Check one package by name.")
|
|
148
149
|
@click.option(
|
|
@@ -168,7 +169,7 @@ def discover_packages(
|
|
|
168
169
|
def check_releases(only, include_core, include_products, divergent_only):
|
|
169
170
|
"""Compare git tags against PyPI releases for the whole workspace."""
|
|
170
171
|
workspace = str(context.workspace())
|
|
171
|
-
token =
|
|
172
|
+
token = forge.token()
|
|
172
173
|
|
|
173
174
|
packages = discover_packages(
|
|
174
175
|
workspace,
|
|
@@ -224,12 +225,12 @@ def check_releases(only, include_core, include_products, divergent_only):
|
|
|
224
225
|
|
|
225
226
|
org, repo_name = repo.split("/", 1)
|
|
226
227
|
wants_pypi = release_gate.PYPI in channels
|
|
227
|
-
wants_github = release_gate.
|
|
228
|
+
wants_github = release_gate.FORGE in channels
|
|
228
229
|
|
|
229
230
|
try:
|
|
230
231
|
tags = registry.list_semver_tags(org, repo_name, token, quiet=False)
|
|
231
232
|
except registry.RegistryError as e:
|
|
232
|
-
reason = _reason(e, "
|
|
233
|
+
reason = _reason(e, "the forge")
|
|
233
234
|
unanswered.append((name, reason))
|
|
234
235
|
click.echo(click.style(" [!] ", fg="yellow") + f"{name}: {reason}")
|
|
235
236
|
continue
|
|
@@ -249,7 +250,7 @@ def check_releases(only, include_core, include_products, divergent_only):
|
|
|
249
250
|
try:
|
|
250
251
|
release_tags = registry.list_release_tags(org, repo_name, token)
|
|
251
252
|
except registry.RegistryError as e:
|
|
252
|
-
reason = _reason(e, "
|
|
253
|
+
reason = _reason(e, "the forge") + " when listing releases"
|
|
253
254
|
unanswered.append((name, reason))
|
|
254
255
|
click.echo(click.style(" [!] ", fg="yellow") + f"{name}: {reason}")
|
|
255
256
|
continue
|
|
@@ -277,7 +278,7 @@ def check_releases(only, include_core, include_products, divergent_only):
|
|
|
277
278
|
if missing:
|
|
278
279
|
tag_without_release.append((name, missing))
|
|
279
280
|
problems.append(
|
|
280
|
-
f"{len(missing)} tag(s) with no
|
|
281
|
+
f"{len(missing)} tag(s) with no release on the forge: "
|
|
281
282
|
+ ", ".join(missing[:6])
|
|
282
283
|
+ (" ..." if len(missing) > 6 else "")
|
|
283
284
|
)
|
|
@@ -290,7 +291,7 @@ def check_releases(only, include_core, include_products, divergent_only):
|
|
|
290
291
|
if not wants_pypi:
|
|
291
292
|
click.echo(
|
|
292
293
|
click.style(" [OK] ", fg="green")
|
|
293
|
-
+ f"{name}:
|
|
294
|
+
+ f"{name}: forge only by declaration, {len(tags)} tag(s)"
|
|
294
295
|
)
|
|
295
296
|
elif not tags and not pypi_versions:
|
|
296
297
|
click.echo(
|
|
@@ -310,7 +311,8 @@ def check_releases(only, include_core, include_products, divergent_only):
|
|
|
310
311
|
if not total and not unanswered:
|
|
311
312
|
checked = len(packages) - len(no_remote)
|
|
312
313
|
click.secho(
|
|
313
|
-
f"
|
|
314
|
+
f" The forge and PyPI agree for all {checked} package(s) checked.",
|
|
315
|
+
fg="green",
|
|
314
316
|
)
|
|
315
317
|
if no_remote:
|
|
316
318
|
click.secho(
|
|
@@ -320,7 +322,8 @@ def check_releases(only, include_core, include_products, divergent_only):
|
|
|
320
322
|
click.echo()
|
|
321
323
|
if not token:
|
|
322
324
|
click.secho(
|
|
323
|
-
" Set
|
|
325
|
+
f" Set {forge.token_env_name()} to avoid rate limits on this command.",
|
|
326
|
+
fg="yellow",
|
|
324
327
|
)
|
|
325
328
|
click.echo()
|
|
326
329
|
return
|
|
@@ -358,7 +361,7 @@ def check_releases(only, include_core, include_products, divergent_only):
|
|
|
358
361
|
|
|
359
362
|
if tag_without_release:
|
|
360
363
|
click.secho(
|
|
361
|
-
f" {len(tag_without_release)} package(s) tagged with no
|
|
364
|
+
f" {len(tag_without_release)} package(s) tagged with no release on the forge",
|
|
362
365
|
fg="red",
|
|
363
366
|
bold=True,
|
|
364
367
|
)
|
|
@@ -384,7 +387,8 @@ def check_releases(only, include_core, include_products, divergent_only):
|
|
|
384
387
|
|
|
385
388
|
if not token:
|
|
386
389
|
click.secho(
|
|
387
|
-
" Set
|
|
390
|
+
f" Set {forge.token_env_name()} to avoid rate limits on this command.",
|
|
391
|
+
fg="yellow",
|
|
388
392
|
)
|
|
389
393
|
click.echo()
|
|
390
394
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
2
4
|
|
|
3
5
|
import click
|
|
4
6
|
from flask_migrate import migrate as alembic_migrate
|
|
@@ -46,10 +48,39 @@ def _is_empty_migration(path: str) -> bool:
|
|
|
46
48
|
return True
|
|
47
49
|
|
|
48
50
|
|
|
51
|
+
def _format_migration(path: str) -> None:
|
|
52
|
+
"""Run ``ruff format`` on a freshly generated migration, if ruff exists.
|
|
53
|
+
|
|
54
|
+
Alembic writes single quotes and one long create_table() call, which the
|
|
55
|
+
release gate's ``ruff format --check`` then rejects: the first release of
|
|
56
|
+
any feature with a model failed lint on a file nobody wrote by hand.
|
|
57
|
+
"""
|
|
58
|
+
ruff = shutil.which("ruff")
|
|
59
|
+
if not ruff:
|
|
60
|
+
return
|
|
61
|
+
subprocess.run([ruff, "format", "--quiet", path], check=False)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _hand_to_directory_owner(path: str) -> None:
|
|
65
|
+
"""Give a generated migration the owner of the directory it lands in.
|
|
66
|
+
|
|
67
|
+
The CLI runs as root inside its container, so Alembic wrote the file as
|
|
68
|
+
root:root 0644. On the host that is a file the user cannot save, and a
|
|
69
|
+
blank ``--empty`` migration exists precisely to be edited by hand.
|
|
70
|
+
"""
|
|
71
|
+
if not (hasattr(os, "geteuid") and os.geteuid() == 0):
|
|
72
|
+
return
|
|
73
|
+
try:
|
|
74
|
+
st = os.stat(os.path.dirname(path))
|
|
75
|
+
os.chown(path, st.st_uid, st.st_gid)
|
|
76
|
+
except OSError:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
|
|
49
80
|
@requires_db
|
|
50
81
|
@click.command(
|
|
51
82
|
"db:migrate",
|
|
52
|
-
short_help="Generate a new migration from model changes
|
|
83
|
+
short_help="Generate a new migration from model changes (db:upgrade applies it).",
|
|
53
84
|
)
|
|
54
85
|
@click.argument("feature", required=False, default=None)
|
|
55
86
|
@click.option(
|
|
@@ -67,8 +98,8 @@ def _is_empty_migration(path: str) -> bool:
|
|
|
67
98
|
@context.requires_product
|
|
68
99
|
def db_migrate(feature, message, empty):
|
|
69
100
|
"""
|
|
70
|
-
Generate new migration files for features that have schema changes
|
|
71
|
-
|
|
101
|
+
Generate new migration files for features that have schema changes.
|
|
102
|
+
Nothing is applied: run ``splent db:upgrade`` for that.
|
|
72
103
|
|
|
73
104
|
Empty migrations (no schema changes detected) are automatically removed,
|
|
74
105
|
unless ``--empty`` is passed, in which case a blank migration is created
|
|
@@ -158,10 +189,24 @@ def db_migrate(feature, message, empty):
|
|
|
158
189
|
|
|
159
190
|
after = _count_versions(mdir)
|
|
160
191
|
|
|
192
|
+
newest = None
|
|
193
|
+
if after > before:
|
|
194
|
+
versions_dir = os.path.join(mdir, "versions")
|
|
195
|
+
newest = max(
|
|
196
|
+
(
|
|
197
|
+
os.path.join(versions_dir, f)
|
|
198
|
+
for f in os.listdir(versions_dir)
|
|
199
|
+
if f.endswith(".py")
|
|
200
|
+
),
|
|
201
|
+
key=os.path.getmtime,
|
|
202
|
+
)
|
|
203
|
+
|
|
161
204
|
# A blank migration requested with --empty is intentional: keep it so the
|
|
162
205
|
# user can fill in upgrade()/downgrade() by hand (e.g. refinement columns).
|
|
163
206
|
if empty:
|
|
164
|
-
if
|
|
207
|
+
if newest:
|
|
208
|
+
_format_migration(newest)
|
|
209
|
+
_hand_to_directory_owner(newest)
|
|
165
210
|
click.echo(
|
|
166
211
|
click.style(
|
|
167
212
|
f" 📝 {feat}: blank migration generated — "
|
|
@@ -174,20 +219,13 @@ def db_migrate(feature, message, empty):
|
|
|
174
219
|
continue
|
|
175
220
|
|
|
176
221
|
# If a new file was generated, check if it's empty (no real schema changes)
|
|
177
|
-
if
|
|
178
|
-
versions_dir = os.path.join(mdir, "versions")
|
|
179
|
-
newest = max(
|
|
180
|
-
(
|
|
181
|
-
os.path.join(versions_dir, f)
|
|
182
|
-
for f in os.listdir(versions_dir)
|
|
183
|
-
if f.endswith(".py")
|
|
184
|
-
),
|
|
185
|
-
key=os.path.getmtime,
|
|
186
|
-
)
|
|
222
|
+
if newest:
|
|
187
223
|
if _is_empty_migration(newest):
|
|
188
224
|
os.remove(newest)
|
|
189
225
|
click.echo(click.style(f" ✔ {feat}: up to date", fg="green"))
|
|
190
226
|
else:
|
|
227
|
+
_format_migration(newest)
|
|
228
|
+
_hand_to_directory_owner(newest)
|
|
191
229
|
click.echo(
|
|
192
230
|
click.style(f" 📝 {feat}: new migration generated", fg="cyan")
|
|
193
231
|
)
|
|
@@ -114,6 +114,7 @@ def _entry_lookup() -> dict:
|
|
|
114
114
|
@click.argument("feature_name", required=False)
|
|
115
115
|
@click.option("-y", "--yes", is_flag=True, help="Confirm without prompting.")
|
|
116
116
|
@context.requires_product
|
|
117
|
+
@context.refuses_production
|
|
117
118
|
def db_reset(feature_name, yes):
|
|
118
119
|
"""Reset the database.
|
|
119
120
|
|
|
@@ -69,6 +69,7 @@ def _find_dependents(feature: str, product_dir: str) -> list[str]:
|
|
|
69
69
|
)
|
|
70
70
|
@click.option("--cascade", is_flag=True, help="Also rollback dependent features.")
|
|
71
71
|
@context.requires_product
|
|
72
|
+
@context.refuses_production
|
|
72
73
|
def db_rollback(feature, steps, cascade):
|
|
73
74
|
app = current_app
|
|
74
75
|
product_path = PathUtils.get_app_base_dir()
|
|
@@ -177,6 +177,7 @@ def _truncate_data():
|
|
|
177
177
|
@click.option("-y", "--yes", is_flag=True, help="Skip confirmation prompts.")
|
|
178
178
|
@click.argument("module", required=False)
|
|
179
179
|
@context.requires_product
|
|
180
|
+
@context.refuses_production
|
|
180
181
|
def db_seed(reset, yes, module):
|
|
181
182
|
if reset:
|
|
182
183
|
if yes or click.confirm(
|
|
@@ -104,8 +104,10 @@ def db_status():
|
|
|
104
104
|
mdir = dirs.get(feat)
|
|
105
105
|
fs_head = _get_filesystem_head(mdir) if mdir else None
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
# Cut to the column, not to 12: a hex revision is 12 characters but a
|
|
108
|
+
# named one is not, and "auth0002_role" showed as "auth0002_rol".
|
|
109
|
+
db_display = db_rev[:col_rev] if db_rev else "—"
|
|
110
|
+
fs_display = fs_head[:col_rev] if fs_head else "—"
|
|
109
111
|
|
|
110
112
|
if db_rev and fs_head and db_rev == fs_head:
|
|
111
113
|
status = click.style("✔ synced", fg="green")
|
|
@@ -12,7 +12,7 @@ from splent_cli.commands.check.check_env import check_env
|
|
|
12
12
|
from splent_cli.commands.check.check_pyproject import check_pyproject
|
|
13
13
|
from splent_cli.commands.check.check_features import check_features
|
|
14
14
|
from splent_cli.commands.check.check_docker import check_docker
|
|
15
|
-
from splent_cli.commands.check.check_github import
|
|
15
|
+
from splent_cli.commands.check.check_github import check_forge
|
|
16
16
|
from splent_cli.commands.check.check_pypi import check_pypi
|
|
17
17
|
from splent_cli.commands.check.check_infra import check_infra
|
|
18
18
|
from splent_cli.commands.check.check_product import check_product
|
|
@@ -49,7 +49,7 @@ SECTIONS = [
|
|
|
49
49
|
(
|
|
50
50
|
"Credentials",
|
|
51
51
|
[
|
|
52
|
-
("check:
|
|
52
|
+
("check:forge", check_forge, False, True),
|
|
53
53
|
("check:pypi", check_pypi, False, True),
|
|
54
54
|
],
|
|
55
55
|
),
|
|
@@ -110,10 +110,10 @@ def doctor(fast):
|
|
|
110
110
|
Environment — Python, env vars, pyproject, Docker, infrastructure
|
|
111
111
|
Product — product health, feature cache/symlinks, feature status
|
|
112
112
|
Services — containers, database migrations
|
|
113
|
-
Credentials —
|
|
113
|
+
Credentials — git forge and PyPI tokens (skipped with --fast)
|
|
114
114
|
|
|
115
115
|
\b
|
|
116
|
-
Use --fast to skip network checks (
|
|
116
|
+
Use --fast to skip network checks (git forge, PyPI) and database checks.
|
|
117
117
|
|
|
118
118
|
\b
|
|
119
119
|
For full product validation (UVL + contracts + imports), run:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import click
|
|
2
2
|
from splent_cli.services import context
|
|
3
|
+
from splent_cli.utils import forge
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
# -------------------------
|
|
@@ -67,16 +68,19 @@ def set_mode_interactive():
|
|
|
67
68
|
|
|
68
69
|
|
|
69
70
|
def set_github_interactive():
|
|
70
|
-
user
|
|
71
|
-
|
|
71
|
+
"""Ask for the forge user and token and store them under the variables
|
|
72
|
+
this forge reads (SPLENT_FORGE_* on git.splent.io, GITHUB_* on github.com)."""
|
|
73
|
+
host = forge.host()
|
|
74
|
+
user = click.prompt(f"{host} username", type=str)
|
|
75
|
+
token = click.prompt(f"{host} access token", hide_input=True)
|
|
72
76
|
|
|
73
|
-
user = _validate_env_value(user, "
|
|
74
|
-
token = _validate_env_value(token, "
|
|
77
|
+
user = _validate_env_value(user, f"{host} username")
|
|
78
|
+
token = _validate_env_value(token, f"{host} token")
|
|
75
79
|
|
|
76
|
-
set_var(
|
|
77
|
-
set_var(
|
|
80
|
+
set_var(forge.user_env_name(), user)
|
|
81
|
+
set_var(forge.token_env_name(), token)
|
|
78
82
|
|
|
79
|
-
click.secho("✔
|
|
83
|
+
click.secho(f"✔ {host} configuration updated.", fg="green")
|
|
80
84
|
remind_source()
|
|
81
85
|
|
|
82
86
|
|
|
@@ -124,8 +128,15 @@ def env_set_mode():
|
|
|
124
128
|
set_mode_interactive()
|
|
125
129
|
|
|
126
130
|
|
|
127
|
-
@env_set_group.command("
|
|
131
|
+
@env_set_group.command("forge")
|
|
132
|
+
def env_set_forge():
|
|
133
|
+
"""Set the git forge user and token."""
|
|
134
|
+
set_github_interactive()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@env_set_group.command("github", hidden=True)
|
|
128
138
|
def env_set_github():
|
|
139
|
+
"""Alias of `env:set forge`."""
|
|
129
140
|
set_github_interactive()
|
|
130
141
|
|
|
131
142
|
|
|
@@ -143,7 +154,7 @@ def run_wizard():
|
|
|
143
154
|
while True:
|
|
144
155
|
click.echo("\n=== SPLENT ENV CONFIG WIZARD ===")
|
|
145
156
|
click.echo("1. Set mode (dev/prod)")
|
|
146
|
-
click.echo("2. Configure
|
|
157
|
+
click.echo("2. Configure git forge credentials")
|
|
147
158
|
click.echo("3. Configure PyPI token")
|
|
148
159
|
click.echo("4. Exit")
|
|
149
160
|
click.echo("--------------------------------")
|
|
@@ -817,7 +817,7 @@ def export_puml(
|
|
|
817
817
|
if not uvl_path:
|
|
818
818
|
click.secho(
|
|
819
819
|
"❌ No UVL file found.\n"
|
|
820
|
-
" Set [tool.splent].spl in pyproject.toml (SPL
|
|
820
|
+
" Set [tool.splent].spl in pyproject.toml (the SPL model)\n"
|
|
821
821
|
" or [tool.splent.uvl].file (legacy).",
|
|
822
822
|
fg="red",
|
|
823
823
|
)
|
|
@@ -10,7 +10,9 @@ from splent_cli.utils.feature_utils import (
|
|
|
10
10
|
product_namespace_spelling,
|
|
11
11
|
prune_feature_links,
|
|
12
12
|
read_feature_list,
|
|
13
|
+
read_project_name,
|
|
13
14
|
remove_feature_link,
|
|
15
|
+
set_feature_distribution,
|
|
14
16
|
write_features_to_data,
|
|
15
17
|
)
|
|
16
18
|
from splent_cli.utils.io_utils import load_toml, atomic_write
|
|
@@ -147,6 +149,18 @@ def feature_attach(feature_identifier, version, env_scope):
|
|
|
147
149
|
scope_label = f" ({env_scope} only)" if env_scope else ""
|
|
148
150
|
click.echo(f" {short}@{version} attached{scope_label}")
|
|
149
151
|
|
|
152
|
+
# ── PyPI name, when it is not the feature's own ──────────────────
|
|
153
|
+
# The production image installs pinned features from PyPI, so it has to
|
|
154
|
+
# know the name this version was published under. A release under a name
|
|
155
|
+
# of one's own (splent-feature-notes-alice) keeps the import package, and
|
|
156
|
+
# this pin, as splent_feature_notes.
|
|
157
|
+
distribution = read_project_name(versioned_dir)
|
|
158
|
+
data = load_toml(pyproject_path, what="pyproject.toml")
|
|
159
|
+
if set_feature_distribution(data, feature_name, distribution):
|
|
160
|
+
atomic_write(pyproject_path, tomli_w.dumps(data))
|
|
161
|
+
if distribution and distribution != feature_name:
|
|
162
|
+
click.echo(f" pypi {short} is published on PyPI as {distribution}")
|
|
163
|
+
|
|
150
164
|
# ── Create/update symlink ─────────────────────────────────────────
|
|
151
165
|
product_features_dir = os.path.join(product_path, "features", namespace_fs)
|
|
152
166
|
os.makedirs(product_features_dir, exist_ok=True)
|