datex-studio-cli 0.5.4__tar.gz → 0.5.6__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.5.4 → datex_studio_cli-0.5.6}/.github/workflows/promote-prod.yml +16 -2
- datex_studio_cli-0.5.6/.github/workflows/release.yml +178 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/PKG-INFO +1 -1
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/pyproject.toml +2 -1
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/__init__.py +1 -1
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/cli.py +98 -25
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/branch.py +509 -5
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/document.py +19 -8
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/init.py +36 -18
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/odata.py +30 -17
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/release_notes.py +53 -31
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/schema.py +679 -9
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/source.py +200 -254
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/api/__init__.py +8 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/api/app_config.py +89 -1
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/api/endpoints.py +82 -1
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/api/metadata_models.py +35 -1
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/api/models.py +17 -5
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/auth/datex_tenant.py +39 -2
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/datasource/flow_generator.py +2 -2
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/datasource/flow_shape.py +24 -16
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/datasource/validator.py +5 -4
- datex_studio_cli-0.5.6/src/dxs/core/footprint/capabilities.py +156 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/footprint/edmx_parser.py +104 -1
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/onboarding.py +11 -2
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/release_notes.py +72 -57
- datex_studio_cli-0.5.6/src/dxs/core/workitem_resolve.py +474 -0
- datex_studio_cli-0.5.6/src/dxs/core/workitems.py +332 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/errors.py +4 -0
- datex_studio_cli-0.5.6/src/dxs/utils/http_errors.py +109 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/app.py +1 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/404/index.html +1 -1
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/404.html +1 -1
- datex_studio_cli-0.5.6/src/dxs/web/static/_next/static/chunks/247-8b4d885f516d109f.js +1 -0
- datex_studio_cli-0.5.6/src/dxs/web/static/_next/static/chunks/app/design/page-3e8e7d9d780d48c9.js +1 -0
- datex_studio_cli-0.5.6/src/dxs/web/static/_next/static/chunks/app/page-6fafc1af296a566d.js +1 -0
- datex_studio_cli-0.5.6/src/dxs/web/static/_next/static/css/e7143a5b42f30598.css +3 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/design/capture/index.html +1 -1
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/design/capture/index.txt +2 -2
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/design/index.html +1 -1
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/design/index.txt +3 -3
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/index.html +1 -1
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/index.txt +3 -3
- datex_studio_cli-0.5.4/.github/workflows/release.yml +0 -108
- datex_studio_cli-0.5.4/src/dxs/web/static/_next/static/chunks/871-1acacdb7b4022abf.js +0 -1
- datex_studio_cli-0.5.4/src/dxs/web/static/_next/static/chunks/app/design/page-689cd5b9e1d9a74e.js +0 -1
- datex_studio_cli-0.5.4/src/dxs/web/static/_next/static/chunks/app/page-f0380b0f29983264.js +0 -1
- datex_studio_cli-0.5.4/src/dxs/web/static/_next/static/css/360c657de5e04df8.css +0 -3
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/.dockerignore +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/.github/actionlint.yaml +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/.github/workflows/build-image.yml +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/.github/workflows/ci.yml +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/.gitignore +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/CONTEXT.md +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/README.md +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/otel/.env.example +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/otel/Dockerfile +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/otel/README.md +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/otel/apply-local-cache.js +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/otel/collector-config.yaml +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/otel/docker-compose.yml +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/otel/local-test-settings.json +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/otel/remote-settings.json +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/report-creator.skill +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/scripts/analyze_corpus.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/scripts/check_wheel_completeness.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/scripts/datadog_errors.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/scripts/generate_rdlx_schema.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/scripts/wavelength_query.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/__main__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/api.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/auth.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/branch_commit_guard.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/cascade.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/config.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/configuration.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/crm.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/crm_delivery.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/crm_time.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/datasource.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/devops.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/endpoint.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/env.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/explore.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/function.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/marketplace.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/ng/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/ng/_common.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/ng/api.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/ng/create.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/ng/data.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/ng/pack.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/ng/preview.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/organization.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/proxy.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/reference.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/repo.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/add_cmds.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/api.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/batch.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/create.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/data.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/dataset.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/edit.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/folder.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/preview.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/report/schema_cmds.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/reset.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/servicepack.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/skills.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/studio.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/telemetry.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/user.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/commands/validation_report.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/context.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/agent_browser.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/api/client.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/api/reference_graph.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/api/replacements.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/auth/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/auth/browser_pages.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/auth/decorators.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/auth/msal_client.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/auth/pages/error.html +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/auth/pages/success.html +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/auth/tenants.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/auth/token_cache.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/cache.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/datasource/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/datasource/flow_models.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/datasource/generator.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/datasource/models.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/datasource/parsers.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/datasource/resolver.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/datasource/type_def_parser.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/designer/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/designer/flow_models.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/designer/models.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/designer/parsers.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/devops/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/devops/client.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/devops/helpers.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/devops/models.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/dynamics/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/dynamics/client.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/dynamics/html.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/dynamics/metadata_cache.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/dynamics/pagination.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/dynamics/resolvers.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/dynamics/time_entry.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/footprint/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/footprint/client.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/footprint/metadata.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/function/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/function/generator.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/function/models.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/function/validator.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/graph.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/output/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/output/csv_fmt.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/output/formatter.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/output/json_fmt.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/output/redaction.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/output/yaml_fmt.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/quartermaster/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/quartermaster/client.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/quartermaster/tenants.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/responses.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/skills/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/skills/frontmatter.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/skills/installer.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/skills/layout.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/skills/lock.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/skills/resolver.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/skills/staleness.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/telemetry/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/telemetry/config.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/telemetry/identity.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/telemetry/recorder.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/telemetry/scrubber.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/core/telemetry/sender.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/models/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/ng/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/ng/folder.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/ng/manifest.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/ng/mocks.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/ng/preview.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/ng/scaffold.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/ng/serve_lock.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/py.typed +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/datasource_binding.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/engine.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/field_parser.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/folder.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/manifest.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/models.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/preview.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/preview_arjs.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/schema.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/svg_renderer.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/templates/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/templates/bill_of_lading.rdlx-json +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/templates/shipping_label.rdlx-json +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/validate_arjs.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/validator.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/report/wrapper.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/click_options.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/clipboard.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/config.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/filtering.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/image.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/paths.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/resolvers.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/responses.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/restricted.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/sorting.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/update_check.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/utils/versioning.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/__init__.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/design.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/proxy_app.py +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/0b24cca5-c1e1c8810348f107.js +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/255-102f2e5b2e3dc2ef.js +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/538-b06224e65f71c9a1.js +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/697-0e000ab410d9f470.js +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/8b8f67fc-6295949a4b5485a4.js +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/909-c88abba3cf0caec3.js +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/app/_not-found/page-e3c660196c244183.js +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/app/design/capture/page-c083dc9f01f32a04.js +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/app/layout-cec86f90e385371d.js +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/framework-de98b93a850cfc71.js +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/main-0f18f91200dac003.js +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/main-app-694cf3568b6c9fa9.js +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/pages/_app-7d307437aca18ad4.js +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/_next/static/chunks/webpack-2b297ada5306c17f.js +0 -0
- {datex_studio_cli-0.5.4/src/dxs/web/static/_next/static/CAvN0dC1A3kKCmq1ZHlgY → datex_studio_cli-0.5.6/src/dxs/web/static/_next/static/lyKgXuwqDbkDi4sPVtE8F}/_buildManifest.js +0 -0
- {datex_studio_cli-0.5.4/src/dxs/web/static/_next/static/CAvN0dC1A3kKCmq1ZHlgY → datex_studio_cli-0.5.6/src/dxs/web/static/_next/static/lyKgXuwqDbkDi4sPVtE8F}/_ssgManifest.js +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/render-cli.js +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/report-preview.html +0 -0
- {datex_studio_cli-0.5.4 → datex_studio_cli-0.5.6}/src/dxs/web/static/report-validate.html +0 -0
|
@@ -82,6 +82,7 @@ jobs:
|
|
|
82
82
|
fi
|
|
83
83
|
- name: Commit the prod digest bump
|
|
84
84
|
run: |
|
|
85
|
+
set -euo pipefail
|
|
85
86
|
digest='${{ steps.resolve.outputs.digest }}'
|
|
86
87
|
sed -i -E "s|^ digest:.*| digest: \"${digest}\"|" quartermaster/deploy/prod/values.yaml
|
|
87
88
|
if git diff --quiet; then
|
|
@@ -94,11 +95,24 @@ jobs:
|
|
|
94
95
|
git commit \
|
|
95
96
|
-m "chore(deploy): prod → ${digest}" \
|
|
96
97
|
-m "Promoted by @${{ github.actor }} via promote-prod (ref input: ${{ inputs.ref }})"
|
|
98
|
+
# Same collision release.yml handles: main moves under us while the
|
|
99
|
+
# job runs. Only one ref goes up here, so there is nothing for
|
|
100
|
+
# --atomic to make atomic — the asymmetry with release.yml is the
|
|
101
|
+
# point, not an oversight. What does carry over is checking the
|
|
102
|
+
# rebase. Unchecked, a conflicted one left HEAD detached at main's
|
|
103
|
+
# tip — someone else's promotion — where the next iteration's push is
|
|
104
|
+
# "Everything up-to-date" and the loop exits 0. A green run that
|
|
105
|
+
# promoted nothing, on the workflow that moves prod.
|
|
97
106
|
for attempt in 1 2 3; do
|
|
98
107
|
if git push origin HEAD:main; then
|
|
99
108
|
exit 0
|
|
100
109
|
fi
|
|
101
|
-
|
|
110
|
+
echo "push attempt ${attempt} lost a race with another push to main; rebasing" >&2
|
|
111
|
+
if ! git pull --rebase origin main; then
|
|
112
|
+
git rebase --abort || true
|
|
113
|
+
echo "could not rebase the prod bump onto main — nothing was pushed; re-dispatch once main is settled" >&2
|
|
114
|
+
exit 1
|
|
115
|
+
fi
|
|
102
116
|
done
|
|
103
|
-
echo "prod bump push failed after 3 attempts" >&2
|
|
117
|
+
echo "prod bump push failed after 3 attempts — nothing landed, re-dispatch" >&2
|
|
104
118
|
exit 1
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# The CLI's PyPI release lane (spec §5), retiring publish.sh and ~/.pypirc.
|
|
2
|
+
# Two ways in, one publish path:
|
|
3
|
+
# - tag push vX.Y.Z — versions must already be aligned across pyproject,
|
|
4
|
+
# __init__, and uv.lock (the uv.lock entry is the one nothing else updates)
|
|
5
|
+
# - manual dispatch with an optional `version` input — the workflow bumps
|
|
6
|
+
# pyproject/__init__/uv.lock, commits to main, tags vX.Y.Z, then publishes.
|
|
7
|
+
# A blank input publishes the version already in the files (the retry lane
|
|
8
|
+
# for a release that failed after its bump commit landed).
|
|
9
|
+
# The bump's branch+tag push is atomic and rebase-retried: main pushes race
|
|
10
|
+
# with it for the whole CI wait, and a lost race must leave nothing behind
|
|
11
|
+
# rather than a bump commit with no tag.
|
|
12
|
+
# Both lanes: full CI → version-agreement assert → frontend build with a hard
|
|
13
|
+
# existence check (the gitignored web/static/ ships via hatch `artifacts`,
|
|
14
|
+
# which silently includes nothing when absent) → uv build + publish via PyPI
|
|
15
|
+
# trusted publishing.
|
|
16
|
+
# Prerequisite: a trusted publisher for this repo/workflow configured on PyPI.
|
|
17
|
+
name: Release to PyPI
|
|
18
|
+
|
|
19
|
+
on:
|
|
20
|
+
push:
|
|
21
|
+
tags: ["v*"]
|
|
22
|
+
workflow_dispatch:
|
|
23
|
+
inputs:
|
|
24
|
+
version:
|
|
25
|
+
description: "New version (X.Y.Z) — bumps files, commits to main, tags, publishes. Blank — publish the version already in the files."
|
|
26
|
+
required: false
|
|
27
|
+
type: string
|
|
28
|
+
|
|
29
|
+
permissions:
|
|
30
|
+
contents: read
|
|
31
|
+
|
|
32
|
+
concurrency:
|
|
33
|
+
group: release-pypi
|
|
34
|
+
|
|
35
|
+
jobs:
|
|
36
|
+
ci:
|
|
37
|
+
uses: ./.github/workflows/ci.yml
|
|
38
|
+
|
|
39
|
+
publish:
|
|
40
|
+
needs: ci
|
|
41
|
+
runs-on: arc-dxs
|
|
42
|
+
timeout-minutes: 30
|
|
43
|
+
permissions:
|
|
44
|
+
contents: write # the dispatch lane pushes the bump commit + tag
|
|
45
|
+
id-token: write # PyPI trusted publishing
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@v7
|
|
48
|
+
with:
|
|
49
|
+
# The bump lane rebases onto main when it loses a push race; a
|
|
50
|
+
# depth-1 checkout has no history to rebase against.
|
|
51
|
+
fetch-depth: 0
|
|
52
|
+
- uses: astral-sh/setup-uv@v10.0.1
|
|
53
|
+
with:
|
|
54
|
+
enable-cache: true
|
|
55
|
+
cache-suffix: release
|
|
56
|
+
download-from-astral-mirror: false
|
|
57
|
+
- run: uv python install 3.13
|
|
58
|
+
- name: Bump version, commit, and tag (dispatch with a version input)
|
|
59
|
+
if: github.event_name == 'workflow_dispatch' && inputs.version != ''
|
|
60
|
+
env:
|
|
61
|
+
VERSION: ${{ inputs.version }}
|
|
62
|
+
run: |
|
|
63
|
+
set -euo pipefail
|
|
64
|
+
if [ "${GITHUB_REF}" != "refs/heads/main" ]; then
|
|
65
|
+
echo "version bumps may only be dispatched from main (got ${GITHUB_REF})" >&2
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
68
|
+
if ! echo "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
|
|
69
|
+
echo "version must be X.Y.Z (got '$VERSION')" >&2
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
tag="v$VERSION"
|
|
73
|
+
# The version-reuse guard, checked before anything is written: a taken
|
|
74
|
+
# version fails here rather than surfacing later as a push rejection
|
|
75
|
+
# that reads like a lost race.
|
|
76
|
+
if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
|
|
77
|
+
echo "${tag} already exists on origin — pick a new version, or re-dispatch with a blank version input to publish the version already in the files" >&2
|
|
78
|
+
exit 1
|
|
79
|
+
fi
|
|
80
|
+
# A tag is permanent, and the agreement assert below runs after this
|
|
81
|
+
# step — too late to stop a bad one. A tag pushed onto files that
|
|
82
|
+
# disagree burns the version outright: the assert then fails the
|
|
83
|
+
# release, and the version can never be retried either, because the
|
|
84
|
+
# preflight above refuses a tag that exists. So agreement is checked
|
|
85
|
+
# here, before either lane pushes. (The assert step stays: it is the
|
|
86
|
+
# only check for the tag-push and blank-version lanes, which never
|
|
87
|
+
# reach this step.)
|
|
88
|
+
assert_versions_agree() {
|
|
89
|
+
local pyproject init lock
|
|
90
|
+
pyproject=$(grep -m1 '^version = ' pyproject.toml | cut -d'"' -f2)
|
|
91
|
+
init=$(grep -m1 '__version__' src/dxs/__init__.py | cut -d'"' -f2)
|
|
92
|
+
lock=$(awk '/^name = "datex-studio-cli"$/ {found=1} found && /^version = / {print; exit}' uv.lock | cut -d'"' -f2)
|
|
93
|
+
if [ "$pyproject" != "$VERSION" ] || [ "$init" != "$VERSION" ] || [ "$lock" != "$VERSION" ]; then
|
|
94
|
+
echo "version files disagree with ${VERSION} (pyproject=${pyproject} __init__=${init} uv.lock=${lock}) — align all three, run 'uv lock', commit, then re-dispatch; nothing was pushed" >&2
|
|
95
|
+
exit 1
|
|
96
|
+
fi
|
|
97
|
+
}
|
|
98
|
+
current=$(grep -m1 '^version = ' pyproject.toml | cut -d'"' -f2)
|
|
99
|
+
if [ "$VERSION" = "$current" ]; then
|
|
100
|
+
# The bump commit already landed and only the tag is missing (a
|
|
101
|
+
# half-done state older runs could leave behind). Nothing to push to
|
|
102
|
+
# main, so no race to lose — but a partial bump, where pyproject
|
|
103
|
+
# moved and __init__/uv.lock did not, reaches exactly here.
|
|
104
|
+
assert_versions_agree
|
|
105
|
+
git tag "${tag}"
|
|
106
|
+
git push origin "refs/tags/${tag}"
|
|
107
|
+
exit 0
|
|
108
|
+
fi
|
|
109
|
+
sed -i "s/^version = \".*\"/version = \"$VERSION\"/" pyproject.toml
|
|
110
|
+
sed -i "s/^__version__ = \".*\"/__version__ = \"$VERSION\"/" src/dxs/__init__.py
|
|
111
|
+
uv lock
|
|
112
|
+
git config user.name "github-actions[bot]"
|
|
113
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
114
|
+
git add pyproject.toml src/dxs/__init__.py uv.lock
|
|
115
|
+
git commit -m "Bump version to $VERSION"
|
|
116
|
+
assert_versions_agree
|
|
117
|
+
# Branch and tag go up together. The race window here is the whole CI
|
|
118
|
+
# wait, and any merge during it triggers a staging-digest bump that
|
|
119
|
+
# pushes to main — so losing a push is normal and gets a rebase-retry,
|
|
120
|
+
# exactly as promote-prod.yml does. --atomic is what keeps a lost race
|
|
121
|
+
# from being worse than a retry: without it a branch push that lands
|
|
122
|
+
# ahead of a failing tag push leaves main carrying "Bump version to
|
|
123
|
+
# X.Y.Z" with no tag and nothing published.
|
|
124
|
+
for attempt in 1 2 3; do
|
|
125
|
+
# Re-pointed on every attempt, not once before the loop: a rebase
|
|
126
|
+
# replays the bump onto a new commit, and the tag has to follow it
|
|
127
|
+
# rather than the commit the rebase left behind.
|
|
128
|
+
git tag -f "${tag}"
|
|
129
|
+
if git push --atomic origin HEAD:main "refs/tags/${tag}"; then
|
|
130
|
+
exit 0
|
|
131
|
+
fi
|
|
132
|
+
# The preflight cleared this tag, so a rejection is normally the
|
|
133
|
+
# lost race a rebase fixes. If the tag itself appeared meanwhile,
|
|
134
|
+
# rebasing can never help and the advice below would send the
|
|
135
|
+
# operator to a re-dispatch the preflight then refuses.
|
|
136
|
+
if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
|
|
137
|
+
echo "${tag} appeared on origin while this run was pushing — nothing landed from this run; pick a new version" >&2
|
|
138
|
+
exit 1
|
|
139
|
+
fi
|
|
140
|
+
echo "push attempt ${attempt} lost a race with another push to main; rebasing" >&2
|
|
141
|
+
if ! git pull --rebase origin main; then
|
|
142
|
+
git rebase --abort || true
|
|
143
|
+
echo "could not rebase the bump commit onto main — nothing was pushed; re-dispatch the same version once main is settled" >&2
|
|
144
|
+
exit 1
|
|
145
|
+
fi
|
|
146
|
+
done
|
|
147
|
+
echo "bump push failed after 3 attempts — --atomic means nothing landed, so re-dispatch the same version" >&2
|
|
148
|
+
exit 1
|
|
149
|
+
- name: Assert expected version == pyproject == __init__ == uv.lock
|
|
150
|
+
env:
|
|
151
|
+
VERSION: ${{ inputs.version }}
|
|
152
|
+
run: |
|
|
153
|
+
pyproject=$(grep -m1 '^version = ' pyproject.toml | cut -d'"' -f2)
|
|
154
|
+
init=$(grep -m1 '__version__' src/dxs/__init__.py | cut -d'"' -f2)
|
|
155
|
+
lock=$(awk '/^name = "datex-studio-cli"$/ {found=1} found && /^version = / {print; exit}' uv.lock | cut -d'"' -f2)
|
|
156
|
+
if [ "${GITHUB_EVENT_NAME}" = "push" ]; then
|
|
157
|
+
expected="${GITHUB_REF#refs/tags/v}"
|
|
158
|
+
else
|
|
159
|
+
expected="${VERSION:-$pyproject}"
|
|
160
|
+
fi
|
|
161
|
+
echo "expected=${expected} pyproject=${pyproject} __init__=${init} uv.lock=${lock}"
|
|
162
|
+
if [ "$expected" != "$pyproject" ] || [ "$pyproject" != "$init" ] || [ "$pyproject" != "$lock" ]; then
|
|
163
|
+
echo "version sources disagree — run 'uv lock' and align all three before releasing" >&2
|
|
164
|
+
exit 1
|
|
165
|
+
fi
|
|
166
|
+
- uses: actions/setup-node@v7
|
|
167
|
+
with:
|
|
168
|
+
node-version: 22
|
|
169
|
+
- name: Build the web UI (shipped via hatch artifacts)
|
|
170
|
+
working-directory: src/dxs/web/frontend
|
|
171
|
+
run: |
|
|
172
|
+
npm ci
|
|
173
|
+
npm run build
|
|
174
|
+
- name: Assert the frontend build actually produced the static export
|
|
175
|
+
run: test -f src/dxs/web/static/index.html
|
|
176
|
+
- run: uv build
|
|
177
|
+
- name: Publish via trusted publishing
|
|
178
|
+
run: uv publish
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: datex-studio-cli
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.6
|
|
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
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "datex-studio-cli"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.6"
|
|
8
8
|
description = "CLI for Datex Studio low-code platform, designed for LLM-based AI agents"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -165,6 +165,7 @@ markers = [
|
|
|
165
165
|
"telemetry_live: run without the suite-wide DXS_TELEMETRY=0 kill-switch",
|
|
166
166
|
"live_auth: sign in against real Entra tenants (opt-in; needs DXS_LIVE_AUTH=1 + a human)",
|
|
167
167
|
"mssql: integration tests against a throwaway SQL Server container (opt-in; needs DXS_MSSQL_TEST=1)",
|
|
168
|
+
"schema_capability_probe: exercise the real Indexes capability probe (no assumed-capable override)",
|
|
168
169
|
]
|
|
169
170
|
|
|
170
171
|
[tool.coverage.run]
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
"""Main CLI entry point for Datex Studio CLI."""
|
|
2
2
|
|
|
3
|
+
import errno
|
|
4
|
+
import os
|
|
3
5
|
import sys
|
|
4
6
|
from collections.abc import Callable
|
|
5
|
-
from typing import Any, TypeVar, cast
|
|
7
|
+
from typing import Any, NoReturn, TypeVar, cast
|
|
6
8
|
|
|
7
9
|
import click
|
|
8
10
|
|
|
9
11
|
from dxs import __app_name__, __version__
|
|
10
12
|
from dxs.context import DxsContext, pass_context # re-exported for backward compat
|
|
11
|
-
from dxs.core.auth.datex_tenant import
|
|
13
|
+
from dxs.core.auth.datex_tenant import InternalCommandGateMixin
|
|
12
14
|
from dxs.core.output import OutputFormat, format_error
|
|
13
15
|
from dxs.core.output.redaction import (
|
|
14
16
|
AUTHOR_VERBOSE_FIELDS as _AUTHOR_VERBOSE_FIELDS, # noqa: F401 — re-exported
|
|
@@ -121,12 +123,12 @@ HIDDEN_COMMAND_ALIASES = frozenset({"config"})
|
|
|
121
123
|
RAW_ARGS_META_KEY = "dxs.raw_args"
|
|
122
124
|
|
|
123
125
|
|
|
124
|
-
class DxsGroup(click.Group):
|
|
126
|
+
class DxsGroup(InternalCommandGateMixin, click.Group):
|
|
125
127
|
"""Custom Group that shows global options in subcommand help.
|
|
126
128
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
Gating of the Datex-internal command groups comes from
|
|
130
|
+
:class:`InternalCommandGateMixin`, shared with the subgroups that hold one
|
|
131
|
+
(``dxs source branch workitem``).
|
|
130
132
|
"""
|
|
131
133
|
|
|
132
134
|
def invoke(self, ctx: click.Context) -> Any:
|
|
@@ -136,28 +138,20 @@ class DxsGroup(click.Group):
|
|
|
136
138
|
so ``_resolve_command_path`` reads them back from ``ctx.meta``.
|
|
137
139
|
"""
|
|
138
140
|
ctx.meta[RAW_ARGS_META_KEY] = [*getattr(ctx, "_protected_args", []), *ctx.args]
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
try:
|
|
142
|
+
return super().invoke(ctx)
|
|
143
|
+
except (BrokenPipeError, OSError) as e:
|
|
144
|
+
# Handle a closed stdout here, inside Click's own invoke, rather
|
|
145
|
+
# than around cli(): Click catches EPIPE itself and exits 1, so by
|
|
146
|
+
# the time main() sees anything it is a SystemExit. Raising
|
|
147
|
+
# SystemExit from here passes straight through that handler.
|
|
148
|
+
if not _is_broken_pipe(e):
|
|
149
|
+
raise
|
|
150
|
+
_exit_broken_pipe()
|
|
144
151
|
|
|
145
152
|
def list_commands(self, ctx: click.Context) -> list[str]:
|
|
146
153
|
"""List visible subcommands, omitting hidden aliases and gated groups."""
|
|
147
|
-
return [
|
|
148
|
-
name
|
|
149
|
-
for name in super().list_commands(ctx)
|
|
150
|
-
if name not in HIDDEN_COMMAND_ALIASES and not self._is_gated(name)
|
|
151
|
-
]
|
|
152
|
-
|
|
153
|
-
def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command | None:
|
|
154
|
-
"""Resolve a subcommand, refusing unavailable Datex-internal groups."""
|
|
155
|
-
if self._is_gated(cmd_name):
|
|
156
|
-
ctx.fail(
|
|
157
|
-
f"'{cmd_name}' is a Datex-internal command group and requires a "
|
|
158
|
-
"Datex tenant identity. Run 'dxs auth login' with your Datex account."
|
|
159
|
-
)
|
|
160
|
-
return super().get_command(ctx, cmd_name)
|
|
154
|
+
return [name for name in super().list_commands(ctx) if name not in HIDDEN_COMMAND_ALIASES]
|
|
161
155
|
|
|
162
156
|
def format_options(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
|
|
163
157
|
"""Write options to the formatter, including global options."""
|
|
@@ -547,6 +541,15 @@ def handle_errors(f: F) -> F:
|
|
|
547
541
|
_record_error_silently(e, dxs_ctx)
|
|
548
542
|
dxs_ctx.output_error(e)
|
|
549
543
|
sys.exit(1)
|
|
544
|
+
except (BrokenPipeError, OSError) as e:
|
|
545
|
+
# A closed stdout is not a command failure: re-raise so main()
|
|
546
|
+
# can silence it. Rendering an error envelope here would both
|
|
547
|
+
# lie to the user and record a bogus command.error.
|
|
548
|
+
if not _is_broken_pipe(e):
|
|
549
|
+
_record_error_silently(e, dxs_ctx)
|
|
550
|
+
dxs_ctx.output_error(e)
|
|
551
|
+
sys.exit(1)
|
|
552
|
+
raise
|
|
550
553
|
except (click.ClickException, click.Abort, click.exceptions.Exit):
|
|
551
554
|
# Let Click handle its own exceptions and control-flow signals
|
|
552
555
|
# (Abort from confirm(abort=True), Exit from ctx.exit) — these are
|
|
@@ -560,6 +563,67 @@ def handle_errors(f: F) -> F:
|
|
|
560
563
|
return cast(F, wrapper)
|
|
561
564
|
|
|
562
565
|
|
|
566
|
+
#: 128 + SIGPIPE, the conventional shell status for "died because the pipe closed".
|
|
567
|
+
BROKEN_PIPE_EXIT_CODE = 141
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def _is_broken_pipe(exc: BaseException) -> bool:
|
|
571
|
+
"""True when ``exc`` is stdout's consumer going away rather than a failure.
|
|
572
|
+
|
|
573
|
+
POSIX raises ``BrokenPipeError`` (EPIPE). Windows has no SIGPIPE and instead
|
|
574
|
+
fails the underlying ``flush()`` with ``OSError [Errno 22] Invalid argument``
|
|
575
|
+
— so EINVAL counts only there, since on POSIX it means a hundred other
|
|
576
|
+
things and must not be mistaken for a closed pipe.
|
|
577
|
+
"""
|
|
578
|
+
if isinstance(exc, BrokenPipeError):
|
|
579
|
+
return True
|
|
580
|
+
if not isinstance(exc, OSError):
|
|
581
|
+
return False
|
|
582
|
+
if exc.errno == errno.EPIPE:
|
|
583
|
+
return True
|
|
584
|
+
return exc.errno == errno.EINVAL and sys.platform == "win32"
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def _silence_stdout() -> None:
|
|
588
|
+
"""Point stdout at devnull so interpreter shutdown cannot re-raise.
|
|
589
|
+
|
|
590
|
+
Python flushes the standard streams on exit; against a closed pipe that
|
|
591
|
+
raises a second time, past any handler we control, and CPython prints
|
|
592
|
+
"Exception ignored in: <_io.TextIOWrapper name='<stdout>'>". Redirecting the
|
|
593
|
+
file descriptor is the documented way to avoid it. Best-effort: under pytest
|
|
594
|
+
or Click's CliRunner stdout is a StringIO with no real fileno.
|
|
595
|
+
"""
|
|
596
|
+
devnull = None
|
|
597
|
+
try:
|
|
598
|
+
devnull = os.open(os.devnull, os.O_WRONLY)
|
|
599
|
+
os.dup2(devnull, sys.stdout.fileno())
|
|
600
|
+
except Exception: # noqa: BLE001 - silencing output must never itself fail
|
|
601
|
+
pass
|
|
602
|
+
finally:
|
|
603
|
+
if devnull is not None:
|
|
604
|
+
try:
|
|
605
|
+
os.close(devnull)
|
|
606
|
+
except OSError:
|
|
607
|
+
pass
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def _exit_broken_pipe() -> NoReturn:
|
|
611
|
+
"""Leave quietly after the consumer of stdout went away."""
|
|
612
|
+
_silence_stdout()
|
|
613
|
+
_record_broken_pipe_silently()
|
|
614
|
+
sys.exit(BROKEN_PIPE_EXIT_CODE)
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def _record_broken_pipe_silently() -> None:
|
|
618
|
+
"""Record a closed pipe as a completed run, never as command.error."""
|
|
619
|
+
try:
|
|
620
|
+
from dxs.core.telemetry import record_end
|
|
621
|
+
|
|
622
|
+
record_end(exit_code=BROKEN_PIPE_EXIT_CODE)
|
|
623
|
+
except Exception: # noqa: BLE001
|
|
624
|
+
pass
|
|
625
|
+
|
|
626
|
+
|
|
563
627
|
def _record_error_silently(exc: BaseException, dxs_ctx: "DxsContext | None" = None) -> None:
|
|
564
628
|
"""Emit a telemetry error event; never raises.
|
|
565
629
|
|
|
@@ -850,6 +914,15 @@ def main() -> None:
|
|
|
850
914
|
# SystemExit from sys.exit() inside handle_errors — telemetry already
|
|
851
915
|
# recorded an error there; just propagate.
|
|
852
916
|
raise
|
|
917
|
+
except (BrokenPipeError, OSError) as e:
|
|
918
|
+
if not _is_broken_pipe(e):
|
|
919
|
+
_record_error_silently(e)
|
|
920
|
+
raise
|
|
921
|
+
# The consumer stopped reading (`| head`, an agent that moved on).
|
|
922
|
+
# Say nothing, record a completed run, and exit on the SIGPIPE
|
|
923
|
+
# convention so `set -o pipefail` scripts can still tell truncated
|
|
924
|
+
# output from a clean one.
|
|
925
|
+
_exit_broken_pipe()
|
|
853
926
|
except BaseException as e:
|
|
854
927
|
# Anything else escaping cli() shouldn't take telemetry with it.
|
|
855
928
|
_record_error_silently(e)
|