splent-cli 1.22.2__tar.gz → 1.24.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.22.2/src/splent_cli.egg-info → splent_cli-1.24.0}/PKG-INFO +4 -1
- {splent_cli-1.22.2 → splent_cli-1.24.0}/pyproject.toml +10 -1
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/cli.py +14 -1
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/check/check_contracts.py +24 -6
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/check/check_env.py +7 -4
- splent_cli-1.24.0/src/splent_cli/commands/check/check_github.py +126 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/check/check_infra.py +21 -5
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/database/db_migrate.py +34 -14
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/database/db_status.py +4 -2
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/export/export_puml.py +1 -1
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_contract.py +11 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_fork.py +30 -14
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_impact.py +1 -1
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_refine.py +115 -23
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_release.py +120 -7
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_test.py +92 -1
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_unlock.py +9 -3
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/marketplace/marketplace_index.py +1 -1
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_build.py +19 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_configure.py +1 -1
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_containers.py +4 -1
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_create.py +4 -4
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_deploy.py +21 -19
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_derive.py +31 -7
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_down.py +12 -4
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_release.py +14 -1
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_shell.py +21 -1
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/release/release_core.py +10 -1
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/release/release_resume.py +29 -2
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/spl/spl_add_feature.py +15 -6
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/tokens_setup.py +35 -11
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/services/compose.py +12 -2
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/services/index_refresh.py +54 -24
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/services/preflight.py +28 -15
- splent_cli-1.24.0/src/splent_cli/services/pypi_quota.py +538 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/services/registry.py +118 -58
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/services/release.py +196 -80
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/services/release_gate.py +62 -33
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/services/spl_store.py +33 -3
- splent_cli-1.24.0/src/splent_cli/utils/forge.py +231 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/utils/git_url.py +47 -28
- {splent_cli-1.22.2 → splent_cli-1.24.0/src/splent_cli.egg-info}/PKG-INFO +4 -1
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli.egg-info/SOURCES.txt +2 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli.egg-info/requires.txt +3 -0
- splent_cli-1.22.2/src/splent_cli/commands/check/check_github.py +0 -105
- {splent_cli-1.22.2 → splent_cli-1.24.0}/LICENSE +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/README.md +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/setup.cfg +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/check/check_deps.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/check/check_features.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/check/check_product.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/check/check_releases.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/clear/__init__.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/command/__init__.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/command/command_create.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/database/db_console.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/database/db_dump.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/database/db_reset.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/database/db_seed.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/doctor.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/export/__init__.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_attach.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_compile.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_detach.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_info.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_remove.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/lint.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/locust.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/marketplace/login.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/marketplace/logout.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/marketplace/whoami.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_clean.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_commands.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_config.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_console.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_drift.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_env.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_missing.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_restart.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_routes.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_run.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_select.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_signals.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_test.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_up.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/product/product_validate.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/spl/__init__.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/spl/spl_migrate_catalog.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/spl/spl_pin.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/spl/spl_publish.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/commands/version.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/services/context.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/services/credentials.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/services/marketplace.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/services/marketplace_api.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/services/marketplace_url.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/utils/archetype.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/utils/cache_utils.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/utils/contract_freshness.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/utils/feature_utils.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/utils/integrity.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/utils/io_utils.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/utils/lifecycle.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/utils/manifest.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/utils/proc.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli/utils/template_drift.py +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.22.2 → splent_cli-1.24.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.24.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.0
|
|
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.24.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.0,<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):
|
|
@@ -17,6 +17,7 @@ import click
|
|
|
17
17
|
import tomllib
|
|
18
18
|
|
|
19
19
|
from splent_cli.services import context, marketplace, spl_store
|
|
20
|
+
from splent_cli.utils import forge
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def _snapshot_version_key(pyproject_path: Path) -> tuple:
|
|
@@ -65,7 +66,7 @@ def _local_contract_requires(workspace: str, package: str) -> list[str] | None:
|
|
|
65
66
|
|
|
66
67
|
|
|
67
68
|
def _exists_remotely(org: str | None, package: str) -> bool:
|
|
68
|
-
"""Whether a feature not present locally at least exists on
|
|
69
|
+
"""Whether a feature not present locally at least exists on the forge.
|
|
69
70
|
|
|
70
71
|
Answers True whenever the question cannot be settled: no org to look
|
|
71
72
|
under, no network, a rate limit, anything unexpected. A diagnostic that
|
|
@@ -78,10 +79,27 @@ def _exists_remotely(org: str | None, package: str) -> bool:
|
|
|
78
79
|
import urllib.error
|
|
79
80
|
import urllib.request
|
|
80
81
|
|
|
81
|
-
# The UVL writes the org the way
|
|
82
|
-
# not the way the filesystem does ("splent_io"), so it is used verbatim.
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
# The UVL writes the org the way the forge spells it ("splent-io"), which
|
|
83
|
+
# is not the way the filesystem does ("splent_io"), so it is used verbatim.
|
|
84
|
+
#
|
|
85
|
+
# GitHub keeps the anonymous HEAD against the web URL it has always used.
|
|
86
|
+
# A self-hosted forge is far likelier to keep its repositories private, and
|
|
87
|
+
# a private repository answers 404 to a stranger, which would read here as
|
|
88
|
+
# a feature nobody ever wrote. When such a forge gives us a token we ask
|
|
89
|
+
# its API instead, with GET because Gitea-compatible routes are registered
|
|
90
|
+
# for GET alone and answer a HEAD with 405.
|
|
91
|
+
token = None if forge.is_github() else forge.token()
|
|
92
|
+
if token:
|
|
93
|
+
request = urllib.request.Request(
|
|
94
|
+
f"{forge.api_base()}/repos/{org}/{package}",
|
|
95
|
+
headers={
|
|
96
|
+
"Authorization": f"token {token}",
|
|
97
|
+
"Accept": "application/json",
|
|
98
|
+
"User-Agent": "splent-cli",
|
|
99
|
+
},
|
|
100
|
+
)
|
|
101
|
+
else:
|
|
102
|
+
request = urllib.request.Request(forge.web_url(org, package), method="HEAD")
|
|
85
103
|
try:
|
|
86
104
|
with urllib.request.urlopen(request, timeout=10) as response:
|
|
87
105
|
return response.status < 400
|
|
@@ -230,7 +248,7 @@ def check_contracts(spl_name):
|
|
|
230
248
|
click.secho(
|
|
231
249
|
f" ✗ {short} is offered by the model but does not exist: "
|
|
232
250
|
f"{meta.get('org')}/{meta['package']} is not in this workspace, "
|
|
233
|
-
f"not in the cache and not on
|
|
251
|
+
f"not in the cache and not on {forge.host()}",
|
|
234
252
|
fg="red",
|
|
235
253
|
)
|
|
236
254
|
click.echo(
|
|
@@ -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")
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import urllib.request
|
|
3
|
+
import urllib.error
|
|
4
|
+
import json
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from splent_cli.utils import forge
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.command(
|
|
11
|
+
"check:github",
|
|
12
|
+
short_help="Verify forge credentials from .env (GITHUB_USER / forge token)",
|
|
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.
|
|
18
|
+
forge_host = forge.host()
|
|
19
|
+
token_var = forge.token_env_name()
|
|
20
|
+
|
|
21
|
+
click.echo(click.style(f"\n{forge_host} Credentials Check\n", fg="cyan", bold=True))
|
|
22
|
+
|
|
23
|
+
user = os.getenv("GITHUB_USER", "").strip()
|
|
24
|
+
# Resolved through forge so a self-hosted forge reads SPLENT_FORGE_TOKEN
|
|
25
|
+
# while GitHub keeps reading GITHUB_TOKEN, as it always has.
|
|
26
|
+
token = (forge.token() or "").strip()
|
|
27
|
+
|
|
28
|
+
# --- presence checks ---
|
|
29
|
+
if not user:
|
|
30
|
+
click.echo(click.style("[✖] ", fg="red") + "GITHUB_USER not set in .env")
|
|
31
|
+
click.secho(
|
|
32
|
+
" Run 'splent tokens:setup' for setup instructions.", fg="bright_black"
|
|
33
|
+
)
|
|
34
|
+
raise SystemExit(1)
|
|
35
|
+
click.echo(click.style("[✔] ", fg="green") + f"GITHUB_USER = {user}")
|
|
36
|
+
|
|
37
|
+
if not token:
|
|
38
|
+
click.echo(click.style("[✖] ", fg="red") + f"{token_var} not set in .env")
|
|
39
|
+
click.secho(
|
|
40
|
+
" Run 'splent tokens:setup' for setup instructions.", fg="bright_black"
|
|
41
|
+
)
|
|
42
|
+
raise SystemExit(1)
|
|
43
|
+
|
|
44
|
+
masked = token[:4] + "*" * (len(token) - 8) + token[-4:]
|
|
45
|
+
click.echo(click.style("[✔] ", fg="green") + f"{token_var} = {masked}")
|
|
46
|
+
|
|
47
|
+
# --- API call ---
|
|
48
|
+
# Vendor media types are GitHub's own; a Gitea-compatible forge does not
|
|
49
|
+
# know them and answers plain JSON, so they are only sent to GitHub.
|
|
50
|
+
headers = {
|
|
51
|
+
"Authorization": f"token {token}",
|
|
52
|
+
"Accept": "application/json",
|
|
53
|
+
"User-Agent": "splent-cli",
|
|
54
|
+
}
|
|
55
|
+
if forge.is_github():
|
|
56
|
+
headers["Accept"] = "application/vnd.github+json"
|
|
57
|
+
headers["X-GitHub-Api-Version"] = "2022-11-28"
|
|
58
|
+
|
|
59
|
+
click.echo(f"\nContacting the {forge_host} API...")
|
|
60
|
+
req = urllib.request.Request(f"{forge.api_base()}/user", headers=headers)
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
64
|
+
data = json.loads(resp.read().decode())
|
|
65
|
+
rate_remaining = resp.headers.get("X-RateLimit-Remaining", "?")
|
|
66
|
+
rate_limit = resp.headers.get("X-RateLimit-Limit", "?")
|
|
67
|
+
except urllib.error.HTTPError as e:
|
|
68
|
+
if e.code == 401:
|
|
69
|
+
click.echo(
|
|
70
|
+
click.style("[✖] ", fg="red")
|
|
71
|
+
+ "Token invalid or expired (401 Unauthorized)"
|
|
72
|
+
)
|
|
73
|
+
click.secho(
|
|
74
|
+
" Run 'splent tokens:setup' for instructions on obtaining a valid token.",
|
|
75
|
+
fg="bright_black",
|
|
76
|
+
)
|
|
77
|
+
elif e.code == 403:
|
|
78
|
+
click.echo(
|
|
79
|
+
click.style("[✖] ", fg="red")
|
|
80
|
+
+ "Token lacks required permissions (403 Forbidden)"
|
|
81
|
+
)
|
|
82
|
+
scope = "repo" if forge.is_github() else "write:repository"
|
|
83
|
+
click.secho(
|
|
84
|
+
f" Run 'splent tokens:setup', the token needs the '{scope}' scope.",
|
|
85
|
+
fg="bright_black",
|
|
86
|
+
)
|
|
87
|
+
else:
|
|
88
|
+
click.echo(
|
|
89
|
+
click.style("[✖] ", fg="red") + f"{forge_host} API error: HTTP {e.code}"
|
|
90
|
+
)
|
|
91
|
+
raise SystemExit(1)
|
|
92
|
+
except urllib.error.URLError as e:
|
|
93
|
+
click.echo(click.style("[✖] ", fg="red") + f"Network error: {e.reason}")
|
|
94
|
+
raise SystemExit(1)
|
|
95
|
+
|
|
96
|
+
# --- validate identity ---
|
|
97
|
+
api_login = data.get("login", "")
|
|
98
|
+
if api_login.lower() != user.lower():
|
|
99
|
+
click.echo(
|
|
100
|
+
click.style("[⚠] ", fg="yellow")
|
|
101
|
+
+ f"Token belongs to '{api_login}', but GITHUB_USER is '{user}'"
|
|
102
|
+
)
|
|
103
|
+
else:
|
|
104
|
+
click.echo(click.style("[✔] ", fg="green") + f"Authenticated as {api_login}")
|
|
105
|
+
|
|
106
|
+
# GitHub calls the display name "name"; a Gitea-compatible forge calls it
|
|
107
|
+
# "full_name", so without the fallback every Forgejo check reported "-".
|
|
108
|
+
name = data.get("name") or data.get("full_name") or "-"
|
|
109
|
+
click.echo(click.style("[✔] ", fg="green") + f"Name: {name}")
|
|
110
|
+
|
|
111
|
+
# The plan and the rate limit headers are GitHub's alone. A Gitea-compatible
|
|
112
|
+
# forge answers /user without either, and printing "-" and "?/? remaining"
|
|
113
|
+
# would read as a fault rather than as an absence.
|
|
114
|
+
if forge.is_github():
|
|
115
|
+
plan = (data.get("plan") or {}).get("name", "-")
|
|
116
|
+
click.echo(click.style("[✔] ", fg="green") + f"Plan: {plan}")
|
|
117
|
+
click.echo(
|
|
118
|
+
click.style("[✔] ", fg="green")
|
|
119
|
+
+ f"Rate limit: {rate_remaining}/{rate_limit} remaining"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
click.echo()
|
|
123
|
+
click.secho(f"Credentials for {forge_host} OK.", fg="green")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
cli_command = 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,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,23 @@ 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
|
+
|
|
49
64
|
@requires_db
|
|
50
65
|
@click.command(
|
|
51
66
|
"db:migrate",
|
|
52
|
-
short_help="Generate a new migration from model changes
|
|
67
|
+
short_help="Generate a new migration from model changes (db:upgrade applies it).",
|
|
53
68
|
)
|
|
54
69
|
@click.argument("feature", required=False, default=None)
|
|
55
70
|
@click.option(
|
|
@@ -67,8 +82,8 @@ def _is_empty_migration(path: str) -> bool:
|
|
|
67
82
|
@context.requires_product
|
|
68
83
|
def db_migrate(feature, message, empty):
|
|
69
84
|
"""
|
|
70
|
-
Generate new migration files for features that have schema changes
|
|
71
|
-
|
|
85
|
+
Generate new migration files for features that have schema changes.
|
|
86
|
+
Nothing is applied: run ``splent db:upgrade`` for that.
|
|
72
87
|
|
|
73
88
|
Empty migrations (no schema changes detected) are automatically removed,
|
|
74
89
|
unless ``--empty`` is passed, in which case a blank migration is created
|
|
@@ -158,10 +173,23 @@ def db_migrate(feature, message, empty):
|
|
|
158
173
|
|
|
159
174
|
after = _count_versions(mdir)
|
|
160
175
|
|
|
176
|
+
newest = None
|
|
177
|
+
if after > before:
|
|
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
|
+
)
|
|
187
|
+
|
|
161
188
|
# A blank migration requested with --empty is intentional: keep it so the
|
|
162
189
|
# user can fill in upgrade()/downgrade() by hand (e.g. refinement columns).
|
|
163
190
|
if empty:
|
|
164
|
-
if
|
|
191
|
+
if newest:
|
|
192
|
+
_format_migration(newest)
|
|
165
193
|
click.echo(
|
|
166
194
|
click.style(
|
|
167
195
|
f" 📝 {feat}: blank migration generated — "
|
|
@@ -174,20 +202,12 @@ def db_migrate(feature, message, empty):
|
|
|
174
202
|
continue
|
|
175
203
|
|
|
176
204
|
# 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
|
-
)
|
|
205
|
+
if newest:
|
|
187
206
|
if _is_empty_migration(newest):
|
|
188
207
|
os.remove(newest)
|
|
189
208
|
click.echo(click.style(f" ✔ {feat}: up to date", fg="green"))
|
|
190
209
|
else:
|
|
210
|
+
_format_migration(newest)
|
|
191
211
|
click.echo(
|
|
192
212
|
click.style(f" 📝 {feat}: new migration generated", fg="cyan")
|
|
193
213
|
)
|
|
@@ -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")
|
|
@@ -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
|
)
|
|
@@ -233,6 +233,17 @@ def _print_diff(current: dict, inferred: dict) -> bool:
|
|
|
233
233
|
diff_lines.append(click.style(f" - archetype: {old_arch}", fg="red"))
|
|
234
234
|
diff_lines.append(click.style(f" + archetype: {new_arch}", fg="green"))
|
|
235
235
|
|
|
236
|
+
# extensible.routes is a boolean; a flip alone is still a change to write
|
|
237
|
+
old_routes = bool(current.get("extensible_routes", False))
|
|
238
|
+
new_routes = bool(inferred.get("extensible_routes", False))
|
|
239
|
+
if old_routes != new_routes:
|
|
240
|
+
diff_lines.append(
|
|
241
|
+
click.style(
|
|
242
|
+
f" ~ extensible.routes: {old_routes} → {new_routes}",
|
|
243
|
+
fg="green" if new_routes else "red",
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
|
|
236
247
|
for key, label in FIELDS:
|
|
237
248
|
diff_lines.extend(
|
|
238
249
|
_diff_field(label, current.get(key, []), inferred.get(key, []))
|
|
@@ -5,32 +5,46 @@ import requests
|
|
|
5
5
|
from splent_cli.commands.feature.feature_clone import feature_clone
|
|
6
6
|
from splent_cli.utils.cache_utils import make_feature_writable
|
|
7
7
|
from splent_cli.services import context
|
|
8
|
+
from splent_cli.utils import forge
|
|
8
9
|
from splent_cli.utils.feature_utils import normalize_namespace
|
|
9
10
|
|
|
10
11
|
|
|
12
|
+
def _forge_label() -> str:
|
|
13
|
+
"""How to name the forge in a message: the brand on GitHub, the host elsewhere."""
|
|
14
|
+
return "GitHub" if forge.is_github() else forge.host()
|
|
15
|
+
|
|
16
|
+
|
|
11
17
|
@click.command(
|
|
12
18
|
"feature:fork",
|
|
13
|
-
short_help="Fork a feature on
|
|
19
|
+
short_help="Fork a feature on the forge and clone it locally",
|
|
14
20
|
)
|
|
15
21
|
@click.argument("feature_name", required=True)
|
|
16
22
|
@click.option(
|
|
17
23
|
"--version", "-v", default="v1.0.0", help="Feature version (default: v1.0.0)"
|
|
18
24
|
)
|
|
19
25
|
def feature_fork(feature_name, version):
|
|
20
|
-
token =
|
|
26
|
+
token = forge.token()
|
|
21
27
|
github_user = os.getenv("GITHUB_USER")
|
|
22
28
|
|
|
23
29
|
if not token or not github_user:
|
|
24
|
-
click.secho(
|
|
30
|
+
click.secho(
|
|
31
|
+
f"✖ Error: {forge.token_env_name()} and GITHUB_USER must be set", fg="red"
|
|
32
|
+
)
|
|
25
33
|
raise SystemExit(1)
|
|
26
34
|
|
|
27
|
-
upstream_owner = "splent-io"
|
|
28
|
-
api_url = f"
|
|
35
|
+
upstream_owner = os.getenv("SPLENT_DEFAULT_ORG", "splent-io")
|
|
36
|
+
api_url = f"{forge.api_base()}/repos/{upstream_owner}/{feature_name}/forks"
|
|
29
37
|
headers = {
|
|
30
38
|
"Authorization": f"token {token}",
|
|
31
|
-
"Accept":
|
|
39
|
+
"Accept": (
|
|
40
|
+
"application/vnd.github+json" if forge.is_github() else "application/json"
|
|
41
|
+
),
|
|
32
42
|
}
|
|
33
43
|
|
|
44
|
+
# `default_branch_only` is a GitHub-only key. Gitea-compatible forges reject
|
|
45
|
+
# nothing but understand nothing either, so they get an empty body.
|
|
46
|
+
payload = {"default_branch_only": False} if forge.is_github() else {}
|
|
47
|
+
|
|
34
48
|
click.secho(
|
|
35
49
|
f"🔁 Forking {upstream_owner}/{feature_name} into {github_user}...", fg="cyan"
|
|
36
50
|
)
|
|
@@ -38,17 +52,17 @@ def feature_fork(feature_name, version):
|
|
|
38
52
|
resp = requests.post(
|
|
39
53
|
api_url,
|
|
40
54
|
headers=headers,
|
|
41
|
-
json=
|
|
55
|
+
json=payload,
|
|
42
56
|
timeout=30,
|
|
43
57
|
)
|
|
44
58
|
except requests.RequestException as e:
|
|
45
|
-
click.secho(f"❌ Could not reach
|
|
59
|
+
click.secho(f"❌ Could not reach {_forge_label()}: {e}", fg="red")
|
|
46
60
|
raise SystemExit(2)
|
|
47
61
|
|
|
48
62
|
if resp.status_code in (403, 429):
|
|
49
63
|
click.secho(
|
|
50
|
-
"⚠
|
|
51
|
-
"Set
|
|
64
|
+
f"⚠ {_forge_label()} API rate limit or access denied. "
|
|
65
|
+
f"Set {forge.token_env_name()} to a token with repo scope and try again.",
|
|
52
66
|
fg="yellow",
|
|
53
67
|
)
|
|
54
68
|
if os.getenv("SPLENT_DEBUG"):
|
|
@@ -73,22 +87,24 @@ def feature_fork(feature_name, version):
|
|
|
73
87
|
raise SystemExit(2)
|
|
74
88
|
click.secho(f"🌐 Fork created: {fork_html_url}", fg="green")
|
|
75
89
|
|
|
76
|
-
# Esperar a que
|
|
90
|
+
# Esperar a que la forja lo procese
|
|
77
91
|
for i in range(5):
|
|
78
92
|
try:
|
|
79
93
|
r = requests.get(
|
|
80
|
-
f"
|
|
94
|
+
f"{forge.api_base()}/repos/{github_user}/{feature_name}",
|
|
81
95
|
headers=headers,
|
|
82
96
|
timeout=30,
|
|
83
97
|
)
|
|
84
98
|
except requests.RequestException as e:
|
|
85
|
-
click.secho(
|
|
99
|
+
click.secho(
|
|
100
|
+
f"⌛ Waiting for {_forge_label()} to process fork... ({e})", fg="yellow"
|
|
101
|
+
)
|
|
86
102
|
time.sleep(3)
|
|
87
103
|
continue
|
|
88
104
|
if r.status_code == 200:
|
|
89
105
|
click.secho("✅ Fork is ready.", fg="green")
|
|
90
106
|
break
|
|
91
|
-
click.secho("⌛ Waiting for
|
|
107
|
+
click.secho(f"⌛ Waiting for {_forge_label()} to process fork...", fg="yellow")
|
|
92
108
|
time.sleep(3)
|
|
93
109
|
else:
|
|
94
110
|
click.secho("⚠ Fork may still be processing.", fg="yellow")
|
|
@@ -65,7 +65,7 @@ def feature_impact(feature_ref):
|
|
|
65
65
|
click.secho(" pyproject.toml not found.", fg="red")
|
|
66
66
|
raise SystemExit(1)
|
|
67
67
|
|
|
68
|
-
uvl = _uvl_path(product_dir)
|
|
68
|
+
uvl = _uvl_path(product_dir, workspace)
|
|
69
69
|
if not uvl or not os.path.isfile(uvl):
|
|
70
70
|
click.secho(" No UVL file found — cannot compute impact.", fg="red")
|
|
71
71
|
raise SystemExit(1)
|