splent-cli 1.36.0__tar.gz → 1.38.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.36.0/src/splent_cli.egg-info → splent_cli-1.38.0}/PKG-INFO +1 -1
- {splent_cli-1.36.0 → splent_cli-1.38.0}/pyproject.toml +1 -1
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/check/check_product.py +59 -1
- splent_cli-1.38.0/src/splent_cli/commands/check/check_vendor.py +271 -0
- splent_cli-1.38.0/src/splent_cli/commands/clear/clear_uploads.py +298 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/database/db_reset.py +4 -2
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/database/db_upgrade.py +51 -4
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/export/export_puml.py +3 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_clean.py +4 -3
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_contract.py +53 -4
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_create.py +42 -6
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_info.py +1 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_refine.py +1 -1
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_release.py +373 -8
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_test.py +340 -74
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_build.py +7 -4
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_clean.py +4 -3
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_create.py +33 -19
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_deploy.py +97 -16
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_derive.py +7 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_env.py +57 -12
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_sync_template.py +3 -2
- splent_cli-1.38.0/src/splent_cli/commands/product/product_test.py +274 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_validate.py +181 -7
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/services/context.py +25 -11
- splent_cli-1.38.0/src/splent_cli/services/neutral_app.py +567 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/services/release.py +61 -11
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/services/release_dry_run.py +8 -3
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/services/web_container.py +9 -6
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_.gitignore.j2 +9 -8
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_MANIFEST.in.j2 +12 -2
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_pyproject.toml.j2 +26 -3
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_pyproject_minimal.toml.j2 +23 -2
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_.env.prod.example.j2 +14 -7
- splent_cli-1.38.0/src/splent_cli/templates/product/product_03_initialize_migrations.sh.j2 +38 -0
- splent_cli-1.38.0/src/splent_cli/templates/product/product_04_handle_migrations.sh.j2 +32 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_ci_validate.yml.j2 +62 -12
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/ci_services.py +41 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/ci_workflow.py +3 -2
- splent_cli-1.38.0/src/splent_cli/utils/menu_keys.py +424 -0
- splent_cli-1.38.0/src/splent_cli/utils/migration_lock.py +98 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0/src/splent_cli.egg-info}/PKG-INFO +1 -1
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli.egg-info/SOURCES.txt +4 -0
- splent_cli-1.36.0/src/splent_cli/commands/clear/clear_uploads.py +0 -110
- splent_cli-1.36.0/src/splent_cli/commands/product/product_test.py +0 -141
- splent_cli-1.36.0/src/splent_cli/templates/product/product_03_initialize_migrations.sh.j2 +0 -23
- splent_cli-1.36.0/src/splent_cli/templates/product/product_04_handle_migrations.sh.j2 +0 -22
- {splent_cli-1.36.0 → splent_cli-1.38.0}/COPYING +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/COPYING.LESSER +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/README.md +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/setup.cfg +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/cli.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/check/check_contracts.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/check/check_deps.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/check/check_env.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/check/check_features.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/check/check_github.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/check/check_infra.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/check/check_releases.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/clear/__init__.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/command/__init__.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/command/command_create.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/database/db_console.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/database/db_dump.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/database/db_seed.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/database/db_status.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/doctor.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/export/__init__.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_attach.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_compile.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_detach.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_remove.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_unlock.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/lint.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/locust.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/marketplace/login.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/marketplace/logout.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/marketplace/whoami.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_commands.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_config.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_configure.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_console.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_containers.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_down.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_drift.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_missing.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_release.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_restart.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_routes.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_run.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_select.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_signals.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/product/product_up.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/release/release_core.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/release/release_resume.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/spl/__init__.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/spl/spl_pin.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/spl/spl_publish.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/tokens_setup.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/commands/version.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/services/compose.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/services/credentials.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/services/deploy_bundle.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/services/image_registry.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/services/index_refresh.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/services/marketplace.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/services/marketplace_api.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/services/marketplace_url.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/services/preflight.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/services/pypi_quota.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/services/registry.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/services/release_gate.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/services/spl_store.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_README.md.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_commands.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_config.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_docker-compose.dev.yml.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_docker-compose.prod.yml.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_docker.env.example.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_forms.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_hooks.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_init.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_init_config.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_init_light.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_init_service.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_migrations_alembic.ini.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_migrations_env.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_migrations_script.py.mako.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_models.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_repositories.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_routes.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_scripts.js.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_seeders.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_services.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_signals.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_templates_index.html.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_tests_conftest.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_tests_e2e.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_tests_functional.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_tests_integration.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_tests_locustfile.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_tests_unit.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/feature/feature_webpack.config.js.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/licenses/COPYING +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/licenses/COPYING.LESSER +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_.env.dev.example.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_.gitignore.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_00_core_requirements_dev.sh.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_00_install_features.sh.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_01_compile_assets.sh.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_02_0_db_wait_connection.sh.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_02_1_db_create_db_test.sh.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_02_2_db_create_splent_migrations.sh.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_05_0_start_app_dev.sh.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_05_1_start_app_prod.sh.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_400.html +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_401.html +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_404.html +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_500.html +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_Dockerfile.dev.dockerignore.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_Dockerfile.dev.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_Dockerfile.prod.dockerignore.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_Dockerfile.prod.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_README.md.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_app.css +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_app.js +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_base_template.html +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_ci_upgrade.yml.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_config.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_docker-compose.dev.yml.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_docker-compose.prod.yml.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_dropzone.css +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_entrypoint.dev.sh.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_entrypoint.prod.sh.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_errors.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_favicon.svg +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_init.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_logging.py.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_own.css +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_package.json.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_pyproject.toml.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/templates/product/product_uvl.j2 +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/archetype.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/cache_utils.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/contract_freshness.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/db_client.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/feature_naming.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/feature_requirements.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/feature_resolution.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/feature_utils.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/forge.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/git_url.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/integrity.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/io_utils.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/lifecycle.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/manifest.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/proc.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/redact.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/refinement_facts.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/running_version.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/template_drift.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/toml_edit.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli/utils/upgrade_plan.py +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli.egg-info/requires.txt +0 -0
- {splent_cli-1.36.0 → splent_cli-1.38.0}/src/splent_cli.egg-info/top_level.txt +0 -0
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
check:product — Product-level health checks.
|
|
3
3
|
|
|
4
4
|
Validates that the active product is correctly wired:
|
|
5
|
-
1. Required env vars from feature contracts are set
|
|
5
|
+
1. Required env vars from feature contracts are set, and the app.config
|
|
6
|
+
keys the contracts read (requires.config_keys) are listed, set or not
|
|
6
7
|
2. Feature symlinks resolve to real directories
|
|
7
8
|
3. Config overwrites are flagged
|
|
8
9
|
4. Features that loaded but registered no blueprints are warned
|
|
@@ -176,6 +177,62 @@ def _check_env_vars(workspace, product_path, features, counters):
|
|
|
176
177
|
counters["ok"] += 1
|
|
177
178
|
|
|
178
179
|
|
|
180
|
+
def _check_config_keys(workspace, product_path, features):
|
|
181
|
+
"""List the app.config keys the features read and no feature sets.
|
|
182
|
+
|
|
183
|
+
``requires.config_keys`` of each contract. They are not environment
|
|
184
|
+
variables: a feature reads most of them with a default, and a list or a
|
|
185
|
+
table (theme's SITE_NAV) can only come from the product's config.py. So
|
|
186
|
+
a key the product leaves alone is shown, never counted as a failure,
|
|
187
|
+
which is what made check:product fail for every product once the
|
|
188
|
+
contracts of auth and theme listed them among the env_vars.
|
|
189
|
+
"""
|
|
190
|
+
var_features: dict[str, list[str]] = {}
|
|
191
|
+
for entry in features:
|
|
192
|
+
ns_safe, name, version = parse_feature_entry(entry)
|
|
193
|
+
data = _resolve_feature_pyproject(
|
|
194
|
+
workspace, product_path, ns_safe, name, version, None
|
|
195
|
+
)
|
|
196
|
+
keys = (
|
|
197
|
+
(data or {})
|
|
198
|
+
.get("tool", {})
|
|
199
|
+
.get("splent", {})
|
|
200
|
+
.get("contract", {})
|
|
201
|
+
.get("requires", {})
|
|
202
|
+
.get("config_keys", [])
|
|
203
|
+
)
|
|
204
|
+
for key in keys if isinstance(keys, list) else []:
|
|
205
|
+
var_features.setdefault(str(key), []).append(_short(name))
|
|
206
|
+
if not var_features:
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
click.echo()
|
|
210
|
+
click.secho(" Config Keys", bold=True, fg="cyan")
|
|
211
|
+
click.echo()
|
|
212
|
+
|
|
213
|
+
config = None
|
|
214
|
+
try:
|
|
215
|
+
from splent_cli.utils.dynamic_imports import get_app
|
|
216
|
+
|
|
217
|
+
config = get_app().config
|
|
218
|
+
except Exception:
|
|
219
|
+
pass
|
|
220
|
+
|
|
221
|
+
col_var = 30
|
|
222
|
+
col_feat = 25
|
|
223
|
+
click.echo(f" {'Key':<{col_var}} {'Feature(s)':<{col_feat}} Status")
|
|
224
|
+
click.echo(f" {'-' * col_var} {'-' * col_feat} {'-' * 24}")
|
|
225
|
+
for key in sorted(var_features):
|
|
226
|
+
feats = ", ".join(sorted(var_features[key]))
|
|
227
|
+
if config is None:
|
|
228
|
+
status = click.style("? app not built", fg="bright_black")
|
|
229
|
+
elif key in config:
|
|
230
|
+
status = click.style("✔ set", fg="green")
|
|
231
|
+
else:
|
|
232
|
+
status = click.style("· the feature's default", fg="bright_black")
|
|
233
|
+
click.echo(f" {key:<{col_var}} {feats:<{col_feat}} {status}")
|
|
234
|
+
|
|
235
|
+
|
|
179
236
|
# ── Symlinks ─────────────────────────────────────────────────────────────────
|
|
180
237
|
|
|
181
238
|
|
|
@@ -434,6 +491,7 @@ def check_product():
|
|
|
434
491
|
check_and_refresh_contracts(workspace, features, interactive=False)
|
|
435
492
|
|
|
436
493
|
_check_env_vars(workspace, product_path, features, counters)
|
|
494
|
+
_check_config_keys(workspace, product_path, features)
|
|
437
495
|
_check_symlinks(product_path, features, counters)
|
|
438
496
|
_check_config_overwrites(counters)
|
|
439
497
|
_check_blueprints(counters)
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""
|
|
2
|
+
check:vendor — Find Keenthemes (Metronic) code in a product and its features.
|
|
3
|
+
|
|
4
|
+
Metronic is Keenthemes' commercial theme, and its licence does not allow
|
|
5
|
+
redistributing it, so neither a product of the line nor a feature any
|
|
6
|
+
product can pin may carry it. A feature written against it also breaks as
|
|
7
|
+
soon as the product that ships its files drops them: its icons go blank and
|
|
8
|
+
its scripts call globals nobody defines.
|
|
9
|
+
|
|
10
|
+
It is found by content, not by where it sits: the globals its JavaScript
|
|
11
|
+
defines, its icon classes, its data attributes and its name, in the
|
|
12
|
+
product's own tree and in the src/ of every feature the product declares,
|
|
13
|
+
at the copy the product runs. Only Duotune's icons, SVG with no mark of
|
|
14
|
+
their own, are found by the name in their path. tests/ is left out
|
|
15
|
+
everywhere, since a test may name the patterns it guards against.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
|
|
21
|
+
import click
|
|
22
|
+
|
|
23
|
+
from splent_cli.services import context
|
|
24
|
+
|
|
25
|
+
# (name, regular expression). The globals and classes are matched as the
|
|
26
|
+
# start of a word, the name in any case.
|
|
27
|
+
PATTERNS = (
|
|
28
|
+
("KTUtil", r"\bKTUtil"),
|
|
29
|
+
("KTComponents", r"\bKTComponents"),
|
|
30
|
+
("KTApp", r"\bKTApp"),
|
|
31
|
+
("KTMenu", r"\bKTMenu"),
|
|
32
|
+
("KTStepper", r"\bKTStepper"),
|
|
33
|
+
("keenthemes", r"(?i:keenthemes)"),
|
|
34
|
+
("Metronic", r"(?i:\bmetronic\b)"),
|
|
35
|
+
("ki-duotone", r"\bki-duotone\b"),
|
|
36
|
+
("ki-outline", r"\bki-outline\b"),
|
|
37
|
+
("ki-solid", r"\bki-solid\b"),
|
|
38
|
+
("data-kt-", r"\bdata-kt-"),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
_PATTERN = re.compile(
|
|
42
|
+
"|".join(f"(?P<p{i}>{regex})" for i, (_, regex) in enumerate(PATTERNS))
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Keenthemes' names in a file or folder name, for Duotune, its icon set.
|
|
46
|
+
_PATH_PATTERN = re.compile(r"(?i)duotune|keenicons|keenthemes|metronic")
|
|
47
|
+
|
|
48
|
+
# Never searched: tests, installed packages, caches, and hidden folders
|
|
49
|
+
# (.git, virtual environments).
|
|
50
|
+
SKIPPED_DIRS = {"tests", "node_modules", "__pycache__"}
|
|
51
|
+
|
|
52
|
+
# Also left out of the product's tree: what the app writes while it runs.
|
|
53
|
+
# Its features/ folder, the links product:resolve makes, is skipped at the
|
|
54
|
+
# top, and each feature is searched on its own.
|
|
55
|
+
SKIPPED_PRODUCT_DIRS = SKIPPED_DIRS | {"instance", "uploads"}
|
|
56
|
+
|
|
57
|
+
# Read as bytes first: a file with a NUL byte is binary and has no text to
|
|
58
|
+
# search. These are not even opened.
|
|
59
|
+
BINARY_SUFFIXES = (
|
|
60
|
+
".png",
|
|
61
|
+
".jpg",
|
|
62
|
+
".jpeg",
|
|
63
|
+
".gif",
|
|
64
|
+
".webp",
|
|
65
|
+
".ico",
|
|
66
|
+
".bmp",
|
|
67
|
+
".woff",
|
|
68
|
+
".woff2",
|
|
69
|
+
".ttf",
|
|
70
|
+
".otf",
|
|
71
|
+
".eot",
|
|
72
|
+
".pdf",
|
|
73
|
+
".zip",
|
|
74
|
+
".gz",
|
|
75
|
+
".tgz",
|
|
76
|
+
".mp4",
|
|
77
|
+
".webm",
|
|
78
|
+
".mp3",
|
|
79
|
+
".pyc",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Files listed per place before the rest is counted, unless --all.
|
|
83
|
+
SHOWN_PER_PLACE = 10
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def scan_file(path: str) -> dict | None:
|
|
87
|
+
"""{"line": first line, "patterns": [...], "count": matches} for a file
|
|
88
|
+
with Keenthemes code, or None."""
|
|
89
|
+
try:
|
|
90
|
+
with open(path, "rb") as f:
|
|
91
|
+
raw = f.read()
|
|
92
|
+
except OSError:
|
|
93
|
+
return None
|
|
94
|
+
if b"\0" in raw[:8192]:
|
|
95
|
+
return None
|
|
96
|
+
first = None
|
|
97
|
+
names: set[str] = set()
|
|
98
|
+
count = 0
|
|
99
|
+
for number, line in enumerate(raw.decode("utf-8", "replace").splitlines(), 1):
|
|
100
|
+
for match in _PATTERN.finditer(line):
|
|
101
|
+
names.add(PATTERNS[int(match.lastgroup[1:])][0])
|
|
102
|
+
count += 1
|
|
103
|
+
if first is None:
|
|
104
|
+
first = number
|
|
105
|
+
if not count:
|
|
106
|
+
return None
|
|
107
|
+
order = [name for name, _ in PATTERNS]
|
|
108
|
+
return {"line": first, "patterns": sorted(names, key=order.index), "count": count}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def scan_tree(root: str, *, skip=SKIPPED_DIRS, skip_at_root=()) -> dict[str, dict]:
|
|
112
|
+
"""{path relative to root: hit} for every file of the tree with
|
|
113
|
+
Keenthemes code, or a Keenthemes name in its path under root. A hit is
|
|
114
|
+
scan_file()'s, and a file found by its path alone has no line and a
|
|
115
|
+
count of 0. Symbolic links to folders are not followed."""
|
|
116
|
+
found: dict[str, dict] = {}
|
|
117
|
+
for current, dirs, files in os.walk(root):
|
|
118
|
+
dirs[:] = sorted(
|
|
119
|
+
d
|
|
120
|
+
for d in dirs
|
|
121
|
+
if not d.startswith(".")
|
|
122
|
+
and d not in skip
|
|
123
|
+
and not (current == root and d in skip_at_root)
|
|
124
|
+
)
|
|
125
|
+
for name in sorted(files):
|
|
126
|
+
path = os.path.join(current, name)
|
|
127
|
+
relative = os.path.relpath(path, root)
|
|
128
|
+
named = _PATH_PATTERN.search(relative)
|
|
129
|
+
hit = None
|
|
130
|
+
if not name.lower().endswith(BINARY_SUFFIXES):
|
|
131
|
+
hit = scan_file(path)
|
|
132
|
+
if named:
|
|
133
|
+
hit = hit or {"line": None, "patterns": [], "count": 0}
|
|
134
|
+
hit["patterns"].insert(0, f"{named.group().lower()} in its path")
|
|
135
|
+
if hit:
|
|
136
|
+
found[relative] = hit
|
|
137
|
+
return found
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _feature_places(workspace: str, product_dir: str, data: dict):
|
|
141
|
+
"""(label, src folder or None) for every feature the product declares,
|
|
142
|
+
in every environment, at the copy the product runs."""
|
|
143
|
+
from splent_cli.utils.feature_utils import read_all_feature_entries
|
|
144
|
+
from splent_cli.utils.refinement_facts import product_source
|
|
145
|
+
|
|
146
|
+
for entry in read_all_feature_entries(data):
|
|
147
|
+
source = product_source(workspace, product_dir, entry)
|
|
148
|
+
label = f"{source.name}@{source.version}" if source.version else source.name
|
|
149
|
+
src = source.root / "src" if source.root is not None else None
|
|
150
|
+
yield label, (str(src) if src is not None and src.is_dir() else None)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _report_place(label: str, where: str, found: dict, show_all: bool, tone):
|
|
154
|
+
click.echo(
|
|
155
|
+
click.style(f" {label}", bold=True) + click.style(f" ({where})", dim=True)
|
|
156
|
+
)
|
|
157
|
+
paths = sorted(found)
|
|
158
|
+
shown = paths if show_all else paths[:SHOWN_PER_PLACE]
|
|
159
|
+
for path in shown:
|
|
160
|
+
hit = found[path]
|
|
161
|
+
location = f"{path}:{hit['line']}" if hit["line"] else path
|
|
162
|
+
what = ", ".join(hit["patterns"])
|
|
163
|
+
if hit["count"]:
|
|
164
|
+
what += " (1 match)" if hit["count"] == 1 else f" ({hit['count']} matches)"
|
|
165
|
+
click.secho(f" {tone[0]} {location} {what}", fg=tone[1])
|
|
166
|
+
if len(paths) > len(shown):
|
|
167
|
+
click.secho(
|
|
168
|
+
f" … and {len(paths) - len(shown)} more files (--all lists them)",
|
|
169
|
+
fg=tone[1],
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@click.command(
|
|
174
|
+
"check:vendor",
|
|
175
|
+
short_help="Find Keenthemes (Metronic) code in the product and its features.",
|
|
176
|
+
)
|
|
177
|
+
@click.option(
|
|
178
|
+
"--warn",
|
|
179
|
+
is_flag=True,
|
|
180
|
+
help="Report what is found without failing, while a product moves off it.",
|
|
181
|
+
)
|
|
182
|
+
@click.option("--all", "show_all", is_flag=True, help="List every file found.")
|
|
183
|
+
def check_vendor(warn, show_all):
|
|
184
|
+
"""
|
|
185
|
+
Search the active product and the features it declares for Keenthemes
|
|
186
|
+
code: Metronic's globals (KTUtil, KTApp, KTMenu, KTStepper,
|
|
187
|
+
KTComponents), its icon classes (ki-duotone, ki-outline, ki-solid), its
|
|
188
|
+
data-kt- attributes and the names Keenthemes and Metronic, and files
|
|
189
|
+
with Duotune or Keenthemes in their path.
|
|
190
|
+
|
|
191
|
+
\b
|
|
192
|
+
It reads the product's tree and the src/ of each feature, at the
|
|
193
|
+
release the product pins (or the working copy of an editable one),
|
|
194
|
+
and leaves out tests/, node_modules/ and hidden folders. A feature with
|
|
195
|
+
no local copy is said and not searched.
|
|
196
|
+
|
|
197
|
+
\b
|
|
198
|
+
Exits 1 when anything is found. --warn reports it and exits 0.
|
|
199
|
+
|
|
200
|
+
\b
|
|
201
|
+
Examples:
|
|
202
|
+
splent check:vendor
|
|
203
|
+
splent check:vendor --warn
|
|
204
|
+
splent check:vendor --all
|
|
205
|
+
"""
|
|
206
|
+
from splent_cli.commands.uvl.uvl_utils import load_pyproject
|
|
207
|
+
|
|
208
|
+
workspace = str(context.workspace())
|
|
209
|
+
product = context.require_app()
|
|
210
|
+
product_dir = os.path.join(workspace, product)
|
|
211
|
+
data = load_pyproject(os.path.join(product_dir, "pyproject.toml"))
|
|
212
|
+
tone = ("⚠️ ", "yellow") if warn else ("✗", "red")
|
|
213
|
+
|
|
214
|
+
click.echo()
|
|
215
|
+
click.echo(click.style(f" check:vendor {product}", bold=True))
|
|
216
|
+
click.echo(click.style(f" {'-' * 60}", fg="bright_black"))
|
|
217
|
+
|
|
218
|
+
places: list[tuple[str, int]] = []
|
|
219
|
+
found = scan_tree(
|
|
220
|
+
product_dir, skip=SKIPPED_PRODUCT_DIRS, skip_at_root=("features",)
|
|
221
|
+
)
|
|
222
|
+
if found:
|
|
223
|
+
_report_place(product, "the product's tree", found, show_all, tone)
|
|
224
|
+
places.append((product, len(found)))
|
|
225
|
+
|
|
226
|
+
clean = 0
|
|
227
|
+
unchecked: list[str] = []
|
|
228
|
+
for label, src in _feature_places(workspace, product_dir, data):
|
|
229
|
+
if src is None:
|
|
230
|
+
unchecked.append(label)
|
|
231
|
+
continue
|
|
232
|
+
found = scan_tree(src)
|
|
233
|
+
if found:
|
|
234
|
+
_report_place(label, "src/", found, show_all, tone)
|
|
235
|
+
places.append((label, len(found)))
|
|
236
|
+
else:
|
|
237
|
+
clean += 1
|
|
238
|
+
|
|
239
|
+
click.echo()
|
|
240
|
+
if unchecked:
|
|
241
|
+
click.secho(
|
|
242
|
+
f" ℹ️ Not searched, no local copy: {', '.join(unchecked)}",
|
|
243
|
+
fg="bright_black",
|
|
244
|
+
)
|
|
245
|
+
if not places:
|
|
246
|
+
click.secho(
|
|
247
|
+
f" ✅ No Keenthemes code in {product} or its "
|
|
248
|
+
f"{clean} feature{'s' if clean != 1 else ''}",
|
|
249
|
+
fg="green",
|
|
250
|
+
)
|
|
251
|
+
click.echo()
|
|
252
|
+
return
|
|
253
|
+
|
|
254
|
+
files = sum(n for _, n in places)
|
|
255
|
+
features = [label for label, _ in places if label != product]
|
|
256
|
+
summary = f"{files} file{'s' if files != 1 else ''} with Keenthemes code"
|
|
257
|
+
if features:
|
|
258
|
+
summary += f", in {len(features)} feature{'s' if len(features) != 1 else ''}"
|
|
259
|
+
if places[0][0] == product:
|
|
260
|
+
summary += " and the product"
|
|
261
|
+
click.secho(f" {tone[0]} {summary}", fg=tone[1], bold=True)
|
|
262
|
+
click.echo(
|
|
263
|
+
" Replace it with Bootstrap, feather icons or code of your own. In a "
|
|
264
|
+
"feature, that takes a release the product then pins."
|
|
265
|
+
)
|
|
266
|
+
click.echo()
|
|
267
|
+
if not warn:
|
|
268
|
+
raise SystemExit(1)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
cli_command = check_vendor
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from splent_cli.utils.path_utils import PathUtils
|
|
8
|
+
from splent_cli.services import context
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class UploadsDirError(Exception):
|
|
12
|
+
"""The uploads directory of the active product cannot be resolved safely."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Top-level directories of a system or a container. No product keeps its
|
|
16
|
+
# uploads in one of them, and emptying one would take the machine with it.
|
|
17
|
+
_SYSTEM_DIRS = {
|
|
18
|
+
"/bin",
|
|
19
|
+
"/boot",
|
|
20
|
+
"/dev",
|
|
21
|
+
"/etc",
|
|
22
|
+
"/home",
|
|
23
|
+
"/lib",
|
|
24
|
+
"/lib64",
|
|
25
|
+
"/opt",
|
|
26
|
+
"/proc",
|
|
27
|
+
"/root",
|
|
28
|
+
"/run",
|
|
29
|
+
"/sbin",
|
|
30
|
+
"/srv",
|
|
31
|
+
"/sys",
|
|
32
|
+
"/tmp",
|
|
33
|
+
"/usr",
|
|
34
|
+
"/var",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# What a directory of code has at its top and a directory of uploads never
|
|
38
|
+
# has. A PROTECTED_UPLOADS_DIR pointing at a checkout is refused on sight.
|
|
39
|
+
_CODE_MARKERS = ("pyproject.toml", "setup.py", ".git", "__init__.py")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _instance_path(product: str, product_dir: str) -> str:
|
|
43
|
+
"""Flask's instance folder for the product's app.
|
|
44
|
+
|
|
45
|
+
A product running from its source tree, as in development, gets
|
|
46
|
+
``<product>/src/instance``. One installed outside it, as the production
|
|
47
|
+
image installs it, gets the folder Flask picks for an installed package.
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
spec = importlib.util.find_spec(product)
|
|
51
|
+
except (ImportError, ValueError):
|
|
52
|
+
spec = None
|
|
53
|
+
# A namespace match (the product directory itself on sys.path) has no
|
|
54
|
+
# origin and says nothing about where the app runs from.
|
|
55
|
+
origin = os.path.realpath(spec.origin) if spec and spec.origin else ""
|
|
56
|
+
if origin and not origin.startswith(product_dir + os.sep):
|
|
57
|
+
try:
|
|
58
|
+
from flask.sansio.scaffold import find_package
|
|
59
|
+
except ImportError:
|
|
60
|
+
find_package = None
|
|
61
|
+
if find_package is not None:
|
|
62
|
+
prefix, package_path = find_package(product)
|
|
63
|
+
if prefix is not None:
|
|
64
|
+
return os.path.join(prefix, "var", f"{product}-instance")
|
|
65
|
+
return os.path.join(package_path, "instance")
|
|
66
|
+
return os.path.join(product_dir, "src", "instance")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _configured_dir(app) -> str:
|
|
70
|
+
"""PROTECTED_UPLOADS_DIR as the app reads it: its config, then the
|
|
71
|
+
environment. Every command starts with the product's env files loaded
|
|
72
|
+
(utils/dynamic_imports), so the environment here is the app's."""
|
|
73
|
+
if app is not None:
|
|
74
|
+
configured = app.config.get("PROTECTED_UPLOADS_DIR")
|
|
75
|
+
if configured:
|
|
76
|
+
return str(configured)
|
|
77
|
+
return os.getenv("PROTECTED_UPLOADS_DIR", "").strip()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _refuse(path: str, real: str, why: str):
|
|
81
|
+
shown = path if path == real else f"{path} (resolves to {real})"
|
|
82
|
+
raise UploadsDirError(f"{shown} {why}. Nothing was deleted.")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def resolve_uploads_dir(app=None) -> str:
|
|
86
|
+
"""Return the directory where the product's app keeps protected uploads.
|
|
87
|
+
|
|
88
|
+
That is where the files of the hub live (hubfile and dataset write under
|
|
89
|
+
it, media keeps its restricted files there): PROTECTED_UPLOADS_DIR, which
|
|
90
|
+
the production compose mounts as a named volume, or, when it is empty,
|
|
91
|
+
``<instance_path>/protected_uploads``, the framework's fallback
|
|
92
|
+
(splent_framework.utils.path_utils.protected_uploads_dir). With ``app``
|
|
93
|
+
the path comes from the app itself; without it, from the environment and
|
|
94
|
+
the product on disk, so the product does not need to be importable.
|
|
95
|
+
|
|
96
|
+
Raises UploadsDirError when no product is selected, the product directory
|
|
97
|
+
does not exist, or the path is one no uploads directory can be: relative,
|
|
98
|
+
the root, a system directory, the home directory, the workspace, the
|
|
99
|
+
product or its package, anything holding one of them, or a directory with
|
|
100
|
+
code at its top.
|
|
101
|
+
"""
|
|
102
|
+
product = os.getenv("SPLENT_APP", "")
|
|
103
|
+
if not product or product in (".", "..") or os.sep in product or "/" in product:
|
|
104
|
+
raise UploadsDirError(f"'{product}' is not a valid product name.")
|
|
105
|
+
|
|
106
|
+
product_dir = os.path.realpath(PathUtils.get_app_base_dir())
|
|
107
|
+
if not os.path.isdir(product_dir):
|
|
108
|
+
raise UploadsDirError(f"Product directory not found: {product_dir}")
|
|
109
|
+
|
|
110
|
+
configured = _configured_dir(app)
|
|
111
|
+
if configured:
|
|
112
|
+
uploads_dir = configured
|
|
113
|
+
elif app is not None:
|
|
114
|
+
uploads_dir = os.path.join(app.instance_path, "protected_uploads")
|
|
115
|
+
else:
|
|
116
|
+
uploads_dir = os.path.join(
|
|
117
|
+
_instance_path(product, product_dir), "protected_uploads"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if not os.path.isabs(uploads_dir):
|
|
121
|
+
raise UploadsDirError(
|
|
122
|
+
f"PROTECTED_UPLOADS_DIR={uploads_dir} is not an absolute path, so it "
|
|
123
|
+
"depends on where the app was started. Nothing was deleted."
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
real = os.path.realpath(uploads_dir)
|
|
127
|
+
if real == os.sep or real in _SYSTEM_DIRS:
|
|
128
|
+
_refuse(uploads_dir, real, "is a system directory")
|
|
129
|
+
if real == os.path.realpath(os.path.expanduser("~")):
|
|
130
|
+
_refuse(uploads_dir, real, "is the home directory")
|
|
131
|
+
|
|
132
|
+
package_dir = os.path.join(product_dir, "src", product)
|
|
133
|
+
guarded = [product_dir, package_dir]
|
|
134
|
+
workspace = PathUtils.get_working_dir()
|
|
135
|
+
if workspace:
|
|
136
|
+
guarded.append(os.path.realpath(workspace))
|
|
137
|
+
for kept in guarded:
|
|
138
|
+
if real == kept or kept.startswith(real + os.sep):
|
|
139
|
+
_refuse(uploads_dir, real, f"holds {kept}")
|
|
140
|
+
if real.startswith(package_dir + os.sep):
|
|
141
|
+
_refuse(uploads_dir, real, f"is inside the product's package {package_dir}")
|
|
142
|
+
for marker in _CODE_MARKERS:
|
|
143
|
+
if os.path.lexists(os.path.join(real, marker)):
|
|
144
|
+
_refuse(uploads_dir, real, f"has a {marker}, so it holds code, not uploads")
|
|
145
|
+
return real
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def clear_product_uploads(app=None) -> list[str]:
|
|
149
|
+
"""Delete the contents of the active product's protected uploads.
|
|
150
|
+
|
|
151
|
+
Prints the outcome and returns the errors (empty when everything was
|
|
152
|
+
cleared or there was nothing to clear). It never raises SystemExit, so
|
|
153
|
+
db:reset can call it after dropping the schema and still re-apply the
|
|
154
|
+
migrations. It does not ask: its callers do.
|
|
155
|
+
"""
|
|
156
|
+
try:
|
|
157
|
+
uploads_dir = resolve_uploads_dir(app)
|
|
158
|
+
except UploadsDirError as e:
|
|
159
|
+
click.echo(click.style(f"Cannot clear the uploads directory. {e}", fg="red"))
|
|
160
|
+
return [str(e)]
|
|
161
|
+
|
|
162
|
+
if not os.path.isdir(uploads_dir):
|
|
163
|
+
click.echo(
|
|
164
|
+
click.style(
|
|
165
|
+
f"The uploads directory {uploads_dir} does not exist.", fg="yellow"
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
return []
|
|
169
|
+
|
|
170
|
+
errors = []
|
|
171
|
+
try:
|
|
172
|
+
entries = sorted(os.listdir(uploads_dir))
|
|
173
|
+
except OSError as e:
|
|
174
|
+
entries = []
|
|
175
|
+
errors.append(f"{uploads_dir}: {e}")
|
|
176
|
+
|
|
177
|
+
for filename in entries:
|
|
178
|
+
file_path = os.path.join(uploads_dir, filename)
|
|
179
|
+
try:
|
|
180
|
+
# A symlink is removed itself, never the directory it points to
|
|
181
|
+
if os.path.islink(file_path) or not os.path.isdir(file_path):
|
|
182
|
+
os.remove(file_path)
|
|
183
|
+
else:
|
|
184
|
+
shutil.rmtree(file_path)
|
|
185
|
+
except OSError as e:
|
|
186
|
+
errors.append(f"{filename}: {e}")
|
|
187
|
+
|
|
188
|
+
if errors:
|
|
189
|
+
click.echo(
|
|
190
|
+
click.style(
|
|
191
|
+
f"Error clearing the uploads directory {uploads_dir}. "
|
|
192
|
+
f"Could not delete {len(errors)} "
|
|
193
|
+
f"{'entry' if len(errors) == 1 else 'entries'}.",
|
|
194
|
+
fg="red",
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
for err in errors:
|
|
198
|
+
click.echo(click.style(f" {err}", fg="red"))
|
|
199
|
+
else:
|
|
200
|
+
click.echo(
|
|
201
|
+
click.style(
|
|
202
|
+
f"The contents of the uploads directory {uploads_dir} have been "
|
|
203
|
+
"successfully cleared.",
|
|
204
|
+
fg="green",
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
return errors
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _run_where_the_uploads_are(product: str, env: str, yes: bool) -> None:
|
|
211
|
+
"""Run this command in the product's web container when it is up there.
|
|
212
|
+
|
|
213
|
+
PROTECTED_UPLOADS_DIR is a path inside that container, and in production
|
|
214
|
+
a named volume only that container mounts: emptying the same path from
|
|
215
|
+
the CLI container would empty a directory the app never reads, and say
|
|
216
|
+
it worked. That container is ``<product>_web`` in development and
|
|
217
|
+
``<product>_web_deploy`` in production; only the first was looked for,
|
|
218
|
+
so a production deployment was never reached.
|
|
219
|
+
|
|
220
|
+
Returns when this already is that container, or when it is down in
|
|
221
|
+
development, where the CLI container sees the same files. In production
|
|
222
|
+
with it down, nothing here is the app's: it says so and exits.
|
|
223
|
+
"""
|
|
224
|
+
from splent_cli.services import web_container
|
|
225
|
+
|
|
226
|
+
if web_container.is_product_container():
|
|
227
|
+
return
|
|
228
|
+
container = web_container.name(product, env)
|
|
229
|
+
if web_container.running(container):
|
|
230
|
+
# No --prod: the container runs as what it is, and the CLI of an
|
|
231
|
+
# older image would not know the flag.
|
|
232
|
+
argv = ["clear:uploads", *(["--yes"] if yes else [])]
|
|
233
|
+
raise SystemExit(web_container.delegate(product, argv, container=container))
|
|
234
|
+
|
|
235
|
+
if env == "prod":
|
|
236
|
+
# product:clean --prod gets here after 'down -v', which deleted the
|
|
237
|
+
# volume with everything in it.
|
|
238
|
+
click.echo(
|
|
239
|
+
click.style(
|
|
240
|
+
f"{container} is not running, and its uploads are in a volume "
|
|
241
|
+
"only it mounts. Nothing was deleted from here.",
|
|
242
|
+
fg="yellow",
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
raise SystemExit(0)
|
|
246
|
+
deployed = web_container.name(product, "prod")
|
|
247
|
+
if web_container.running(deployed):
|
|
248
|
+
click.echo(
|
|
249
|
+
click.style(
|
|
250
|
+
f"These are the development uploads. {deployed} is running: "
|
|
251
|
+
"'splent clear:uploads --prod' empties its own.",
|
|
252
|
+
fg="yellow",
|
|
253
|
+
)
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@click.command(
|
|
258
|
+
"clear:uploads",
|
|
259
|
+
short_help="Empty the product's protected uploads (PROTECTED_UPLOADS_DIR).",
|
|
260
|
+
)
|
|
261
|
+
@click.option("--dev", "env_dev", is_flag=True, help="Use development environment.")
|
|
262
|
+
@click.option("--prod", "env_prod", is_flag=True, help="Use production environment.")
|
|
263
|
+
@click.option("-y", "--yes", is_flag=True, help="Confirm without prompting.")
|
|
264
|
+
@context.requires_product
|
|
265
|
+
def clear_uploads(env_dev, env_prod, yes):
|
|
266
|
+
"""
|
|
267
|
+
Delete every file in the directory where the product's app keeps the
|
|
268
|
+
uploads it serves behind an access check: PROTECTED_UPLOADS_DIR, or the
|
|
269
|
+
app's instance/protected_uploads when it is empty. The directory itself
|
|
270
|
+
stays. Public files under static/ are not touched.
|
|
271
|
+
|
|
272
|
+
When the product's web container is up, the command runs there, where
|
|
273
|
+
the directory is mounted: <product>_web in development, and
|
|
274
|
+
<product>_web_deploy with --prod or SPLENT_ENV=prod. In production
|
|
275
|
+
nothing is deleted while that container is down, since its uploads are
|
|
276
|
+
a volume only it mounts.
|
|
277
|
+
"""
|
|
278
|
+
if env_dev and env_prod:
|
|
279
|
+
click.echo(click.style("Cannot specify both --dev and --prod.", fg="red"))
|
|
280
|
+
raise SystemExit(1)
|
|
281
|
+
env = context.resolve_env(env_dev, env_prod)
|
|
282
|
+
_run_where_the_uploads_are(os.getenv("SPLENT_APP", ""), env, yes)
|
|
283
|
+
|
|
284
|
+
try:
|
|
285
|
+
uploads_dir = resolve_uploads_dir()
|
|
286
|
+
except UploadsDirError as e:
|
|
287
|
+
click.echo(click.style(f"Cannot clear the uploads directory. {e}", fg="red"))
|
|
288
|
+
raise SystemExit(1)
|
|
289
|
+
|
|
290
|
+
if not yes:
|
|
291
|
+
click.confirm(
|
|
292
|
+
f"Delete every file in {uploads_dir}? The datasets and documents "
|
|
293
|
+
"the app serves from it cannot be recovered.",
|
|
294
|
+
abort=True,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
if clear_product_uploads():
|
|
298
|
+
raise SystemExit(1)
|
|
@@ -305,7 +305,7 @@ def _reset_one_feature(app, feature_name, yes, product_path, product_name):
|
|
|
305
305
|
|
|
306
306
|
def _reset_everything(app, yes, product_path, product_name):
|
|
307
307
|
if not yes and not click.confirm(
|
|
308
|
-
"⚠️ WARNING: This will DROP all tables and
|
|
308
|
+
"⚠️ WARNING: This will DROP all tables and empty the protected uploads. Are you sure?",
|
|
309
309
|
abort=True,
|
|
310
310
|
):
|
|
311
311
|
return
|
|
@@ -367,7 +367,9 @@ def _reset_everything(app, yes, product_path, product_name):
|
|
|
367
367
|
raise SystemExit(1)
|
|
368
368
|
|
|
369
369
|
# The schema is already gone: a failure here must not stop the migrations.
|
|
370
|
-
|
|
370
|
+
# The app says where its protected uploads are, the rows of which were
|
|
371
|
+
# just dropped.
|
|
372
|
+
if clear_product_uploads(app):
|
|
371
373
|
click.echo(
|
|
372
374
|
click.style(
|
|
373
375
|
"⚠️ Uploads were not cleared. The reset continues.", fg="yellow"
|