datex-studio-cli 0.4.18__tar.gz → 0.4.19__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.
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/PKG-INFO +1 -1
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/pyproject.toml +1 -1
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/__init__.py +1 -1
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/auth.py +188 -31
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/branch.py +202 -35
- datex_studio_cli-0.4.19/src/dxs/commands/branch_commit_guard.py +177 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/cascade.py +16 -37
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/configuration.py +88 -1
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/crm.py +404 -276
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/crm_delivery.py +70 -20
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/crm_time.py +91 -16
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/explore.py +82 -2
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/source.py +71 -5
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/auth/decorators.py +25 -29
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/auth/token_cache.py +82 -13
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/datasource/generator.py +5 -1
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/datasource/models.py +42 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/datasource/parsers.py +8 -1
- datex_studio_cli-0.4.19/src/dxs/core/dynamics/pagination.py +159 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/output/redaction.py +72 -5
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/utils/errors.py +42 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/utils/resolvers.py +88 -1
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/.gitignore +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/CONTEXT.md +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/README.md +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/otel/.env.example +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/otel/Dockerfile +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/otel/README.md +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/otel/apply-local-cache.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/otel/collector-config.yaml +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/otel/docker-compose.yml +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/otel/local-test-settings.json +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/otel/remote-settings.json +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/report-creator.skill +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/scripts/analyze_corpus.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/scripts/generate_rdlx_schema.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/__main__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/cli.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/__init__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/api.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/config.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/datasource.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/devops.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/document.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/endpoint.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/env.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/function.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/init.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/marketplace.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/ng/__init__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/ng/_common.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/ng/api.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/ng/create.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/ng/data.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/ng/pack.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/ng/preview.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/odata.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/organization.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/proxy.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/reference.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/release_notes.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/repo.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/report/__init__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/report/add_cmds.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/report/api.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/report/batch.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/report/create.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/report/data.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/report/dataset.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/report/edit.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/report/folder.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/report/preview.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/report/schema_cmds.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/reset.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/schema.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/servicepack.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/studio.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/telemetry.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/user.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/commands/validation_report.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/context.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/__init__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/agent_browser.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/api/__init__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/api/app_config.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/api/client.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/api/endpoints.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/api/metadata_models.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/api/models.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/api/reference_graph.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/auth/__init__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/auth/datex_tenant.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/auth/msal_client.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/auth/tenants.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/cache.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/datasource/__init__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/datasource/flow_generator.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/datasource/flow_models.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/datasource/flow_shape.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/datasource/resolver.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/datasource/type_def_parser.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/datasource/validator.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/designer/__init__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/designer/flow_models.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/designer/models.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/designer/parsers.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/devops/__init__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/devops/client.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/devops/helpers.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/devops/models.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/dynamics/__init__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/dynamics/client.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/dynamics/html.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/dynamics/metadata_cache.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/dynamics/resolvers.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/dynamics/time_entry.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/footprint/__init__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/footprint/client.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/footprint/edmx_parser.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/footprint/metadata.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/function/__init__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/function/generator.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/function/models.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/function/validator.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/graph.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/onboarding.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/output/__init__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/output/csv_fmt.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/output/formatter.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/output/json_fmt.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/output/yaml_fmt.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/release_notes.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/responses.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/telemetry/__init__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/telemetry/config.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/telemetry/identity.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/telemetry/recorder.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/telemetry/scrubber.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/core/telemetry/sender.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/models/__init__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/ng/__init__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/ng/folder.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/ng/manifest.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/ng/mocks.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/ng/preview.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/ng/scaffold.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/ng/serve_lock.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/py.typed +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/report/__init__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/report/datasource_binding.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/report/engine.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/report/field_parser.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/report/folder.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/report/manifest.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/report/models.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/report/preview.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/report/preview_arjs.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/report/schema.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/report/svg_renderer.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/report/templates/__init__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/report/templates/bill_of_lading.rdlx-json +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/report/templates/shipping_label.rdlx-json +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/report/validate_arjs.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/report/validator.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/report/wrapper.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/utils/__init__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/utils/click_options.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/utils/clipboard.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/utils/config.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/utils/filtering.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/utils/image.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/utils/paths.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/utils/responses.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/utils/restricted.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/utils/sorting.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/utils/update_check.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/utils/versioning.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/__init__.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/app.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/design.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/proxy_app.py +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/404/index.html +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/404.html +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/UgyAMtPjH-Pt0RYWOHzIT/_buildManifest.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/UgyAMtPjH-Pt0RYWOHzIT/_ssgManifest.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/chunks/0b24cca5-c1e1c8810348f107.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/chunks/255-102f2e5b2e3dc2ef.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/chunks/538-84e2e111415f1fda.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/chunks/697-0e000ab410d9f470.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/chunks/871-1acacdb7b4022abf.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/chunks/8b8f67fc-6295949a4b5485a4.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/chunks/909-c88abba3cf0caec3.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/chunks/app/_not-found/page-ea1be7001c230704.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/chunks/app/design/capture/page-a5e129c2d223432c.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/chunks/app/design/page-21ba66a3dd3b03f0.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/chunks/app/layout-2cc6eac09e476c91.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/chunks/app/page-20c358395882cdac.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/chunks/framework-de98b93a850cfc71.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/chunks/main-0f18f91200dac003.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/chunks/main-app-b69998d8941231d8.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/chunks/pages/_app-7d307437aca18ad4.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/chunks/webpack-2b297ada5306c17f.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/_next/static/css/360c657de5e04df8.css +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/design/capture/index.html +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/design/capture/index.txt +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/design/index.html +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/design/index.txt +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/index.html +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/index.txt +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/render-cli.js +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/report-preview.html +0 -0
- {datex_studio_cli-0.4.18 → datex_studio_cli-0.4.19}/src/dxs/web/static/report-validate.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: datex-studio-cli
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.19
|
|
4
4
|
Summary: CLI for Datex Studio low-code platform, designed for LLM-based AI agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/datex/datex-studio-cli
|
|
6
6
|
Project-URL: Documentation, https://github.com/datex/datex-studio-cli
|
|
@@ -16,6 +16,7 @@ from dxs.core.auth.datex_tenant import (
|
|
|
16
16
|
)
|
|
17
17
|
from dxs.core.auth.decorators import get_access_token
|
|
18
18
|
from dxs.core.auth.tenants import (
|
|
19
|
+
identity_needs_tenant_authority,
|
|
19
20
|
is_foreign_tenant,
|
|
20
21
|
is_home_tenant,
|
|
21
22
|
org_needs_tenant_login,
|
|
@@ -148,9 +149,12 @@ def run_standard_device_login(
|
|
|
148
149
|
external_entra_id_domain_name=domain if external else None,
|
|
149
150
|
)
|
|
150
151
|
# Capture the stored key NOW; update_identity_org_info must not change the key
|
|
151
|
-
# out from under us, and the computed key
|
|
152
|
-
#
|
|
153
|
-
|
|
152
|
+
# out from under us, and the computed key changes once discovery fills in the
|
|
153
|
+
# org name on the external-discover path. Resolve rather than recompute: a
|
|
154
|
+
# legacy bare-keyed record for the same account+tenant is updated in place,
|
|
155
|
+
# so the stored key can be the bare username while the computed key is
|
|
156
|
+
# username#tenant.
|
|
157
|
+
identity_key = cache.resolve_stored_key(saved_identity) or saved_identity.identity_key
|
|
154
158
|
|
|
155
159
|
if organization_id is None:
|
|
156
160
|
org_info = _fetch_organization_info(ctx, base_url=api_base_url)
|
|
@@ -487,8 +491,29 @@ def logout(ctx: DxsContext, identity: str | None, logout_all: bool) -> None:
|
|
|
487
491
|
return
|
|
488
492
|
|
|
489
493
|
if identity:
|
|
490
|
-
# Logout specific identity
|
|
494
|
+
# Logout specific identity. Resolve a bare username to its storage key
|
|
495
|
+
# (external identities live under username#org / username#tenant_id);
|
|
496
|
+
# several matches means the selector is ambiguous — never guess which
|
|
497
|
+
# identity to remove.
|
|
498
|
+
storage_key = identity
|
|
491
499
|
existing = cache.get_identity(identity)
|
|
500
|
+
if not existing:
|
|
501
|
+
matches = cache.find_identity_keys_by_username(identity)
|
|
502
|
+
if len(matches) > 1:
|
|
503
|
+
ctx.output_error(
|
|
504
|
+
AuthenticationError(
|
|
505
|
+
message=f"'{identity}' matches more than one identity.",
|
|
506
|
+
code="DXS-AUTH-011",
|
|
507
|
+
suggestions=[
|
|
508
|
+
"Pass one of these exact identities to 'dxs auth logout':",
|
|
509
|
+
*matches,
|
|
510
|
+
],
|
|
511
|
+
)
|
|
512
|
+
)
|
|
513
|
+
raise SystemExit(1)
|
|
514
|
+
if matches:
|
|
515
|
+
storage_key = matches[0]
|
|
516
|
+
existing = cache.get_identity(storage_key)
|
|
492
517
|
if not existing:
|
|
493
518
|
ctx.output(
|
|
494
519
|
single(
|
|
@@ -503,7 +528,7 @@ def logout(ctx: DxsContext, identity: str | None, logout_all: bool) -> None:
|
|
|
503
528
|
return
|
|
504
529
|
|
|
505
530
|
was_active = cache.get_active_identity() == existing
|
|
506
|
-
cache.remove_identity(
|
|
531
|
+
cache.remove_identity(storage_key)
|
|
507
532
|
|
|
508
533
|
remaining = cache.list_identities()
|
|
509
534
|
ctx.output(
|
|
@@ -538,7 +563,9 @@ def logout(ctx: DxsContext, identity: str | None, logout_all: bool) -> None:
|
|
|
538
563
|
return
|
|
539
564
|
|
|
540
565
|
account_name = active.account_username
|
|
541
|
-
|
|
566
|
+
# Remove by the real storage key — external identities are not stored
|
|
567
|
+
# under the bare username.
|
|
568
|
+
cache.remove_identity(cache.resolve_stored_key(active) or account_name or "")
|
|
542
569
|
|
|
543
570
|
remaining = cache.list_identities()
|
|
544
571
|
ctx.output(
|
|
@@ -1230,8 +1257,30 @@ def _switch_by_username(
|
|
|
1230
1257
|
"""Switch to an identity by username (original behavior)."""
|
|
1231
1258
|
cache = MultiIdentityTokenCache()
|
|
1232
1259
|
|
|
1233
|
-
# Check if identity exists
|
|
1260
|
+
# Check if identity exists. External identities are stored under
|
|
1261
|
+
# disambiguated keys (username#org, username#tenant_id), so a bare
|
|
1262
|
+
# username that is not itself a storage key still resolves when it
|
|
1263
|
+
# matches exactly one identity. Matching several is the same-UPN
|
|
1264
|
+
# multi-tenant case: refuse to guess and list the candidates.
|
|
1265
|
+
storage_key = username
|
|
1234
1266
|
existing = cache.get_identity(username)
|
|
1267
|
+
if not existing:
|
|
1268
|
+
matches = cache.find_identity_keys_by_username(username)
|
|
1269
|
+
if len(matches) > 1:
|
|
1270
|
+
ctx.output_error(
|
|
1271
|
+
AuthenticationError(
|
|
1272
|
+
message=f"'{username}' matches more than one identity.",
|
|
1273
|
+
code="DXS-AUTH-011",
|
|
1274
|
+
suggestions=[
|
|
1275
|
+
"Pass one of these exact identities to 'dxs auth switch':",
|
|
1276
|
+
*matches,
|
|
1277
|
+
],
|
|
1278
|
+
)
|
|
1279
|
+
)
|
|
1280
|
+
raise SystemExit(1)
|
|
1281
|
+
if matches:
|
|
1282
|
+
storage_key = matches[0]
|
|
1283
|
+
existing = cache.get_identity(storage_key)
|
|
1235
1284
|
if not existing:
|
|
1236
1285
|
# List available identities for suggestion
|
|
1237
1286
|
available = cache.list_identities()
|
|
@@ -1254,7 +1303,7 @@ def _switch_by_username(
|
|
|
1254
1303
|
# Check if token is expired
|
|
1255
1304
|
if existing.is_expired():
|
|
1256
1305
|
# Try to refresh
|
|
1257
|
-
refreshed = cache.try_refresh_identity(
|
|
1306
|
+
refreshed = cache.try_refresh_identity(storage_key)
|
|
1258
1307
|
if not refreshed:
|
|
1259
1308
|
ctx.output_error(
|
|
1260
1309
|
AuthenticationError(
|
|
@@ -1271,7 +1320,7 @@ def _switch_by_username(
|
|
|
1271
1320
|
|
|
1272
1321
|
# Switch to the new identity
|
|
1273
1322
|
try:
|
|
1274
|
-
new_identity = cache.set_active_identity(
|
|
1323
|
+
new_identity = cache.set_active_identity(storage_key)
|
|
1275
1324
|
except KeyError:
|
|
1276
1325
|
ctx.output_error(
|
|
1277
1326
|
AuthenticationError(
|
|
@@ -1283,7 +1332,7 @@ def _switch_by_username(
|
|
|
1283
1332
|
|
|
1284
1333
|
# Persist daemon credentials on the identity if supplied
|
|
1285
1334
|
if client_id and client_secret:
|
|
1286
|
-
updated = cache.update_identity_daemon_creds(
|
|
1335
|
+
updated = cache.update_identity_daemon_creds(storage_key, client_id, client_secret)
|
|
1287
1336
|
if updated is not None:
|
|
1288
1337
|
new_identity = updated
|
|
1289
1338
|
|
|
@@ -1291,7 +1340,7 @@ def _switch_by_username(
|
|
|
1291
1340
|
"status": "switched",
|
|
1292
1341
|
"message": f"Switched to {username}",
|
|
1293
1342
|
"previous_identity": previous_username,
|
|
1294
|
-
"active_identity":
|
|
1343
|
+
"active_identity": storage_key,
|
|
1295
1344
|
"organization": new_identity.organization_name,
|
|
1296
1345
|
"organization_id": new_identity.organization_id,
|
|
1297
1346
|
}
|
|
@@ -1343,19 +1392,30 @@ def _switch_by_organization(
|
|
|
1343
1392
|
|
|
1344
1393
|
# Step 3: Check whether this org lives in a tenant of its own
|
|
1345
1394
|
if not org_needs_tenant_login(org_info):
|
|
1346
|
-
#
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1395
|
+
# No device login is needed — but "no login" is not "no action".
|
|
1396
|
+
if org_info.external_entra_id:
|
|
1397
|
+
# Flagged external yet resolving to no foreign tenant: a
|
|
1398
|
+
# misregistered org record must never capture the caller's
|
|
1399
|
+
# identity (see _handled_home_tenant_external_org).
|
|
1400
|
+
if _handled_home_tenant_external_org(ctx, org_info):
|
|
1401
|
+
return
|
|
1402
|
+
ctx.output_error(
|
|
1403
|
+
AuthenticationError(
|
|
1404
|
+
message=(
|
|
1405
|
+
f"Organization '{org_name}' requires external authentication "
|
|
1406
|
+
"but has no tenant ID configured"
|
|
1407
|
+
),
|
|
1408
|
+
code="DXS-AUTH-012",
|
|
1409
|
+
suggestions=[
|
|
1410
|
+
"Contact your organization administrator to configure the tenant ID"
|
|
1411
|
+
],
|
|
1412
|
+
)
|
|
1357
1413
|
)
|
|
1358
|
-
|
|
1414
|
+
raise SystemExit(1)
|
|
1415
|
+
# Home-tenant org (Datex, or an org with no tenant of its own): the
|
|
1416
|
+
# current identity's tokens already cover it, but the org binding on
|
|
1417
|
+
# the cached identity must still be re-pointed at the requested org.
|
|
1418
|
+
_rebind_home_tenant_identity(ctx, cache, org_info)
|
|
1359
1419
|
return
|
|
1360
1420
|
|
|
1361
1421
|
# Step 4: Reject a misregistered home-tenant org BEFORE any identity lookup —
|
|
@@ -1368,9 +1428,14 @@ def _switch_by_organization(
|
|
|
1368
1428
|
if org_info.tenant_id:
|
|
1369
1429
|
existing_by_tenant = cache.get_identity_by_tenant(org_info.tenant_id)
|
|
1370
1430
|
if existing_by_tenant and existing_by_tenant.account_username:
|
|
1371
|
-
# Update org info and activate
|
|
1431
|
+
# Update org info and activate. Resolve the real storage key: a
|
|
1432
|
+
# self-service identity is stored under username#tenant_id (legacy
|
|
1433
|
+
# files: the bare username), never looked up by bare username alone.
|
|
1434
|
+
tenant_storage_key = (
|
|
1435
|
+
cache.resolve_stored_key(existing_by_tenant) or existing_by_tenant.account_username
|
|
1436
|
+
)
|
|
1372
1437
|
cache.update_identity_org_info(
|
|
1373
|
-
|
|
1438
|
+
tenant_storage_key,
|
|
1374
1439
|
org_info.id,
|
|
1375
1440
|
org_info.name,
|
|
1376
1441
|
)
|
|
@@ -1410,10 +1475,11 @@ def _activate_existing_identity(
|
|
|
1410
1475
|
) -> None:
|
|
1411
1476
|
"""Activate an existing cached identity."""
|
|
1412
1477
|
# The identity's computed identity_key can diverge from its actual storage
|
|
1413
|
-
# key: a customer external identity is stored under
|
|
1414
|
-
#
|
|
1415
|
-
#
|
|
1416
|
-
#
|
|
1478
|
+
# key: a customer external identity is stored under username#tenant_id
|
|
1479
|
+
# (legacy files: the bare username) because the org name is unknown at save
|
|
1480
|
+
# time; discovery then sets the org name, which makes identity_key recompute
|
|
1481
|
+
# to username#org while the stored key never changes. Resolve the real
|
|
1482
|
+
# storage key for all cache operations.
|
|
1417
1483
|
storage_key = cache.resolve_stored_key(identity) or identity.identity_key
|
|
1418
1484
|
|
|
1419
1485
|
# Check if token is expired
|
|
@@ -1481,6 +1547,96 @@ def _fetch_organization_by_name(ctx: DxsContext, org_name: str) -> Any:
|
|
|
1481
1547
|
return None
|
|
1482
1548
|
|
|
1483
1549
|
|
|
1550
|
+
def _rebind_home_tenant_identity(
|
|
1551
|
+
ctx: DxsContext,
|
|
1552
|
+
cache: MultiIdentityTokenCache,
|
|
1553
|
+
org_info: Any,
|
|
1554
|
+
) -> None:
|
|
1555
|
+
"""Re-point the active home-tenant identity at ``org_info`` (issue #112).
|
|
1556
|
+
|
|
1557
|
+
A home-tenant org needs no device login, but "no login required" never
|
|
1558
|
+
meant "no action required": the organization binding lives on the cached
|
|
1559
|
+
identity, and the API resolves the caller's user record through it. Reads
|
|
1560
|
+
against Datex-owned resources work under the old binding — which is what
|
|
1561
|
+
made the silent no-op hard to spot — while writes fail with a misleading
|
|
1562
|
+
``DXS-API-400``. Only the two org fields move; tokens and the active
|
|
1563
|
+
identity selection stay untouched (the issue #83 contract).
|
|
1564
|
+
"""
|
|
1565
|
+
active = cache.get_active_identity()
|
|
1566
|
+
if active is None or identity_needs_tenant_authority(active):
|
|
1567
|
+
# Nothing safe to re-bind: either no identity is cached, or the active
|
|
1568
|
+
# identity belongs to a foreign tenant and its tokens cannot serve a
|
|
1569
|
+
# home-tenant org. Fail closed rather than claim success.
|
|
1570
|
+
described = (
|
|
1571
|
+
f"the active identity '{active.account_username}' belongs to another tenant"
|
|
1572
|
+
if active
|
|
1573
|
+
else "no identity is cached"
|
|
1574
|
+
)
|
|
1575
|
+
ctx.output_error(
|
|
1576
|
+
AuthenticationError(
|
|
1577
|
+
message=(
|
|
1578
|
+
f"Organization '{org_info.name}' authenticates against the Datex "
|
|
1579
|
+
f"home tenant, but {described}"
|
|
1580
|
+
),
|
|
1581
|
+
code="DXS-AUTH-018",
|
|
1582
|
+
suggestions=[
|
|
1583
|
+
"Run 'dxs auth switch <username>' to activate a home-tenant identity first",
|
|
1584
|
+
f"Or run 'dxs auth login {org_info.name}' to sign in with one",
|
|
1585
|
+
],
|
|
1586
|
+
)
|
|
1587
|
+
)
|
|
1588
|
+
raise SystemExit(1)
|
|
1589
|
+
|
|
1590
|
+
already_bound = active.organization_id == org_info.id and (
|
|
1591
|
+
(active.organization_name or "").lower() == org_info.name.lower()
|
|
1592
|
+
)
|
|
1593
|
+
if already_bound:
|
|
1594
|
+
ctx.output(
|
|
1595
|
+
single(
|
|
1596
|
+
item={
|
|
1597
|
+
"status": "no_action_required",
|
|
1598
|
+
"message": (
|
|
1599
|
+
f"Active identity is already bound to organization '{org_info.name}'"
|
|
1600
|
+
),
|
|
1601
|
+
"active_identity": active.account_username,
|
|
1602
|
+
"organization": org_info.name,
|
|
1603
|
+
"organization_id": org_info.id,
|
|
1604
|
+
},
|
|
1605
|
+
semantic_key="authentication",
|
|
1606
|
+
)
|
|
1607
|
+
)
|
|
1608
|
+
return
|
|
1609
|
+
|
|
1610
|
+
storage_key = cache.resolve_stored_key(active) or active.identity_key
|
|
1611
|
+
updated = cache.update_identity_org_info(storage_key, org_info.id, org_info.name)
|
|
1612
|
+
if updated is None:
|
|
1613
|
+
ctx.output_error(
|
|
1614
|
+
AuthenticationError(
|
|
1615
|
+
message=f"Identity '{storage_key}' not found",
|
|
1616
|
+
code="DXS-AUTH-010",
|
|
1617
|
+
)
|
|
1618
|
+
)
|
|
1619
|
+
raise SystemExit(1)
|
|
1620
|
+
|
|
1621
|
+
ctx.output(
|
|
1622
|
+
single(
|
|
1623
|
+
item={
|
|
1624
|
+
"status": "organization_rebound",
|
|
1625
|
+
"message": (
|
|
1626
|
+
f"Re-bound the active identity to organization '{org_info.name}' "
|
|
1627
|
+
"(home tenant; no login required)"
|
|
1628
|
+
),
|
|
1629
|
+
"active_identity": active.account_username,
|
|
1630
|
+
"organization": org_info.name,
|
|
1631
|
+
"organization_id": org_info.id,
|
|
1632
|
+
"previous_organization": active.organization_name,
|
|
1633
|
+
"previous_organization_id": active.organization_id,
|
|
1634
|
+
},
|
|
1635
|
+
semantic_key="authentication",
|
|
1636
|
+
)
|
|
1637
|
+
)
|
|
1638
|
+
|
|
1639
|
+
|
|
1484
1640
|
def _handled_home_tenant_external_org(ctx: DxsContext, org_info: Any) -> bool:
|
|
1485
1641
|
"""Report and stop when an org flagged external points at the home tenant.
|
|
1486
1642
|
|
|
@@ -1642,9 +1798,10 @@ def _login_to_organization(
|
|
|
1642
1798
|
# First, we need to be able to call the API - check if we have any valid identity
|
|
1643
1799
|
active = cache.get_active_identity()
|
|
1644
1800
|
if not active or active.is_expired():
|
|
1645
|
-
# Try to refresh
|
|
1801
|
+
# Try to refresh (no username -> the active identity, whatever its
|
|
1802
|
+
# storage key; the bare username is not the key for external identities)
|
|
1646
1803
|
if active:
|
|
1647
|
-
cache.try_refresh_identity(
|
|
1804
|
+
cache.try_refresh_identity()
|
|
1648
1805
|
active = cache.get_active_identity()
|
|
1649
1806
|
|
|
1650
1807
|
if not active or active.is_expired():
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Branch commands: dxs branch [list|show|create|roles|shell|validate|candelete|search]."""
|
|
2
2
|
|
|
3
|
+
from collections.abc import Callable
|
|
3
4
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
4
5
|
from datetime import datetime
|
|
5
6
|
from typing import Any
|
|
@@ -7,7 +8,14 @@ from typing import Any
|
|
|
7
8
|
import click
|
|
8
9
|
|
|
9
10
|
from dxs.cli import DxsContext, pass_context
|
|
10
|
-
from dxs.commands.
|
|
11
|
+
from dxs.commands.branch_commit_guard import (
|
|
12
|
+
all_changed_refs,
|
|
13
|
+
fetch_commit_preflight,
|
|
14
|
+
guarded_commit,
|
|
15
|
+
modification_bucket,
|
|
16
|
+
validate_branch_or_raise,
|
|
17
|
+
verify_config_refs,
|
|
18
|
+
)
|
|
11
19
|
from dxs.core.api import (
|
|
12
20
|
ApiClient,
|
|
13
21
|
ApplicationGroupEndpoints,
|
|
@@ -817,10 +825,13 @@ def candelete(ctx: DxsContext, branch_id: int) -> None:
|
|
|
817
825
|
@click.argument("branch_id", type=int)
|
|
818
826
|
@click.option(
|
|
819
827
|
"--force",
|
|
828
|
+
"--yes",
|
|
820
829
|
"-f",
|
|
830
|
+
"-y",
|
|
831
|
+
"force",
|
|
821
832
|
is_flag=True,
|
|
822
833
|
default=False,
|
|
823
|
-
help="Skip confirmation and delete immediately",
|
|
834
|
+
help="Skip confirmation and delete immediately (--yes/-y are aliases)",
|
|
824
835
|
)
|
|
825
836
|
@pass_context
|
|
826
837
|
@require_auth
|
|
@@ -837,7 +848,7 @@ def delete(ctx: DxsContext, branch_id: int, force: bool) -> None:
|
|
|
837
848
|
|
|
838
849
|
\b
|
|
839
850
|
Options:
|
|
840
|
-
--force, -f Skip confirmation prompt
|
|
851
|
+
--force, -f Skip confirmation prompt (--yes/-y are aliases)
|
|
841
852
|
|
|
842
853
|
\b
|
|
843
854
|
Examples:
|
|
@@ -1362,22 +1373,7 @@ def publish(
|
|
|
1362
1373
|
if not force:
|
|
1363
1374
|
# Validate the branch first
|
|
1364
1375
|
ctx.log(f"Validating branch {branch_id}...")
|
|
1365
|
-
|
|
1366
|
-
if not isinstance(errors, list):
|
|
1367
|
-
errors = [errors] if errors else []
|
|
1368
|
-
|
|
1369
|
-
if errors:
|
|
1370
|
-
error_messages = server_error_messages(errors)
|
|
1371
|
-
suggestions = error_messages[:5]
|
|
1372
|
-
if len(error_messages) > 5:
|
|
1373
|
-
suggestions.append(
|
|
1374
|
-
f"... and {len(error_messages) - 5} more; run 'dxs branch validate "
|
|
1375
|
-
f"{branch_id}' for the full list"
|
|
1376
|
-
)
|
|
1377
|
-
raise ValidationError(
|
|
1378
|
-
message=f"Branch {branch_id} has validation errors",
|
|
1379
|
-
suggestions=suggestions,
|
|
1380
|
-
)
|
|
1376
|
+
validate_branch_or_raise(client, branch_id)
|
|
1381
1377
|
|
|
1382
1378
|
# Confirm publish
|
|
1383
1379
|
if not click.confirm(
|
|
@@ -1426,6 +1422,55 @@ def publish(
|
|
|
1426
1422
|
)
|
|
1427
1423
|
|
|
1428
1424
|
|
|
1425
|
+
def _commit_confirmation_summary(
|
|
1426
|
+
branch_name: str,
|
|
1427
|
+
branch_id: int,
|
|
1428
|
+
selected: list[str] | None,
|
|
1429
|
+
preflight: dict[str, Any],
|
|
1430
|
+
) -> str:
|
|
1431
|
+
"""Human-readable summary of what a commit is about to merge, for click.confirm."""
|
|
1432
|
+
|
|
1433
|
+
def _breakdown(configs: list[dict[str, Any]]) -> str:
|
|
1434
|
+
tallies = {"created": 0, "updated": 0, "deleted": 0}
|
|
1435
|
+
for cfg in configs:
|
|
1436
|
+
bucket = modification_bucket(str(cfg.get("modificationTypeId", "")))
|
|
1437
|
+
if bucket:
|
|
1438
|
+
tallies[bucket] += 1
|
|
1439
|
+
parts = [f"{count} {bucket}" for bucket, count in tallies.items() if count]
|
|
1440
|
+
return ", ".join(parts) if parts else "no modification details"
|
|
1441
|
+
|
|
1442
|
+
total = preflight["counts"]["total"]
|
|
1443
|
+
lines: list[str] = []
|
|
1444
|
+
if selected is None:
|
|
1445
|
+
lines.append(
|
|
1446
|
+
f"Commit branch '{branch_name}' (ID: {branch_id}) to Main: "
|
|
1447
|
+
f"all {total} changed config(s) ({_breakdown(preflight['configs'])})."
|
|
1448
|
+
)
|
|
1449
|
+
else:
|
|
1450
|
+
selected_set = set(selected)
|
|
1451
|
+
selected_configs = [
|
|
1452
|
+
c for c in preflight["configs"] if c.get("referenceName") in selected_set
|
|
1453
|
+
]
|
|
1454
|
+
lines.append(
|
|
1455
|
+
f"Commit branch '{branch_name}' (ID: {branch_id}) to Main: "
|
|
1456
|
+
f"{len(selected)} of {total} changed config(s) ({_breakdown(selected_configs)})."
|
|
1457
|
+
)
|
|
1458
|
+
leftovers = [r for r in preflight["changed_refs"] if r not in selected_set]
|
|
1459
|
+
if leftovers:
|
|
1460
|
+
lines.append(
|
|
1461
|
+
f"{len(leftovers)} changed config(s) not selected will transfer to a new "
|
|
1462
|
+
f"feature branch: {', '.join(leftovers)}."
|
|
1463
|
+
)
|
|
1464
|
+
if preflight["non_config_changes"]:
|
|
1465
|
+
lines.append(
|
|
1466
|
+
"Non-config changes also on this branch: "
|
|
1467
|
+
+ ", ".join(preflight["non_config_changes"])
|
|
1468
|
+
+ "."
|
|
1469
|
+
)
|
|
1470
|
+
lines.append("Commit to Main?")
|
|
1471
|
+
return "\n".join(lines)
|
|
1472
|
+
|
|
1473
|
+
|
|
1429
1474
|
@branch.command("commit")
|
|
1430
1475
|
@click.argument("branch_id", type=int)
|
|
1431
1476
|
@click.option("--title", "-t", required=True, help="Commit title")
|
|
@@ -1435,13 +1480,27 @@ def publish(
|
|
|
1435
1480
|
"config_refs",
|
|
1436
1481
|
multiple=True,
|
|
1437
1482
|
help="Reference name of a changed config to commit (repeatable). "
|
|
1438
|
-
"
|
|
1483
|
+
"Use --all-changes instead to commit every modified config.",
|
|
1439
1484
|
)
|
|
1440
1485
|
@click.option(
|
|
1441
1486
|
"--all-changes",
|
|
1442
1487
|
is_flag=True,
|
|
1443
1488
|
default=False,
|
|
1444
|
-
help="Commit all modified configs on the branch
|
|
1489
|
+
help="Commit all modified configs on the branch (their reference names are "
|
|
1490
|
+
"resolved from the branch's change list and sent explicitly).",
|
|
1491
|
+
)
|
|
1492
|
+
@click.option(
|
|
1493
|
+
"--force",
|
|
1494
|
+
"-f",
|
|
1495
|
+
is_flag=True,
|
|
1496
|
+
default=False,
|
|
1497
|
+
help="Skip validation and confirmation",
|
|
1498
|
+
)
|
|
1499
|
+
@click.option(
|
|
1500
|
+
"--dry-run",
|
|
1501
|
+
is_flag=True,
|
|
1502
|
+
default=False,
|
|
1503
|
+
help="Show what would be committed without mutating anything",
|
|
1445
1504
|
)
|
|
1446
1505
|
@pass_context
|
|
1447
1506
|
@require_auth
|
|
@@ -1453,12 +1512,27 @@ def commit(
|
|
|
1453
1512
|
description: str,
|
|
1454
1513
|
config_refs: tuple[str, ...],
|
|
1455
1514
|
all_changes: bool,
|
|
1515
|
+
force: bool,
|
|
1516
|
+
dry_run: bool,
|
|
1456
1517
|
) -> None:
|
|
1457
1518
|
"""Commit (merge) a feature branch into its Main branch.
|
|
1458
1519
|
|
|
1520
|
+
Committing is irreversible, so unless --force is given the command verifies
|
|
1521
|
+
the branch's organization, checks each --config ref against the branch's
|
|
1522
|
+
actual changes, validates the branch server-side, and asks for confirmation
|
|
1523
|
+
with a summary of what will be merged. A partial commit transfers the
|
|
1524
|
+
uncommitted leftovers to a new feature branch (reported as new_branch_id).
|
|
1525
|
+
|
|
1526
|
+
--all-changes resolves the branch's changed configs and commits that
|
|
1527
|
+
explicit list (the API requires one). It always fetches the branch's
|
|
1528
|
+
change list — --force skips only validation and confirmation — and fails
|
|
1529
|
+
when the branch has nothing changed.
|
|
1530
|
+
|
|
1459
1531
|
\b
|
|
1460
1532
|
Examples:
|
|
1461
1533
|
dxs source branch commit 3101 --title "bump Module1" -d "Re-pin Module1" --config appConfig
|
|
1534
|
+
dxs source branch commit 3101 -t "release" -d "everything" --all-changes
|
|
1535
|
+
dxs source branch commit 3101 -t "bump" -d "re-pin" --config appConfig --dry-run
|
|
1462
1536
|
"""
|
|
1463
1537
|
if not config_refs and not all_changes:
|
|
1464
1538
|
raise ValidationError(
|
|
@@ -1470,21 +1544,114 @@ def commit(
|
|
|
1470
1544
|
)
|
|
1471
1545
|
|
|
1472
1546
|
client = ApiClient()
|
|
1473
|
-
request_data: dict[str, Any] = {"title": title, "description": description}
|
|
1474
|
-
if not all_changes:
|
|
1475
|
-
request_data["configs"] = list(config_refs)
|
|
1476
1547
|
|
|
1477
|
-
|
|
1478
|
-
|
|
1548
|
+
if dry_run:
|
|
1549
|
+
ctx.log(f"Fetching changes for branch {branch_id}...")
|
|
1550
|
+
preflight = fetch_commit_preflight(client, branch_id)
|
|
1551
|
+
if all_changes:
|
|
1552
|
+
refs = all_changed_refs(preflight, branch_id)
|
|
1553
|
+
else:
|
|
1554
|
+
refs = list(config_refs)
|
|
1555
|
+
verify_config_refs(refs, preflight["changed_refs"], branch_id)
|
|
1556
|
+
item: dict[str, Any] = {
|
|
1557
|
+
"dry_run": True,
|
|
1558
|
+
"branch_id": branch_id,
|
|
1559
|
+
"title": title,
|
|
1560
|
+
"all_changes": all_changes,
|
|
1561
|
+
"configs_to_commit": refs,
|
|
1562
|
+
"changes": preflight["counts"],
|
|
1563
|
+
"non_config_changes": preflight["non_config_changes"],
|
|
1564
|
+
}
|
|
1565
|
+
if not all_changes:
|
|
1566
|
+
leftovers = [r for r in preflight["changed_refs"] if r not in set(refs)]
|
|
1567
|
+
if leftovers:
|
|
1568
|
+
item["configs_not_selected"] = leftovers
|
|
1569
|
+
item["note"] = (
|
|
1570
|
+
"The configs not selected would transfer to a new feature branch "
|
|
1571
|
+
"(newBranchId in the commit result)."
|
|
1572
|
+
)
|
|
1573
|
+
ctx.output(single(item=item, semantic_key="commit_result"))
|
|
1574
|
+
return
|
|
1479
1575
|
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1576
|
+
# Check organization mismatch - committing to Main requires ownership
|
|
1577
|
+
ctx.log(f"Fetching branch {branch_id}...")
|
|
1578
|
+
branch_info = client.get(BranchEndpoints.get(branch_id))
|
|
1579
|
+
branch_name = branch_info.get("name", f"Branch {branch_id}")
|
|
1580
|
+
repo_id = branch_info.get("applicationDefinitionId")
|
|
1581
|
+
if repo_id:
|
|
1582
|
+
ctx.log("Verifying organization access...")
|
|
1583
|
+
repo_info = client.get(RepoEndpoints.get(repo_id))
|
|
1584
|
+
org_id = repo_info.get("organizationId")
|
|
1585
|
+
org_name = (
|
|
1586
|
+
repo_info.get("organization", {}).get("name") if repo_info.get("organization") else None
|
|
1489
1587
|
)
|
|
1588
|
+
if org_id:
|
|
1589
|
+
verify_org_match(org_id, org_name)
|
|
1590
|
+
|
|
1591
|
+
selected: list[str]
|
|
1592
|
+
confirm_cb: Callable[[], bool] | None = None
|
|
1593
|
+
if all_changes or not force:
|
|
1594
|
+
# Preflight: the commit endpoint rejects a body without configs, so
|
|
1595
|
+
# --all-changes needs the branch's change list to build the explicit
|
|
1596
|
+
# config list even under --force. The non-force path needs it anyway,
|
|
1597
|
+
# to check --config refs against the branch's actual changes and build
|
|
1598
|
+
# the confirmation summary from what the server says is modified.
|
|
1599
|
+
ctx.log(f"Fetching changes for branch {branch_id}...")
|
|
1600
|
+
preflight = fetch_commit_preflight(client, branch_id)
|
|
1601
|
+
if all_changes:
|
|
1602
|
+
selected = all_changed_refs(preflight, branch_id)
|
|
1603
|
+
else:
|
|
1604
|
+
selected = list(config_refs)
|
|
1605
|
+
verify_config_refs(selected, preflight["changed_refs"], branch_id)
|
|
1606
|
+
|
|
1607
|
+
if not force:
|
|
1608
|
+
prompt = _commit_confirmation_summary(
|
|
1609
|
+
branch_name, branch_id, None if all_changes else selected, preflight
|
|
1610
|
+
)
|
|
1611
|
+
|
|
1612
|
+
def _confirm() -> bool:
|
|
1613
|
+
return click.confirm(prompt)
|
|
1614
|
+
|
|
1615
|
+
confirm_cb = _confirm
|
|
1616
|
+
ctx.log(f"Validating branch {branch_id}...")
|
|
1617
|
+
else:
|
|
1618
|
+
selected = list(config_refs)
|
|
1619
|
+
|
|
1620
|
+
committed, result = guarded_commit(
|
|
1621
|
+
client,
|
|
1622
|
+
branch_id,
|
|
1623
|
+
title=title,
|
|
1624
|
+
description=description,
|
|
1625
|
+
configs=selected,
|
|
1626
|
+
validate=not force,
|
|
1627
|
+
confirm=confirm_cb,
|
|
1490
1628
|
)
|
|
1629
|
+
|
|
1630
|
+
if not committed:
|
|
1631
|
+
ctx.output(
|
|
1632
|
+
single(
|
|
1633
|
+
item={"committed": False, "reason": "User cancelled"},
|
|
1634
|
+
semantic_key="commit_result",
|
|
1635
|
+
branch_id=branch_id,
|
|
1636
|
+
)
|
|
1637
|
+
)
|
|
1638
|
+
return
|
|
1639
|
+
|
|
1640
|
+
result = result or {}
|
|
1641
|
+
new_branch_id = result.get("newBranchId")
|
|
1642
|
+
item = {
|
|
1643
|
+
"committed": True,
|
|
1644
|
+
"branch_id": branch_id,
|
|
1645
|
+
"title": title,
|
|
1646
|
+
"new_branch_id": new_branch_id,
|
|
1647
|
+
"result": result,
|
|
1648
|
+
}
|
|
1649
|
+
if new_branch_id and result.get("transferredUncommittedChanges"):
|
|
1650
|
+
note = (
|
|
1651
|
+
f"Uncommitted changes were transferred to new feature branch {new_branch_id}; "
|
|
1652
|
+
f"the configs not committed here continue there."
|
|
1653
|
+
)
|
|
1654
|
+
item["note"] = note
|
|
1655
|
+
ctx.log(note)
|
|
1656
|
+
|
|
1657
|
+
ctx.output(single(item=item, semantic_key="commit_result"))
|