splent-cli 1.46.0__tar.gz → 1.47.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.46.0/src/splent_cli.egg-info → splent_cli-1.47.0}/PKG-INFO +1 -1
- {splent_cli-1.46.0 → splent_cli-1.47.0}/pyproject.toml +4 -2
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_compile.py +136 -12
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_build.py +36 -1
- splent_cli-1.47.0/src/splent_cli/commands/product/product_frontend_audit.py +323 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_release.py +2 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_validate.py +104 -7
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/frontend.py +75 -21
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/preflight.py +30 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_Dockerfile.prod.j2 +21 -12
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_ci_validate.yml.j2 +21 -4
- splent_cli-1.47.0/src/splent_cli/toolchain/Dockerfile.frontend +46 -0
- splent_cli-1.47.0/src/splent_cli/toolchain/frontend/package-lock.json +1300 -0
- splent_cli-1.47.0/src/splent_cli/toolchain/frontend/package.json +14 -0
- splent_cli-1.47.0/src/splent_cli/toolchain/frontend/splent/build.js +76 -0
- splent_cli-1.47.0/src/splent_cli/toolchain/frontend/splent/legacy.js +52 -0
- splent_cli-1.47.0/src/splent_cli/toolchain/frontend/splent/plugin.js +288 -0
- splent_cli-1.47.0/src/splent_cli/toolchain/frontend/splent/preset.js +208 -0
- splent_cli-1.47.0/src/splent_cli/toolchain/frontend/splent/singletons.json +18 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/deploy_freshness.py +8 -3
- splent_cli-1.47.0/src/splent_cli/utils/frontend_check.py +866 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/frontend_licenses.py +4 -2
- splent_cli-1.47.0/src/splent_cli/utils/npm_semver.py +200 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0/src/splent_cli.egg-info}/PKG-INFO +1 -1
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli.egg-info/SOURCES.txt +11 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/COPYING +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/COPYING.LESSER +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/README.md +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/setup.cfg +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/__init__.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/__main__.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/cli.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/__init__.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/cache/__init__.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/cache/cache_clear.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/cache/cache_orphans.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/cache/cache_prune.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/cache/cache_size.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/cache/cache_status.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/cache/cache_usage.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/cache/cache_versions.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/__init__.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_contracts.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_deps.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_docker.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_env.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_features.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_github.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_infra.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_product.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_pypi.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_pyproject.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_releases.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/check/check_vendor.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/clear/__init__.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/clear/clear_build.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/clear/clear_log.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/clear/clear_uploads.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/command/__init__.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/command/command_create.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/coverage.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/database/db_console.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/database/db_dump.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/database/db_migrate.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/database/db_reset.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/database/db_restore.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/database/db_rollback.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/database/db_seed.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/database/db_status.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/database/db_upgrade.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/doctor.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/env/env_list.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/env/env_set.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/env/env_show.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/export/__init__.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/export/export_puml.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_add.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_attach.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_clean.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_clone.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_compat.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_contract.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_create.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_delete.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_detach.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_discard.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_drift.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_env.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_fork.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_frontend_init.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_git.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_hook_add.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_hook_remove.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_hooks.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_impact.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_info.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_inject_config.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_install.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_list.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_npm.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_order.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_outdated.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_pin.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_pip_install.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_pull.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_refine.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_release.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_remove.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_rename.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_search.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_status.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_sync_template.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_test.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_translate.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_unlock.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_upgrade.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_versions.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/feature/feature_xray.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/lint.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/locust.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/marketplace/login.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/marketplace/logout.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/marketplace/marketplace_auth_utils.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/marketplace/marketplace_index.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/marketplace/whoami.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/__init__.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_auto_require.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_clean.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_commands.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_config.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_configure.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_console.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_containers.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_create.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_deploy.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_derive.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_deselect.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_down.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_drift.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_env.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_list.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_logs.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_missing.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_params.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_port.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_resolve.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_restart.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_routes.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_run.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_select.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_shell.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_signals.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_sync_template.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_test.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/product/product_up.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/release/__init__.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/release/release_core.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/release/release_resume.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/selenium.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/__init__.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_add_constraints.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_add_feature.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_compare.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_configurations.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_create.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_deps.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_features.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_fetch.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_info.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_list.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_outdated.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_pin.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_publish.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_remove_feature.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/spl/spl_utils.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/tokens_setup.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/uvl/uvl_utils.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/commands/version.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/__init__.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/compose.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/context.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/credentials.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/deploy_bundle.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/editable_refresh.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/image_registry.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/index_refresh.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/marketplace.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/marketplace_api.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/marketplace_url.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/neutral_app.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/own_commands.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/pypi_quota.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/registry.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/release.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/release_dry_run.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/release_gate.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/spl_store.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/services/web_container.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_.gitignore.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_MANIFEST.in.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_README.md.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_commands.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_config.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_docker-compose.dev.yml.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_docker-compose.prod.yml.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_docker.env.example.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_forms.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_frontend.npmrc.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_frontend_scripts.js.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_hooks.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_init.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_init_config.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_init_light.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_init_service.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_migrations_alembic.ini.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_migrations_env.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_migrations_script.py.mako.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_models.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_pyproject.toml.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_pyproject_minimal.toml.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_repositories.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_routes.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_scripts.js.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_seeders.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_services.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_services_service.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_signals.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_templates_index.html.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_tests_conftest.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_tests_e2e.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_tests_functional.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_tests_integration.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_tests_locustfile.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_tests_unit.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/feature/feature_webpack.config.js.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/licenses/COPYING +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/licenses/COPYING.LESSER +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_.env.dev.example.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_.env.prod.example.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_.gitignore.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_00_core_requirements_dev.sh.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_00_install_features.sh.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_01_compile_assets.sh.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_02_0_db_wait_connection.sh.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_02_1_db_create_db_test.sh.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_02_2_db_create_splent_migrations.sh.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_03_initialize_migrations.sh.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_04_handle_migrations.sh.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_05_0_start_app_dev.sh.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_05_1_start_app_prod.sh.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_400.html +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_401.html +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_404.html +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_500.html +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_Dockerfile.dev.dockerignore.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_Dockerfile.dev.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_Dockerfile.prod.dockerignore.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_README.md.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_app.css +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_app.js +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_base_template.html +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_ci_upgrade.yml.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_config.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_docker-compose.dev.yml.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_docker-compose.prod.yml.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_dropzone.css +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_entrypoint.dev.sh.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_entrypoint.prod.sh.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_errors.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_favicon.svg +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_init.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_logging.py.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_own.css +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_package.json.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_pyproject.toml.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/templates/product/product_uvl.j2 +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/__init__.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/archetype.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/cache_utils.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/ci_services.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/ci_workflow.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/command_loader.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/contract_freshness.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/db_client.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/db_utils.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/decorators.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/dynamic_imports.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/feature_installer.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/feature_naming.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/feature_params.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/feature_requirements.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/feature_resolution.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/feature_utils.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/forge.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/git_url.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/integrity.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/io_utils.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/lifecycle.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/manifest.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/menu_keys.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/migration_lock.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/path_utils.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/proc.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/redact.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/refinement_facts.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/running_version.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/template_drift.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/toml_edit.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/upgrade_plan.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/uvl_diagnosis.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli/utils/uvl_params.py +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli.egg-info/dependency_links.txt +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli.egg-info/entry_points.txt +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli.egg-info/requires.txt +0 -0
- {splent_cli-1.46.0 → splent_cli-1.47.0}/src/splent_cli.egg-info/top_level.txt +0 -0
|
@@ -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.47.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"
|
|
@@ -78,8 +78,10 @@ where = ["src"]
|
|
|
78
78
|
|
|
79
79
|
# The templates product:create, feature:create and the rest render. They are
|
|
80
80
|
# data, not modules, so without this a wheel from PyPI shipped none of them.
|
|
81
|
+
# The build context of the frontend toolchain image too, so a CLI installed
|
|
82
|
+
# from PyPI can build the image a feature with frontend/ builds in.
|
|
81
83
|
[tool.setuptools.package-data]
|
|
82
|
-
splent_cli = ["templates/**/*"]
|
|
84
|
+
splent_cli = ["templates/**/*", "toolchain/**/*"]
|
|
83
85
|
|
|
84
86
|
[tool.pytest.ini_options]
|
|
85
87
|
testpaths = ["tests"]
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import dataclasses
|
|
1
2
|
import logging
|
|
2
3
|
import os
|
|
3
4
|
import posixpath
|
|
4
5
|
import shlex
|
|
5
6
|
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
6
8
|
|
|
7
9
|
import click
|
|
8
10
|
|
|
@@ -368,20 +370,43 @@ def _compile_frontend(root, feature, *, watch, production, release_build) -> boo
|
|
|
368
370
|
|
|
369
371
|
|
|
370
372
|
def _verify_features(features, workspace: str) -> int:
|
|
371
|
-
"""feature:compile --verify: 1 when the
|
|
372
|
-
frontend/ does not match its
|
|
373
|
-
|
|
373
|
+
"""feature:compile --verify: 1 when the bundle of any feature with
|
|
374
|
+
frontend/ does not match its manifest, else 0.
|
|
375
|
+
|
|
376
|
+
A working copy is checked at the workspace root, a pinned feature in the
|
|
377
|
+
copy the product links, which is what its app serves, and where neither
|
|
378
|
+
is, as in the production image, in the package pip installed. When the
|
|
379
|
+
product has docker/frontend.lock.json (product:build writes it), every
|
|
380
|
+
feature it lists is checked too, and has to carry the manifest the lock
|
|
381
|
+
names, of the version it names: the bundle its release built.
|
|
382
|
+
"""
|
|
383
|
+
from splent_cli.utils.frontend_check import read_lock
|
|
384
|
+
|
|
385
|
+
product = context.active_app(workspace)
|
|
386
|
+
lock = read_lock(os.path.join(workspace, product)) if product else None
|
|
387
|
+
locked = (lock or {}).get("features") or {}
|
|
388
|
+
refs = list(features)
|
|
389
|
+
named = {_split_ref(feature)[1] for feature in refs}
|
|
390
|
+
for name, entry in sorted(locked.items()):
|
|
391
|
+
if name not in named:
|
|
392
|
+
version = entry.get("version")
|
|
393
|
+
namespace = entry.get("namespace") or "splent_io"
|
|
394
|
+
refs.append(f"{namespace}/{name}" + (f"@v{version}" if version else ""))
|
|
395
|
+
|
|
374
396
|
failed = 0
|
|
375
|
-
for feature in
|
|
397
|
+
for feature in refs:
|
|
376
398
|
namespace, base_name, version = _split_ref(feature)
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
399
|
+
entry = locked.get(base_name)
|
|
400
|
+
copy = _copy_of(workspace, product, namespace, base_name, version)
|
|
401
|
+
if copy is None and entry is not None:
|
|
402
|
+
failed += 1
|
|
403
|
+
click.secho(
|
|
404
|
+
f"❌ {feature}: docker/frontend.lock.json lists it, and it is "
|
|
405
|
+
"neither here nor installed",
|
|
406
|
+
fg="red",
|
|
381
407
|
)
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if not frontend.declares(root):
|
|
408
|
+
continue
|
|
409
|
+
if entry is None and (copy is None or not copy.declares):
|
|
385
410
|
click.echo(
|
|
386
411
|
click.style(
|
|
387
412
|
f" {feature} has no frontend/, nothing to verify.",
|
|
@@ -389,7 +414,11 @@ def _verify_features(features, workspace: str) -> int:
|
|
|
389
414
|
)
|
|
390
415
|
)
|
|
391
416
|
continue
|
|
392
|
-
problems = frontend.
|
|
417
|
+
problems = frontend.verify_assets(
|
|
418
|
+
copy.assets, lock=copy.lock, production=bool(version) or copy.installed
|
|
419
|
+
)
|
|
420
|
+
if entry is not None:
|
|
421
|
+
problems += _against_lock(copy, entry, base_name)
|
|
393
422
|
if problems:
|
|
394
423
|
failed += 1
|
|
395
424
|
click.secho(
|
|
@@ -402,6 +431,101 @@ def _verify_features(features, workspace: str) -> int:
|
|
|
402
431
|
return 1 if failed else 0
|
|
403
432
|
|
|
404
433
|
|
|
434
|
+
@dataclasses.dataclass
|
|
435
|
+
class _Copy:
|
|
436
|
+
"""The copy of a feature --verify checks: its assets/, its frontend
|
|
437
|
+
lock (a package pip installed has none), whether pip installed it, and
|
|
438
|
+
whether it declares frontend/ (an installed one does when it carries a
|
|
439
|
+
manifest)."""
|
|
440
|
+
|
|
441
|
+
assets: Path
|
|
442
|
+
lock: Path | None
|
|
443
|
+
installed: bool
|
|
444
|
+
declares: bool
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def _copy_of(workspace, product, namespace, base_name, version) -> _Copy | None:
|
|
448
|
+
"""The working copy, the product's link to a pinned release, or else
|
|
449
|
+
the package pip installed. None when there is none."""
|
|
450
|
+
if version:
|
|
451
|
+
root = (
|
|
452
|
+
os.path.join(
|
|
453
|
+
workspace, product, "features", namespace, f"{base_name}@{version}"
|
|
454
|
+
)
|
|
455
|
+
if product
|
|
456
|
+
else None
|
|
457
|
+
)
|
|
458
|
+
else:
|
|
459
|
+
root = os.path.join(workspace, base_name)
|
|
460
|
+
if root and os.path.isdir(root):
|
|
461
|
+
return _Copy(
|
|
462
|
+
frontend.assets_dir(root, namespace, base_name),
|
|
463
|
+
Path(root) / frontend.FRONTEND_DIR / frontend.LOCK,
|
|
464
|
+
False,
|
|
465
|
+
frontend.declares(root),
|
|
466
|
+
)
|
|
467
|
+
import importlib.util
|
|
468
|
+
|
|
469
|
+
try:
|
|
470
|
+
spec = importlib.util.find_spec(f"{namespace}.{base_name}")
|
|
471
|
+
except (ImportError, ValueError):
|
|
472
|
+
spec = None
|
|
473
|
+
if spec is None or not spec.submodule_search_locations:
|
|
474
|
+
return None
|
|
475
|
+
assets = Path(list(spec.submodule_search_locations)[0]) / "assets"
|
|
476
|
+
return _Copy(assets, None, True, (assets / "meta" / "manifest.json").is_file())
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def _against_lock(copy: _Copy, entry: dict, base_name: str) -> list[str]:
|
|
480
|
+
"""What does not match between a feature's bundle and its entry in
|
|
481
|
+
docker/frontend.lock.json."""
|
|
482
|
+
problems = []
|
|
483
|
+
wanted = entry.get("manifest_sha256")
|
|
484
|
+
found = frontend.manifest_sha256(copy.assets)
|
|
485
|
+
if wanted and found and found != wanted:
|
|
486
|
+
problems.append(
|
|
487
|
+
"assets/meta/manifest.json is not the one docker/frontend.lock.json "
|
|
488
|
+
"names: run splent product:build"
|
|
489
|
+
)
|
|
490
|
+
if not copy.installed:
|
|
491
|
+
return problems
|
|
492
|
+
import importlib.metadata
|
|
493
|
+
|
|
494
|
+
distribution = entry.get("distribution") or base_name
|
|
495
|
+
try:
|
|
496
|
+
installed = importlib.metadata.version(distribution)
|
|
497
|
+
except importlib.metadata.PackageNotFoundError:
|
|
498
|
+
installed = None
|
|
499
|
+
if entry.get("version") and installed != entry["version"]:
|
|
500
|
+
problems.append(
|
|
501
|
+
f"pip installed {installed or 'no version'}, and "
|
|
502
|
+
f"docker/frontend.lock.json names {entry['version']}"
|
|
503
|
+
)
|
|
504
|
+
if found is None:
|
|
505
|
+
problems.append(
|
|
506
|
+
f"it was installed {_channel(distribution)}, without the bundle its "
|
|
507
|
+
"release built: release it again, so its wheel and its tag carry it"
|
|
508
|
+
)
|
|
509
|
+
return problems
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def _channel(distribution: str) -> str:
|
|
513
|
+
"""How pip installed DISTRIBUTION: from its git tag (git+) or a wheel."""
|
|
514
|
+
import importlib.metadata
|
|
515
|
+
import json
|
|
516
|
+
|
|
517
|
+
try:
|
|
518
|
+
direct = importlib.metadata.distribution(distribution).read_text(
|
|
519
|
+
"direct_url.json"
|
|
520
|
+
)
|
|
521
|
+
url = json.loads(direct or "{}")
|
|
522
|
+
except (importlib.metadata.PackageNotFoundError, ValueError):
|
|
523
|
+
url = {}
|
|
524
|
+
if "vcs_info" in url:
|
|
525
|
+
return f"from its git tag (git+{url.get('url', '')})"
|
|
526
|
+
return "from a wheel"
|
|
527
|
+
|
|
528
|
+
|
|
405
529
|
def _feature_ref(name: str) -> str:
|
|
406
530
|
"""*name* as typed, with the ``splent_feature_`` prefix on the feature's
|
|
407
531
|
own name: ``auth`` and ``splent_io/auth`` are ``splent_feature_auth``
|
|
@@ -62,6 +62,10 @@ LEGACY_REQUIREMENTS_FILE = "requirements.prod.txt"
|
|
|
62
62
|
# Dockerfile applies it as a constraint on every pip install, so two builds of
|
|
63
63
|
# one commit install the same set. Versioned in docker/.
|
|
64
64
|
LOCK_FILE = "requirements.lock"
|
|
65
|
+
# What the features' bundles are and ship, which the image verifies the
|
|
66
|
+
# installed ones against (splent_cli.utils.frontend_check). Versioned in
|
|
67
|
+
# docker/ too.
|
|
68
|
+
FRONTEND_LOCK_FILE = "frontend.lock.json"
|
|
65
69
|
LOCK_HEADER = (
|
|
66
70
|
"# Every third-party package the production image installs, at the version\n"
|
|
67
71
|
"# the last build resolved. Written by splent product:build and\n"
|
|
@@ -1202,6 +1206,30 @@ def _contracts_snapshot(workspace: str, declared_features: Sequence[str]) -> dic
|
|
|
1202
1206
|
return {"format": CONTRACTS_FORMAT, "features": features}
|
|
1203
1207
|
|
|
1204
1208
|
|
|
1209
|
+
def write_frontend_lock(
|
|
1210
|
+
workspace: str, product_path: str, data: dict, declared_features: Sequence[str]
|
|
1211
|
+
) -> str:
|
|
1212
|
+
"""Write docker/frontend.lock.json for the features the image installs.
|
|
1213
|
+
|
|
1214
|
+
For each one with frontend/, the release, the sha256 of the manifest its
|
|
1215
|
+
bundle carries and what the bundle ships, with the provider that
|
|
1216
|
+
satisfies each of its peers; the shell; and the features still built in
|
|
1217
|
+
legacy mode (splent_cli.utils.frontend_check.lock). The production
|
|
1218
|
+
image checks every bundle pip installed against it, and the product's
|
|
1219
|
+
CI audits what they ship from it. Returns its path.
|
|
1220
|
+
"""
|
|
1221
|
+
from splent_cli.utils import frontend_check
|
|
1222
|
+
|
|
1223
|
+
check = frontend_check.check_product(workspace, product_path, declared_features)
|
|
1224
|
+
path = os.path.join(product_path, "docker", FRONTEND_LOCK_FILE)
|
|
1225
|
+
atomic_write(
|
|
1226
|
+
path,
|
|
1227
|
+
frontend_check.lock_text(frontend_check.lock(check, data)),
|
|
1228
|
+
dir_owner=True,
|
|
1229
|
+
)
|
|
1230
|
+
return path
|
|
1231
|
+
|
|
1232
|
+
|
|
1205
1233
|
def _not_shipped(directory, names):
|
|
1206
1234
|
"""Bytecode a local run of a feature left behind, never part of the bundle."""
|
|
1207
1235
|
return {n for n in names if n == "__pycache__" or n.endswith(".pyc")}
|
|
@@ -1481,7 +1509,8 @@ def write_deploy_artifacts(
|
|
|
1481
1509
|
* ``[tool.splent.runtime]`` in pyproject.toml, the framework and CLI the
|
|
1482
1510
|
image installs from PyPI;
|
|
1483
1511
|
* ``splent.contracts.json`` and ``docker/spl/``, the refinement metadata
|
|
1484
|
-
and the UVL model the image reads
|
|
1512
|
+
and the UVL model the image reads;
|
|
1513
|
+
* ``docker/frontend.lock.json``, the bundles the image verifies.
|
|
1485
1514
|
|
|
1486
1515
|
All of them are versioned with the product. product:build and
|
|
1487
1516
|
product:release both go through here, so a release commits exactly what
|
|
@@ -1791,6 +1820,12 @@ def write_deploy_artifacts(
|
|
|
1791
1820
|
written.append(CONTRACTS_SNAPSHOT)
|
|
1792
1821
|
click.echo(f"✅ Created: {contracts_path}")
|
|
1793
1822
|
|
|
1823
|
+
frontend_lock = write_frontend_lock(
|
|
1824
|
+
workspace, product_path, data, declared_features
|
|
1825
|
+
)
|
|
1826
|
+
written.append(f"docker/{FRONTEND_LOCK_FILE}")
|
|
1827
|
+
click.echo(f"✅ Created: {frontend_lock}")
|
|
1828
|
+
|
|
1794
1829
|
staged_model = stage_spl_model(workspace, product, docker_path)
|
|
1795
1830
|
if staged_model:
|
|
1796
1831
|
written.append(f"docker/{SPL_STAGING_DIR}")
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""
|
|
2
|
+
product:frontend:audit — what the product's bundles ship, audited as one set.
|
|
3
|
+
|
|
4
|
+
Each feature with frontend/ audits its own lock when it is released (npm
|
|
5
|
+
audit, services/frontend.py), but a bundle is immutable: an advisory
|
|
6
|
+
published later reaches the product only through a new release of every
|
|
7
|
+
feature that ships the package. This audits the union of what the bundles
|
|
8
|
+
ship, as docker/frontend.lock.json records it (product:build writes it),
|
|
9
|
+
against npm's advisory database, and names the features to release again.
|
|
10
|
+
|
|
11
|
+
npm audit needs a lock it could install, and the product has none, so the
|
|
12
|
+
packages go straight to the registry's bulk advisory endpoint, the one npm
|
|
13
|
+
audit asks, and each advisory is matched against the shipped version here.
|
|
14
|
+
A critical advisory fails, as it fails a feature's release; a high one is
|
|
15
|
+
said; an audit that cannot reach the registry fails too. --sbom writes the
|
|
16
|
+
CycloneDX SBOM of the whole: the product, each feature and what each one
|
|
17
|
+
ships, with what the features vendor outside npm when their snapshot is at
|
|
18
|
+
hand.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import datetime
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import uuid
|
|
25
|
+
|
|
26
|
+
import click
|
|
27
|
+
|
|
28
|
+
from splent_cli.services import compose, context, frontend
|
|
29
|
+
from splent_cli.utils.frontend_check import LOCK_FILE, read_lock
|
|
30
|
+
from splent_cli.utils.npm_semver import satisfies
|
|
31
|
+
|
|
32
|
+
DEFAULT_REGISTRY = "https://registry.npmjs.org"
|
|
33
|
+
ADVISORIES = "/-/npm/v1/security/advisories/bulk"
|
|
34
|
+
REQUEST_TIMEOUT = 30
|
|
35
|
+
BLOCKING = ("critical",)
|
|
36
|
+
WARNING = ("high",)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def shipped(lock: dict) -> dict[str, dict[str, list[str]]]:
|
|
40
|
+
"""``{package: {version: [features]}}`` of what the bundles in LOCK ship."""
|
|
41
|
+
found: dict[str, dict[str, list[str]]] = {}
|
|
42
|
+
for name, feature in sorted((lock.get("features") or {}).items()):
|
|
43
|
+
short = name.removeprefix("splent_feature_")
|
|
44
|
+
for package, described in (feature.get("bundled") or {}).items():
|
|
45
|
+
for one in str(described).split(", "):
|
|
46
|
+
version = one.partition(" ")[0]
|
|
47
|
+
found.setdefault(package, {}).setdefault(version, []).append(short)
|
|
48
|
+
return found
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def registry() -> str:
|
|
52
|
+
"""The npm registry, as npm's own setting names it."""
|
|
53
|
+
return (os.getenv("NPM_CONFIG_REGISTRY") or DEFAULT_REGISTRY).rstrip("/")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def advisories(packages: dict) -> dict:
|
|
57
|
+
"""The registry's advisories for PACKAGES, ``{package: [advisory]}``.
|
|
58
|
+
Raises click.ClickException when the registry cannot be asked."""
|
|
59
|
+
import requests
|
|
60
|
+
|
|
61
|
+
body = {name: sorted(versions) for name, versions in packages.items()}
|
|
62
|
+
try:
|
|
63
|
+
response = requests.post(
|
|
64
|
+
registry() + ADVISORIES, json=body, timeout=REQUEST_TIMEOUT
|
|
65
|
+
)
|
|
66
|
+
except requests.RequestException as e:
|
|
67
|
+
raise click.ClickException(f"The npm registry could not be asked: {e}")
|
|
68
|
+
if response.status_code != 200:
|
|
69
|
+
raise click.ClickException(
|
|
70
|
+
f"The npm registry answered {response.status_code} to the audit."
|
|
71
|
+
)
|
|
72
|
+
try:
|
|
73
|
+
return response.json()
|
|
74
|
+
except ValueError:
|
|
75
|
+
raise click.ClickException("The npm registry's answer is not JSON.")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def findings(packages: dict, found: dict) -> list[dict]:
|
|
79
|
+
"""Each advisory of FOUND that covers a version PACKAGES ships, with the
|
|
80
|
+
features that ship it, most severe first."""
|
|
81
|
+
order = {"critical": 0, "high": 1, "moderate": 2, "low": 3, "info": 4}
|
|
82
|
+
result = []
|
|
83
|
+
for name, versions in packages.items():
|
|
84
|
+
for advisory in found.get(name) or []:
|
|
85
|
+
vulnerable = advisory.get("vulnerable_versions") or "*"
|
|
86
|
+
for version, features in sorted(versions.items()):
|
|
87
|
+
try:
|
|
88
|
+
covered = satisfies(version, vulnerable)
|
|
89
|
+
except ValueError:
|
|
90
|
+
covered = True
|
|
91
|
+
if covered:
|
|
92
|
+
result.append(
|
|
93
|
+
{
|
|
94
|
+
"package": name,
|
|
95
|
+
"version": version,
|
|
96
|
+
"features": features,
|
|
97
|
+
"severity": advisory.get("severity") or "unknown",
|
|
98
|
+
"title": advisory.get("title") or "",
|
|
99
|
+
"url": advisory.get("url") or "",
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
return sorted(
|
|
103
|
+
result, key=lambda f: (order.get(f["severity"], 5), f["package"], f["version"])
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def sbom(lock: dict, workspace: str, product: str, version: str) -> dict:
|
|
108
|
+
"""The CycloneDX 1.5 SBOM of the product's bundles: a component for the
|
|
109
|
+
product, one for each feature and one for each package a bundle ships,
|
|
110
|
+
each feature depending on what it ships."""
|
|
111
|
+
from splent_cli.utils.running_version import cli_version
|
|
112
|
+
|
|
113
|
+
components: dict[str, dict] = {}
|
|
114
|
+
dependencies: list[dict] = []
|
|
115
|
+
for name, feature in sorted((lock.get("features") or {}).items()):
|
|
116
|
+
distribution = feature.get("distribution") or name
|
|
117
|
+
ref = f"pkg:pypi/{distribution}@{feature.get('version') or 'editable'}"
|
|
118
|
+
components[ref] = {
|
|
119
|
+
"type": "library",
|
|
120
|
+
"bom-ref": ref,
|
|
121
|
+
"name": distribution,
|
|
122
|
+
"version": feature.get("version") or "editable",
|
|
123
|
+
"purl": ref,
|
|
124
|
+
"properties": [
|
|
125
|
+
{
|
|
126
|
+
"name": "splent:manifest_sha256",
|
|
127
|
+
"value": feature.get("manifest_sha256"),
|
|
128
|
+
}
|
|
129
|
+
],
|
|
130
|
+
}
|
|
131
|
+
depends = []
|
|
132
|
+
for package, described in sorted((feature.get("bundled") or {}).items()):
|
|
133
|
+
for one in str(described).split(", "):
|
|
134
|
+
version_, _, license_ = one.partition(" ")
|
|
135
|
+
purl = frontend._purl(package, version_)
|
|
136
|
+
depends.append(purl)
|
|
137
|
+
components.setdefault(
|
|
138
|
+
purl,
|
|
139
|
+
{
|
|
140
|
+
"type": "library",
|
|
141
|
+
"bom-ref": purl,
|
|
142
|
+
"name": package,
|
|
143
|
+
"version": version_,
|
|
144
|
+
"purl": purl,
|
|
145
|
+
"licenses": frontend._cyclonedx_licenses(
|
|
146
|
+
None if license_ in ("", "UNKNOWN") else license_
|
|
147
|
+
),
|
|
148
|
+
},
|
|
149
|
+
)
|
|
150
|
+
for vendored in _vendored(workspace, feature, name):
|
|
151
|
+
depends.append(vendored["bom-ref"])
|
|
152
|
+
components.setdefault(vendored["bom-ref"], vendored)
|
|
153
|
+
dependencies.append({"ref": ref, "dependsOn": sorted(set(depends))})
|
|
154
|
+
product_ref = f"pkg:generic/{product}@{version}"
|
|
155
|
+
dependencies.insert(
|
|
156
|
+
0, {"ref": product_ref, "dependsOn": [d["ref"] for d in dependencies]}
|
|
157
|
+
)
|
|
158
|
+
return {
|
|
159
|
+
"bomFormat": "CycloneDX",
|
|
160
|
+
"specVersion": "1.5",
|
|
161
|
+
"serialNumber": f"urn:uuid:{uuid.uuid4()}",
|
|
162
|
+
"version": 1,
|
|
163
|
+
"metadata": {
|
|
164
|
+
"timestamp": datetime.datetime.now(datetime.timezone.utc)
|
|
165
|
+
.replace(microsecond=0)
|
|
166
|
+
.isoformat(),
|
|
167
|
+
"tools": {
|
|
168
|
+
"components": [
|
|
169
|
+
{
|
|
170
|
+
"type": "application",
|
|
171
|
+
"name": "splent_cli",
|
|
172
|
+
"version": cli_version(),
|
|
173
|
+
}
|
|
174
|
+
]
|
|
175
|
+
},
|
|
176
|
+
"component": {
|
|
177
|
+
"type": "application",
|
|
178
|
+
"bom-ref": product_ref,
|
|
179
|
+
"name": product,
|
|
180
|
+
"version": version,
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
"components": list(components.values()),
|
|
184
|
+
"dependencies": dependencies,
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _vendored(workspace: str, feature: dict, name: str) -> list[dict]:
|
|
189
|
+
"""The components a feature's release SBOM lists as vendored (files of
|
|
190
|
+
a third party that come from no npm package), when its copy is here."""
|
|
191
|
+
entry = f"{feature.get('namespace') or 'splent_io'}/{name}"
|
|
192
|
+
if feature.get("version"):
|
|
193
|
+
entry += f"@v{feature['version']}"
|
|
194
|
+
root = compose.feature_dir(workspace, entry)
|
|
195
|
+
assets = frontend.assets_dir(root, feature.get("namespace") or "splent_io", name)
|
|
196
|
+
try:
|
|
197
|
+
document = json.loads((assets / "meta" / "sbom.cdx.json").read_text())
|
|
198
|
+
except (OSError, ValueError):
|
|
199
|
+
return []
|
|
200
|
+
return [
|
|
201
|
+
component
|
|
202
|
+
for component in document.get("components") or []
|
|
203
|
+
if {"name": "splent:origin", "value": "vendored"}
|
|
204
|
+
in (component.get("properties") or [])
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@click.command(
|
|
209
|
+
"product:frontend:audit",
|
|
210
|
+
short_help="Audit what the product's bundles ship against npm's advisories.",
|
|
211
|
+
)
|
|
212
|
+
@click.option(
|
|
213
|
+
"--sbom",
|
|
214
|
+
"sbom_path",
|
|
215
|
+
default=None,
|
|
216
|
+
type=click.Path(dir_okay=False, writable=True),
|
|
217
|
+
help="Also write the CycloneDX SBOM of the product's bundles to this file.",
|
|
218
|
+
)
|
|
219
|
+
def product_frontend_audit(sbom_path):
|
|
220
|
+
"""
|
|
221
|
+
Audit the packages the product's bundles ship, as one set.
|
|
222
|
+
|
|
223
|
+
\b
|
|
224
|
+
Reads docker/frontend.lock.json, which product:build writes, and asks
|
|
225
|
+
npm's registry for the advisories of every package any bundle ships.
|
|
226
|
+
Each one that covers a shipped version is listed with the features
|
|
227
|
+
that ship it, which a fix reaches only through a new release of each.
|
|
228
|
+
A critical advisory fails, a high one is said, the rest are counted.
|
|
229
|
+
An audit that cannot reach the registry fails too.
|
|
230
|
+
|
|
231
|
+
\b
|
|
232
|
+
--sbom writes the CycloneDX SBOM of the whole: the product, each
|
|
233
|
+
feature and what it ships.
|
|
234
|
+
|
|
235
|
+
\b
|
|
236
|
+
Examples:
|
|
237
|
+
splent product:frontend:audit
|
|
238
|
+
splent product:frontend:audit --sbom frontend.sbom.cdx.json
|
|
239
|
+
"""
|
|
240
|
+
product = context.require_app()
|
|
241
|
+
workspace = str(context.workspace())
|
|
242
|
+
product_dir = os.path.join(workspace, product)
|
|
243
|
+
lock = read_lock(product_dir)
|
|
244
|
+
if lock is None:
|
|
245
|
+
raise click.ClickException(
|
|
246
|
+
f"{product} has no docker/{LOCK_FILE}. Run splent product:build, which "
|
|
247
|
+
"writes it."
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
packages = shipped(lock)
|
|
251
|
+
count = sum(len(versions) for versions in packages.values())
|
|
252
|
+
click.echo()
|
|
253
|
+
click.echo(
|
|
254
|
+
click.style(f" Auditing what the bundles of {product} ship...", bold=True)
|
|
255
|
+
)
|
|
256
|
+
click.echo()
|
|
257
|
+
|
|
258
|
+
if sbom_path:
|
|
259
|
+
document = sbom(lock, workspace, product, _product_version(product_dir))
|
|
260
|
+
with open(sbom_path, "w", encoding="utf-8") as handle:
|
|
261
|
+
handle.write(json.dumps(document, indent=2) + "\n")
|
|
262
|
+
click.secho(
|
|
263
|
+
f" ℹ️ SBOM of {len(document['components'])} component(s) written to "
|
|
264
|
+
f"{sbom_path}",
|
|
265
|
+
fg="bright_black",
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
failed = False
|
|
269
|
+
if packages:
|
|
270
|
+
found = findings(packages, advisories(packages))
|
|
271
|
+
for item in found:
|
|
272
|
+
line = (
|
|
273
|
+
f"{item['package']} {item['version']} ({item['severity']}): "
|
|
274
|
+
f"{item['title']} {item['url']}".rstrip()
|
|
275
|
+
+ f". Shipped by {', '.join(item['features'])}, to release again "
|
|
276
|
+
"with a fixed version."
|
|
277
|
+
)
|
|
278
|
+
if item["severity"] in BLOCKING:
|
|
279
|
+
click.secho(f" ❌ {line}", fg="red")
|
|
280
|
+
failed = True
|
|
281
|
+
elif item["severity"] in WARNING:
|
|
282
|
+
click.secho(f" ⚠️ {line}", fg="yellow")
|
|
283
|
+
rest = [f for f in found if f["severity"] not in BLOCKING + WARNING]
|
|
284
|
+
if rest:
|
|
285
|
+
click.secho(
|
|
286
|
+
f" ℹ️ {len(rest)} advisory(ies) of lower severity, which do not "
|
|
287
|
+
"fail the audit",
|
|
288
|
+
fg="bright_black",
|
|
289
|
+
)
|
|
290
|
+
if not found:
|
|
291
|
+
click.secho(
|
|
292
|
+
f" ✅ No advisory covers the {count} package version(s) the "
|
|
293
|
+
f"bundles of {len(lock.get('features') or {})} feature(s) ship",
|
|
294
|
+
fg="green",
|
|
295
|
+
)
|
|
296
|
+
else:
|
|
297
|
+
click.secho(
|
|
298
|
+
" ✅ No bundle of this product ships an npm package", fg="green"
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
click.echo()
|
|
302
|
+
if failed:
|
|
303
|
+
click.secho(
|
|
304
|
+
" ❌ A bundle ships a package with a critical advisory.",
|
|
305
|
+
fg="red",
|
|
306
|
+
bold=True,
|
|
307
|
+
)
|
|
308
|
+
click.echo()
|
|
309
|
+
raise SystemExit(1)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _product_version(product_dir: str) -> str:
|
|
313
|
+
import tomllib
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
with open(os.path.join(product_dir, "pyproject.toml"), "rb") as handle:
|
|
317
|
+
data = tomllib.load(handle)
|
|
318
|
+
except (OSError, tomllib.TOMLDecodeError):
|
|
319
|
+
return "unknown"
|
|
320
|
+
return str(data.get("project", {}).get("version") or "unknown")
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
cli_command = product_frontend_audit
|
|
@@ -11,6 +11,7 @@ import tempfile
|
|
|
11
11
|
import click
|
|
12
12
|
from splent_cli.commands.product.product_build import (
|
|
13
13
|
CONTRACTS_SNAPSHOT,
|
|
14
|
+
FRONTEND_LOCK_FILE,
|
|
14
15
|
LOCK_FILE,
|
|
15
16
|
SPL_STAGING_DIR,
|
|
16
17
|
build_production_image,
|
|
@@ -126,6 +127,7 @@ RELEASE_ARTIFACTS = (
|
|
|
126
127
|
# before they exist.
|
|
127
128
|
f"{deploy_bundle.BUNDLE_DIR}/",
|
|
128
129
|
f"docker/{LOCK_FILE}",
|
|
130
|
+
f"docker/{FRONTEND_LOCK_FILE}",
|
|
129
131
|
CONTRACTS_SNAPSHOT,
|
|
130
132
|
f"docker/{SPL_STAGING_DIR}/",
|
|
131
133
|
)
|