splent-cli 1.4.5__tar.gz → 1.5.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.4.5/src/splent_cli.egg-info → splent_cli-1.5.0}/PKG-INFO +3 -2
- {splent_cli-1.4.5 → splent_cli-1.5.0}/pyproject.toml +4 -2
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/database/db_upgrade.py +3 -1
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_add.py +29 -29
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_attach.py +22 -15
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_detach.py +19 -22
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_edit.py +59 -118
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_release.py +6 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_remove.py +18 -32
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature_compile.py +1 -1
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_configure.py +6 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_create.py +64 -13
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_derive.py +7 -6
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_env.py +31 -46
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_port.py +10 -8
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_run.py +2 -3
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_up.py +22 -12
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_validate.py +11 -5
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/spl/spl_configs.py +5 -2
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/uvl/uvl_utils.py +17 -2
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/utils/feature_utils.py +89 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0/src/splent_cli.egg-info}/PKG-INFO +3 -2
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli.egg-info/requires.txt +3 -1
- {splent_cli-1.4.5 → splent_cli-1.5.0}/LICENSE +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/README.md +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/setup.cfg +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/cli.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/check/check_deps.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/check/check_env.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/check/check_features.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/check/check_github.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/check/check_infra.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/check/check_product.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/clear_cache.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/clear_log.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/clear_uploads.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/command_create.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/database/db_console.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/database/db_dump.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/database/db_reset.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/database/db_seed.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/database/db_status.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/doctor.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/export_puml.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_contract.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_diff.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_test.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/linter.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/locust.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_build.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_clean.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_commands.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_complete.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_config.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_console.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_down.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_drift.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_missing.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_release.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_restart.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_routes.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_select.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_signals.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_status.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_sync.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/product/product_test.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/release/release_core.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/spl/__init__.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/spl/spl_fix.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/tokens.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/commands/version.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/services/compose.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/services/context.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/services/preflight.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/services/release.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/utils/cache_utils.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/utils/contract_freshness.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/utils/git_url.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/utils/integrity.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/utils/lifecycle.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/utils/manifest.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli/utils/template_drift.py +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli.egg-info/SOURCES.txt +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.4.5 → splent_cli-1.5.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.5.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
|
|
@@ -21,7 +21,6 @@ Requires-Dist: black==24.10.0; extra == "dev"
|
|
|
21
21
|
Requires-Dist: coverage==7.6.10; extra == "dev"
|
|
22
22
|
Requires-Dist: docker==7.1.0; extra == "dev"
|
|
23
23
|
Requires-Dist: Faker==33.3.1; extra == "dev"
|
|
24
|
-
Requires-Dist: flamapy==2.5.0; extra == "dev"
|
|
25
24
|
Requires-Dist: flake8==7.1.1; extra == "dev"
|
|
26
25
|
Requires-Dist: graphviz==0.20.3; extra == "dev"
|
|
27
26
|
Requires-Dist: iniconfig==2.0.0; extra == "dev"
|
|
@@ -37,6 +36,8 @@ Requires-Dist: selenium-wire==5.1.0; extra == "dev"
|
|
|
37
36
|
Requires-Dist: pip-tools==7.4.1; extra == "dev"
|
|
38
37
|
Requires-Dist: tomli_w==1.2.0; extra == "dev"
|
|
39
38
|
Requires-Dist: PyYAML==6.0.3; extra == "dev"
|
|
39
|
+
Provides-Extra: uvl
|
|
40
|
+
Requires-Dist: flamapy==2.5.0; extra == "uvl"
|
|
40
41
|
Dynamic: license-file
|
|
41
42
|
|
|
42
43
|
# SPLENT CLI
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "splent_cli"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.5.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"
|
|
@@ -28,7 +28,6 @@ dev = [
|
|
|
28
28
|
"coverage==7.6.10",
|
|
29
29
|
"docker==7.1.0",
|
|
30
30
|
"Faker==33.3.1",
|
|
31
|
-
"flamapy==2.5.0",
|
|
32
31
|
"flake8==7.1.1",
|
|
33
32
|
"graphviz==0.20.3",
|
|
34
33
|
"iniconfig==2.0.0",
|
|
@@ -45,6 +44,9 @@ dev = [
|
|
|
45
44
|
"tomli_w==1.2.0",
|
|
46
45
|
"PyYAML==6.0.3"
|
|
47
46
|
]
|
|
47
|
+
uvl = [
|
|
48
|
+
"flamapy==2.5.0",
|
|
49
|
+
]
|
|
48
50
|
|
|
49
51
|
[project.scripts]
|
|
50
52
|
splent = "splent_cli.__main__:main"
|
|
@@ -59,15 +59,17 @@ def db_upgrade(feature):
|
|
|
59
59
|
import logging
|
|
60
60
|
|
|
61
61
|
logging.getLogger("alembic").setLevel(logging.WARNING)
|
|
62
|
+
logging.getLogger("alembic.runtime.migration").setLevel(logging.WARNING)
|
|
62
63
|
|
|
63
64
|
for feat, mdir in dirs.items():
|
|
64
65
|
try:
|
|
66
|
+
logging.getLogger("alembic.runtime.migration").setLevel(logging.WARNING)
|
|
65
67
|
alembic_upgrade(directory=mdir)
|
|
66
68
|
revision = MigrationManager.get_current_feature_revision(
|
|
67
69
|
feat, app.extensions["migrate"].db.engine
|
|
68
70
|
)
|
|
69
71
|
MigrationManager.update_feature_status(app, feat, revision)
|
|
70
|
-
click.echo(click.style(f"
|
|
72
|
+
click.echo(click.style(f" {feat} -> {revision or 'head'}", fg="green"))
|
|
71
73
|
|
|
72
74
|
# Advance lifecycle state to "migrated"
|
|
73
75
|
info = entry_lookup.get(feat)
|
|
@@ -3,7 +3,7 @@ import tomllib
|
|
|
3
3
|
import tomli_w
|
|
4
4
|
import click
|
|
5
5
|
from splent_cli.services import context
|
|
6
|
-
from splent_cli.utils.feature_utils import normalize_namespace
|
|
6
|
+
from splent_cli.utils.feature_utils import normalize_namespace, hot_reinstall
|
|
7
7
|
from splent_cli.utils.manifest import feature_key, set_feature_state
|
|
8
8
|
|
|
9
9
|
|
|
@@ -39,11 +39,8 @@ def feature_add(full_name, env_scope):
|
|
|
39
39
|
splent feature:add splent-io/splent_feature_admin --dev
|
|
40
40
|
"""
|
|
41
41
|
|
|
42
|
-
# --------------------------
|
|
43
|
-
# 0️⃣ Validate format
|
|
44
|
-
# --------------------------
|
|
45
42
|
if "/" not in full_name:
|
|
46
|
-
click.
|
|
43
|
+
click.secho(" Invalid format. Use: <namespace>/<feature_name>", fg="red")
|
|
47
44
|
raise SystemExit(1)
|
|
48
45
|
|
|
49
46
|
namespace, feature_name = full_name.split("/", 1)
|
|
@@ -55,16 +52,17 @@ def feature_add(full_name, env_scope):
|
|
|
55
52
|
# Editable features live at workspace root
|
|
56
53
|
feature_dir = os.path.join(workspace, feature_name)
|
|
57
54
|
if not os.path.exists(feature_dir):
|
|
58
|
-
click.
|
|
59
|
-
click.echo(
|
|
55
|
+
click.secho(f" {feature_name} not found at workspace root.", fg="red")
|
|
56
|
+
click.echo(
|
|
57
|
+
click.style(" create it first: ", dim=True)
|
|
58
|
+
+ f"splent feature:create {full_name}"
|
|
59
|
+
)
|
|
60
60
|
raise SystemExit(1)
|
|
61
61
|
|
|
62
|
-
#
|
|
63
|
-
# 1️⃣ Update pyproject.toml
|
|
64
|
-
# --------------------------
|
|
62
|
+
# ── Update pyproject.toml ─────────────────────────────────────────
|
|
65
63
|
pyproject_path = os.path.join(workspace, product, "pyproject.toml")
|
|
66
64
|
if not os.path.exists(pyproject_path):
|
|
67
|
-
click.
|
|
65
|
+
click.secho(" pyproject.toml not found.", fg="red")
|
|
68
66
|
raise SystemExit(1)
|
|
69
67
|
|
|
70
68
|
with open(pyproject_path, "rb") as f:
|
|
@@ -82,19 +80,21 @@ def feature_add(full_name, env_scope):
|
|
|
82
80
|
else (data.get("tool", {}).get("splent", {}).get(features_key, []))
|
|
83
81
|
)
|
|
84
82
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
83
|
+
short = feature_name.replace("splent_feature_", "")
|
|
84
|
+
|
|
85
|
+
if full_name in features:
|
|
86
|
+
click.echo(f" {short} already in {features_key}.")
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
features.append(full_name)
|
|
90
|
+
write_features_to_data(data, features, key=features_key)
|
|
91
|
+
with open(pyproject_path, "wb") as f:
|
|
92
|
+
tomli_w.dump(data, f)
|
|
93
|
+
|
|
94
|
+
scope_label = f" ({env_scope} only)" if env_scope else ""
|
|
95
|
+
click.echo(f" {short} added to {features_key}{scope_label}")
|
|
96
|
+
|
|
97
|
+
# ── Create symlink ────────────────────────────────────────────────
|
|
98
98
|
product_features_dir = os.path.join(workspace, product, "features", org_safe)
|
|
99
99
|
os.makedirs(product_features_dir, exist_ok=True)
|
|
100
100
|
|
|
@@ -105,11 +105,8 @@ def feature_add(full_name, env_scope):
|
|
|
105
105
|
except FileExistsError:
|
|
106
106
|
os.unlink(link_path)
|
|
107
107
|
os.symlink(rel_target, link_path)
|
|
108
|
-
click.echo(f"🔗 Linked {link_path} → {rel_target}")
|
|
109
108
|
|
|
110
|
-
#
|
|
111
|
-
# 3️⃣ Update manifest
|
|
112
|
-
# --------------------------
|
|
109
|
+
# ── Update manifest ───────────────────────────────────────────────
|
|
113
110
|
product_path = os.path.join(workspace, product)
|
|
114
111
|
key = feature_key(namespace, feature_name)
|
|
115
112
|
set_feature_state(
|
|
@@ -123,4 +120,7 @@ def feature_add(full_name, env_scope):
|
|
|
123
120
|
mode="editable",
|
|
124
121
|
)
|
|
125
122
|
|
|
126
|
-
|
|
123
|
+
# ── Hot reinstall in web container ────────────────────────────────
|
|
124
|
+
hot_reinstall(product_path, f"/workspace/{feature_name}", feature_name)
|
|
125
|
+
|
|
126
|
+
click.secho(" done.", fg="green")
|
|
@@ -3,6 +3,7 @@ import tomllib
|
|
|
3
3
|
import tomli_w
|
|
4
4
|
import click
|
|
5
5
|
from splent_cli.services import context, compose
|
|
6
|
+
from splent_cli.utils.feature_utils import hot_reinstall
|
|
6
7
|
from splent_cli.utils.manifest import feature_key, set_feature_state
|
|
7
8
|
|
|
8
9
|
|
|
@@ -32,7 +33,7 @@ def feature_attach(feature_identifier, version, env_scope):
|
|
|
32
33
|
If not, run: splent feature:clone <namespace>/<feature>@<version>
|
|
33
34
|
- Updates pyproject.toml referencing feature@version.
|
|
34
35
|
- Creates/updates the versioned symlink in features/<namespace>/.
|
|
35
|
-
-
|
|
36
|
+
- Reinstalls the feature in the web container for hot reload.
|
|
36
37
|
"""
|
|
37
38
|
product = context.require_app()
|
|
38
39
|
ws = context.workspace()
|
|
@@ -47,22 +48,23 @@ def feature_attach(feature_identifier, version, env_scope):
|
|
|
47
48
|
pyproject_path = os.path.join(product_path, "pyproject.toml")
|
|
48
49
|
|
|
49
50
|
if not os.path.exists(pyproject_path):
|
|
50
|
-
click.
|
|
51
|
+
click.secho(" pyproject.toml not found.", fg="red")
|
|
51
52
|
raise SystemExit(1)
|
|
52
53
|
|
|
53
|
-
#
|
|
54
|
+
# ── Verify feature exists in cache ────────────────────────────────
|
|
54
55
|
versioned_dir = os.path.join(cache_base, f"{feature_name}@{version}")
|
|
55
56
|
|
|
56
57
|
if not os.path.exists(versioned_dir):
|
|
58
|
+
click.secho(f" {feature_name}@{version} not found in cache.", fg="red")
|
|
57
59
|
click.echo(
|
|
58
|
-
|
|
59
|
-
f"
|
|
60
|
+
click.style(" clone it first: ", dim=True)
|
|
61
|
+
+ f"splent feature:clone {namespace}/{feature_name}@{version}"
|
|
60
62
|
)
|
|
61
63
|
raise SystemExit(1)
|
|
62
64
|
|
|
63
|
-
|
|
65
|
+
short = feature_name.replace("splent_feature_", "")
|
|
64
66
|
|
|
65
|
-
#
|
|
67
|
+
# ── Update pyproject.toml ─────────────────────────────────────────
|
|
66
68
|
full_name = f"{namespace}/{feature_name}@{version}"
|
|
67
69
|
bare_name = f"{namespace}/{feature_name}"
|
|
68
70
|
|
|
@@ -82,9 +84,9 @@ def feature_attach(feature_identifier, version, env_scope):
|
|
|
82
84
|
)
|
|
83
85
|
|
|
84
86
|
if full_name in features:
|
|
85
|
-
click.echo(f"
|
|
87
|
+
click.echo(f" {short}@{version} already in {features_key}.")
|
|
86
88
|
else:
|
|
87
|
-
# Replace bare entry
|
|
89
|
+
# Replace bare entry or old versioned entry if present
|
|
88
90
|
features = [
|
|
89
91
|
f for f in features if f != bare_name and not f.startswith(f"{bare_name}@")
|
|
90
92
|
]
|
|
@@ -93,9 +95,9 @@ def feature_attach(feature_identifier, version, env_scope):
|
|
|
93
95
|
with open(pyproject_path, "wb") as f:
|
|
94
96
|
tomli_w.dump(data, f)
|
|
95
97
|
scope_label = f" ({env_scope} only)" if env_scope else ""
|
|
96
|
-
click.echo(f"
|
|
98
|
+
click.echo(f" {short}@{version} attached{scope_label}")
|
|
97
99
|
|
|
98
|
-
#
|
|
100
|
+
# ── Create/update symlink ─────────────────────────────────────────
|
|
99
101
|
product_features_dir = os.path.join(product_path, "features", namespace_fs)
|
|
100
102
|
os.makedirs(product_features_dir, exist_ok=True)
|
|
101
103
|
|
|
@@ -105,9 +107,7 @@ def feature_attach(feature_identifier, version, env_scope):
|
|
|
105
107
|
rel_target = os.path.relpath(versioned_dir, product_features_dir)
|
|
106
108
|
os.symlink(rel_target, new_link)
|
|
107
109
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
# --- 4️⃣ Update manifest ------------------------------------------------
|
|
110
|
+
# ── Update manifest ───────────────────────────────────────────────
|
|
111
111
|
key = feature_key(namespace_fs, feature_name, version)
|
|
112
112
|
set_feature_state(
|
|
113
113
|
product_path,
|
|
@@ -120,4 +120,11 @@ def feature_attach(feature_identifier, version, env_scope):
|
|
|
120
120
|
mode="pinned",
|
|
121
121
|
)
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
# ── Hot reinstall in web container ────────────────────────────────
|
|
124
|
+
# Symlink resolves to cache path — install from there
|
|
125
|
+
install_path = (
|
|
126
|
+
f"/workspace/{product}/features/{namespace_fs}/{feature_name}@{version}"
|
|
127
|
+
)
|
|
128
|
+
hot_reinstall(product_path, install_path, feature_name)
|
|
129
|
+
|
|
130
|
+
click.secho(" done.", fg="green")
|
|
@@ -2,6 +2,7 @@ import os
|
|
|
2
2
|
import re
|
|
3
3
|
import click
|
|
4
4
|
from splent_cli.services import context, compose
|
|
5
|
+
from splent_cli.utils.feature_utils import hot_uninstall
|
|
5
6
|
from splent_cli.utils.manifest import (
|
|
6
7
|
feature_key,
|
|
7
8
|
remove_feature,
|
|
@@ -39,39 +40,37 @@ def feature_detach(feature_identifier, version, force):
|
|
|
39
40
|
|
|
40
41
|
product_path = str(ws / product)
|
|
41
42
|
pyproject_path = os.path.join(product_path, "pyproject.toml")
|
|
43
|
+
short = feature_name.replace("splent_feature_", "")
|
|
42
44
|
|
|
43
45
|
if not os.path.exists(pyproject_path):
|
|
44
|
-
click.
|
|
46
|
+
click.secho(" pyproject.toml not found.", fg="red")
|
|
45
47
|
raise SystemExit(1)
|
|
46
48
|
|
|
47
49
|
if not force:
|
|
48
|
-
#
|
|
50
|
+
# Guard: dependency check
|
|
49
51
|
dependents = get_dependents(product_path, feature_name)
|
|
50
52
|
if dependents:
|
|
51
53
|
click.secho(
|
|
52
|
-
f"
|
|
53
|
-
+ "".join(f"
|
|
54
|
-
+ "
|
|
54
|
+
f" Cannot detach '{short}': the following features depend on it:\n"
|
|
55
|
+
+ "".join(f" - {d}\n" for d in dependents)
|
|
56
|
+
+ " Remove those first, or use --force.",
|
|
55
57
|
fg="red",
|
|
56
58
|
)
|
|
57
59
|
raise SystemExit(1)
|
|
58
60
|
|
|
59
|
-
#
|
|
61
|
+
# Guard: migration state
|
|
60
62
|
key = feature_key(namespace_fs, feature_name, version)
|
|
61
63
|
state = get_feature_state(product_path, key)
|
|
62
64
|
if state in ("migrated", "active"):
|
|
63
65
|
click.secho(
|
|
64
|
-
f"
|
|
65
|
-
f"
|
|
66
|
-
f"
|
|
67
|
-
f" Or use --force to skip this check.",
|
|
66
|
+
f" {short} has migrations applied (state: {state}).\n"
|
|
67
|
+
f" Roll them back first: splent db:rollback {feature_name} --steps 999\n"
|
|
68
|
+
f" Or use --force.",
|
|
68
69
|
fg="red",
|
|
69
70
|
)
|
|
70
71
|
raise SystemExit(1)
|
|
71
72
|
|
|
72
|
-
#
|
|
73
|
-
click.echo(f"🧹 Removing {feature_name}@{version} from pyproject.toml...")
|
|
74
|
-
|
|
73
|
+
# ── Remove versioned reference from pyproject ─────────────────────
|
|
75
74
|
with open(pyproject_path, "r", encoding="utf-8") as f:
|
|
76
75
|
content = f.read()
|
|
77
76
|
|
|
@@ -81,22 +80,20 @@ def feature_detach(feature_identifier, version, force):
|
|
|
81
80
|
with open(pyproject_path, "w", encoding="utf-8") as f:
|
|
82
81
|
f.write(new_content)
|
|
83
82
|
|
|
84
|
-
click.echo("
|
|
83
|
+
click.echo(f" {short}@{version} removed from pyproject.toml")
|
|
85
84
|
|
|
86
|
-
#
|
|
85
|
+
# ── Remove symlink ────────────────────────────────────────────────
|
|
87
86
|
product_features_dir = os.path.join(product_path, "features", namespace_fs)
|
|
88
87
|
link_path = os.path.join(product_features_dir, f"{feature_name}@{version}")
|
|
89
88
|
|
|
90
89
|
if os.path.islink(link_path):
|
|
91
90
|
os.unlink(link_path)
|
|
92
|
-
click.echo(f"🔗 Removed symlink: {link_path}")
|
|
93
|
-
else:
|
|
94
|
-
click.echo(
|
|
95
|
-
f"⚠️ No symlink found for {feature_name}@{version} in {namespace_fs}/"
|
|
96
|
-
)
|
|
97
91
|
|
|
98
|
-
#
|
|
92
|
+
# ── Update manifest ───────────────────────────────────────────────
|
|
99
93
|
key = feature_key(namespace_fs, feature_name, version)
|
|
100
94
|
remove_feature(str(ws / product), product, key)
|
|
101
95
|
|
|
102
|
-
|
|
96
|
+
# ── Hot uninstall from web container ──────────────────────────────
|
|
97
|
+
hot_uninstall(product_path, feature_name)
|
|
98
|
+
|
|
99
|
+
click.secho(" done.", fg="green")
|
|
@@ -8,62 +8,6 @@ from splent_cli.utils.feature_utils import read_features_from_data
|
|
|
8
8
|
from splent_cli.utils.cache_utils import make_feature_writable
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
# =====================================================================
|
|
12
|
-
# GITHUB WRITE ACCESS CHECK
|
|
13
|
-
# =====================================================================
|
|
14
|
-
def _has_write_access(ns_git: str, name: str) -> tuple[bool, str]:
|
|
15
|
-
"""Check if the authenticated user has push access to ns_git/name.
|
|
16
|
-
|
|
17
|
-
Returns (has_access, reason_if_denied).
|
|
18
|
-
"""
|
|
19
|
-
token = os.getenv("GITHUB_TOKEN")
|
|
20
|
-
github_user = os.getenv("GITHUB_USER", "(not set)")
|
|
21
|
-
if not token:
|
|
22
|
-
return False, (
|
|
23
|
-
f"GitHub user (env): {github_user}\n"
|
|
24
|
-
f" Repo owner: {ns_git}\n"
|
|
25
|
-
f" GITHUB_TOKEN not set"
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
headers = {
|
|
29
|
-
"Accept": "application/vnd.github+json",
|
|
30
|
-
"Authorization": f"token {token}",
|
|
31
|
-
"User-Agent": "splent-cli",
|
|
32
|
-
}
|
|
33
|
-
try:
|
|
34
|
-
resp = requests.get(
|
|
35
|
-
f"https://api.github.com/repos/{ns_git}/{name}",
|
|
36
|
-
headers=headers,
|
|
37
|
-
timeout=5,
|
|
38
|
-
)
|
|
39
|
-
if resp.status_code == 404:
|
|
40
|
-
return False, (
|
|
41
|
-
f"GitHub user (env): {github_user}\n"
|
|
42
|
-
f" Repo owner: {ns_git}\n"
|
|
43
|
-
f" Repo {ns_git}/{name} not found (404)"
|
|
44
|
-
)
|
|
45
|
-
if resp.status_code == 200:
|
|
46
|
-
perms = resp.json().get("permissions", {})
|
|
47
|
-
if perms.get("push", False):
|
|
48
|
-
return True, ""
|
|
49
|
-
return False, (
|
|
50
|
-
f"GitHub user (env): {github_user}\n"
|
|
51
|
-
f" Repo owner: {ns_git}\n"
|
|
52
|
-
f" Push access: {perms.get('push', False)}"
|
|
53
|
-
)
|
|
54
|
-
return False, (
|
|
55
|
-
f"GitHub user (env): {github_user}\n"
|
|
56
|
-
f" Repo owner: {ns_git}\n"
|
|
57
|
-
f" GitHub API returned {resp.status_code}"
|
|
58
|
-
)
|
|
59
|
-
except requests.RequestException as e:
|
|
60
|
-
return False, (
|
|
61
|
-
f"GitHub user (env): {github_user}\n"
|
|
62
|
-
f" Repo owner: {ns_git}\n"
|
|
63
|
-
f" API error: {e}"
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
|
|
67
11
|
# =====================================================================
|
|
68
12
|
# CACHE PATH RESOLVER
|
|
69
13
|
# =====================================================================
|
|
@@ -140,39 +84,12 @@ def replace_pyproject_reference(pyproject_path: str, name: str, version: str):
|
|
|
140
84
|
|
|
141
85
|
|
|
142
86
|
# =====================================================================
|
|
143
|
-
# HOT REINSTALL —
|
|
87
|
+
# HOT REINSTALL — delegated to shared utility
|
|
144
88
|
# =====================================================================
|
|
145
89
|
def _hot_reinstall(workspace: str, product_path: str, editable_path: str, name: str):
|
|
146
|
-
|
|
147
|
-
product = os.path.basename(product_path)
|
|
148
|
-
env = os.getenv("SPLENT_ENV", "dev")
|
|
149
|
-
docker_dir = os.path.join(product_path, "docker")
|
|
90
|
+
from splent_cli.utils.feature_utils import hot_reinstall
|
|
150
91
|
|
|
151
|
-
|
|
152
|
-
if not compose_file:
|
|
153
|
-
return # no docker-compose file — nothing to do
|
|
154
|
-
|
|
155
|
-
pname = compose.project_name(product, env)
|
|
156
|
-
container_id = compose.find_main_container(pname, compose_file, docker_dir)
|
|
157
|
-
if not container_id:
|
|
158
|
-
return # container not running — nothing to do
|
|
159
|
-
|
|
160
|
-
# 1. pip install -e from the new path
|
|
161
|
-
click.echo(f" 🔄 Reinstalling {name} in web container...")
|
|
162
|
-
pip_cmd = (
|
|
163
|
-
f"pip install --no-cache-dir --root-user-action=ignore -q -e /workspace/{name}"
|
|
164
|
-
)
|
|
165
|
-
subprocess.run(
|
|
166
|
-
["docker", "exec", container_id, "bash", "-c", pip_cmd],
|
|
167
|
-
capture_output=True,
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
# 2. Touch the app's __init__.py to trigger watchmedo auto-restart
|
|
171
|
-
init_py = f"/workspace/{product}/src/{product}/__init__.py"
|
|
172
|
-
subprocess.run(
|
|
173
|
-
["docker", "exec", container_id, "bash", "-c", f"touch {init_py}"],
|
|
174
|
-
capture_output=True,
|
|
175
|
-
)
|
|
92
|
+
hot_reinstall(product_path, f"/workspace/{name}", name)
|
|
176
93
|
|
|
177
94
|
|
|
178
95
|
# =====================================================================
|
|
@@ -205,7 +122,7 @@ def _compile_assets(workspace: str, product_path: str, name: str):
|
|
|
205
122
|
if not container_id:
|
|
206
123
|
return
|
|
207
124
|
|
|
208
|
-
click.echo("
|
|
125
|
+
click.echo(click.style(" compiling assets...", dim=True))
|
|
209
126
|
product_root = f"/workspace/{product}"
|
|
210
127
|
cmd = f"cd {shlex.quote(product_root)} && npx webpack --config {shlex.quote(webpack_file)} --mode development"
|
|
211
128
|
result = subprocess.run(
|
|
@@ -213,10 +130,40 @@ def _compile_assets(workspace: str, product_path: str, name: str):
|
|
|
213
130
|
capture_output=True,
|
|
214
131
|
text=True,
|
|
215
132
|
)
|
|
216
|
-
if result.returncode
|
|
217
|
-
click.
|
|
218
|
-
|
|
219
|
-
|
|
133
|
+
if result.returncode != 0:
|
|
134
|
+
click.secho(" asset compilation failed.", fg="yellow")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# =====================================================================
|
|
138
|
+
# WRITE ACCESS WARNING
|
|
139
|
+
# =====================================================================
|
|
140
|
+
def _warn_no_push_access(ns_git: str, name: str):
|
|
141
|
+
"""Print a warning if the user cannot push to the repo. Non-blocking."""
|
|
142
|
+
token = os.getenv("GITHUB_TOKEN")
|
|
143
|
+
if not token:
|
|
144
|
+
return # no token = can't check, don't nag
|
|
145
|
+
|
|
146
|
+
headers = {
|
|
147
|
+
"Accept": "application/vnd.github+json",
|
|
148
|
+
"Authorization": f"token {token}",
|
|
149
|
+
"User-Agent": "splent-cli",
|
|
150
|
+
}
|
|
151
|
+
try:
|
|
152
|
+
resp = requests.get(
|
|
153
|
+
f"https://api.github.com/repos/{ns_git}/{name}",
|
|
154
|
+
headers=headers,
|
|
155
|
+
timeout=5,
|
|
156
|
+
)
|
|
157
|
+
if resp.status_code == 200:
|
|
158
|
+
perms = resp.json().get("permissions", {})
|
|
159
|
+
if not perms.get("push", False):
|
|
160
|
+
click.secho(
|
|
161
|
+
f" note: no push access to {ns_git}/{name}\n"
|
|
162
|
+
f" use 'splent feature:fork' to work on your own copy",
|
|
163
|
+
fg="yellow",
|
|
164
|
+
)
|
|
165
|
+
except requests.RequestException:
|
|
166
|
+
pass # network error — don't block
|
|
220
167
|
|
|
221
168
|
|
|
222
169
|
# =====================================================================
|
|
@@ -239,38 +186,26 @@ def _edit_one(
|
|
|
239
186
|
name, version = rest, None
|
|
240
187
|
|
|
241
188
|
if not version:
|
|
242
|
-
click.echo(f"
|
|
189
|
+
click.echo(click.style(f" {name}", dim=True) + " already editable")
|
|
243
190
|
return True
|
|
244
191
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
has_access, reason = _has_write_access(ns_git, name)
|
|
248
|
-
if not has_access:
|
|
249
|
-
click.secho(
|
|
250
|
-
f" ❌ No write access to {ns_git}/{name}.\n"
|
|
251
|
-
f" {reason}\n"
|
|
252
|
-
f" To work on your own copy, use: splent feature:fork {ns_git}/{name}\n"
|
|
253
|
-
f" Or use --force to bypass this check.",
|
|
254
|
-
fg="red",
|
|
255
|
-
)
|
|
256
|
-
return False
|
|
257
|
-
|
|
258
|
-
click.echo(f" 🧩 {ns_git}/{name}@{version}")
|
|
192
|
+
short = name.replace("splent_feature_", "")
|
|
193
|
+
click.echo(f" {short} ({version}) -> editable")
|
|
259
194
|
|
|
260
195
|
versioned_path, editable_path = get_feature_paths(workspace, ns_fs, name, version)
|
|
261
196
|
|
|
262
197
|
if not os.path.exists(versioned_path):
|
|
263
|
-
click.secho(f"
|
|
198
|
+
click.secho(f" cached version not found: {versioned_path}", fg="red")
|
|
264
199
|
return False
|
|
265
200
|
|
|
266
201
|
if not os.path.exists(editable_path):
|
|
267
202
|
import shutil
|
|
268
203
|
|
|
269
|
-
click.echo(
|
|
204
|
+
click.echo(click.style(" copying to workspace root...", dim=True))
|
|
270
205
|
result = subprocess.run(["cp", "-r", versioned_path, editable_path])
|
|
271
206
|
if result.returncode != 0:
|
|
272
207
|
shutil.rmtree(editable_path, ignore_errors=True)
|
|
273
|
-
click.secho("
|
|
208
|
+
click.secho(" failed to copy feature.", fg="red")
|
|
274
209
|
return False
|
|
275
210
|
|
|
276
211
|
# Always ensure editable copy is writable (cache files are read-only)
|
|
@@ -302,7 +237,10 @@ def _edit_one(
|
|
|
302
237
|
# Compile webpack assets via the product's web container
|
|
303
238
|
_compile_assets(workspace, product_path, name)
|
|
304
239
|
|
|
305
|
-
|
|
240
|
+
# Non-blocking warning if user can't push
|
|
241
|
+
_warn_no_push_access(ns_git, name)
|
|
242
|
+
|
|
243
|
+
click.secho(" ready.", fg="green")
|
|
306
244
|
return True
|
|
307
245
|
|
|
308
246
|
|
|
@@ -329,7 +267,7 @@ def feature_edit(feature_name, edit_all, force):
|
|
|
329
267
|
pyproject_path = os.path.join(product_path, "pyproject.toml")
|
|
330
268
|
|
|
331
269
|
if not os.path.exists(pyproject_path):
|
|
332
|
-
click.
|
|
270
|
+
click.secho(" pyproject.toml not found.", fg="red")
|
|
333
271
|
raise SystemExit(1)
|
|
334
272
|
|
|
335
273
|
if not feature_name and not edit_all:
|
|
@@ -353,7 +291,7 @@ def feature_edit(feature_name, edit_all, force):
|
|
|
353
291
|
None,
|
|
354
292
|
)
|
|
355
293
|
if not match:
|
|
356
|
-
click.
|
|
294
|
+
click.secho(f" {feature_name} not found in pyproject.", fg="red")
|
|
357
295
|
raise SystemExit(1)
|
|
358
296
|
|
|
359
297
|
click.echo()
|
|
@@ -367,17 +305,20 @@ def feature_edit(feature_name, edit_all, force):
|
|
|
367
305
|
|
|
368
306
|
click.echo()
|
|
369
307
|
if already_editable:
|
|
370
|
-
click.
|
|
371
|
-
|
|
372
|
-
|
|
308
|
+
click.echo(
|
|
309
|
+
click.style(
|
|
310
|
+
f" {len(already_editable)} feature(s) already editable, skipping.",
|
|
311
|
+
dim=True,
|
|
312
|
+
)
|
|
373
313
|
)
|
|
374
314
|
|
|
375
315
|
if not versioned:
|
|
376
|
-
click.secho("
|
|
316
|
+
click.secho(" All features are already editable.", fg="green")
|
|
377
317
|
click.echo()
|
|
378
318
|
return
|
|
379
319
|
|
|
380
|
-
click.
|
|
320
|
+
click.echo(f" Converting {len(versioned)} feature(s) to editable:")
|
|
321
|
+
click.echo()
|
|
381
322
|
ok = 0
|
|
382
323
|
for match in versioned:
|
|
383
324
|
if _edit_one(workspace, product_path, pyproject_path, match, force=force):
|
|
@@ -385,7 +326,7 @@ def feature_edit(feature_name, edit_all, force):
|
|
|
385
326
|
click.echo()
|
|
386
327
|
|
|
387
328
|
click.secho(
|
|
388
|
-
f" {ok}/{len(versioned)}
|
|
329
|
+
f" {ok}/{len(versioned)} converted.",
|
|
389
330
|
fg="green" if ok == len(versioned) else "yellow",
|
|
390
331
|
)
|
|
391
332
|
click.echo()
|
|
@@ -405,6 +405,12 @@ def feature_release(feature_ref, version, attach):
|
|
|
405
405
|
post_pypi_hook=_post_pypi,
|
|
406
406
|
)
|
|
407
407
|
|
|
408
|
+
if not attach:
|
|
409
|
+
product = context.require_app()
|
|
410
|
+
attach = click.confirm(
|
|
411
|
+
f"\n Attach {feature_name}@{tag} to {product}?", default=True
|
|
412
|
+
)
|
|
413
|
+
|
|
408
414
|
if attach:
|
|
409
415
|
click.echo(" attach linking to product...")
|
|
410
416
|
ctx = click.get_current_context()
|