datex-studio-cli 0.3.3__tar.gz → 0.4.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.
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/.gitignore +7 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/PKG-INFO +2 -1
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/pyproject.toml +3 -2
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/__init__.py +1 -1
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/cli.py +68 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/branch.py +173 -49
- datex_studio_cli-0.4.0/src/dxs/commands/datasource.py +1250 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/devops.py +62 -4
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/document.py +252 -144
- datex_studio_cli-0.4.0/src/dxs/commands/endpoint.py +609 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/explore.py +856 -240
- datex_studio_cli-0.4.0/src/dxs/commands/function.py +424 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/release_notes.py +2 -2
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/report/__init__.py +29 -14
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/report/add_cmds.py +50 -52
- datex_studio_cli-0.4.0/src/dxs/commands/report/api.py +427 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/report/batch.py +1 -1
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/report/create.py +77 -45
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/report/data.py +1 -1
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/report/dataset.py +80 -11
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/report/edit.py +3 -3
- datex_studio_cli-0.4.0/src/dxs/commands/report/folder.py +472 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/report/preview.py +0 -2
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/schema.py +102 -971
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/servicepack.py +5 -5
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/source.py +3 -2
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/studio.py +8 -1
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/context.py +1 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/api/endpoints.py +35 -4
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/api/models.py +7 -7
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/cache.py +106 -106
- datex_studio_cli-0.4.0/src/dxs/core/datasource/__init__.py +19 -0
- datex_studio_cli-0.4.0/src/dxs/core/datasource/flow_generator.py +184 -0
- datex_studio_cli-0.4.0/src/dxs/core/datasource/flow_models.py +45 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/datasource/generator.py +36 -17
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/datasource/models.py +25 -28
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/datasource/parsers.py +95 -101
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/datasource/resolver.py +59 -24
- datex_studio_cli-0.4.0/src/dxs/core/datasource/type_def_parser.py +9 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/datasource/validator.py +43 -37
- datex_studio_cli-0.4.0/src/dxs/core/designer/flow_models.py +34 -0
- datex_studio_cli-0.4.0/src/dxs/core/designer/models.py +36 -0
- datex_studio_cli-0.4.0/src/dxs/core/designer/parsers.py +224 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/devops/models.py +24 -1
- datex_studio_cli-0.4.0/src/dxs/core/function/__init__.py +0 -0
- datex_studio_cli-0.4.0/src/dxs/core/function/generator.py +143 -0
- datex_studio_cli-0.4.0/src/dxs/core/function/models.py +45 -0
- datex_studio_cli-0.4.0/src/dxs/core/function/validator.py +98 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/graph.py +88 -54
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/report/datasource_binding.py +18 -20
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/report/engine.py +120 -86
- datex_studio_cli-0.4.0/src/dxs/report/folder.py +469 -0
- datex_studio_cli-0.4.0/src/dxs/report/manifest.py +126 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/report/models.py +6 -21
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/report/preview.py +12 -4
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/report/preview_arjs.py +29 -3
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/report/schema.py +8 -3
- datex_studio_cli-0.4.0/src/dxs/report/templates/__init__.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/report/validator.py +304 -13
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/report/wrapper.py +0 -23
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/utils/click_options.py +142 -1
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/utils/paths.py +26 -22
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/utils/resolvers.py +82 -0
- datex_studio_cli-0.4.0/src/dxs/utils/update_check.py +221 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/404/index.html +1 -1
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/404.html +1 -1
- datex_studio_cli-0.4.0/src/dxs/web/static/_next/static/chunks/538-84e2e111415f1fda.js +1 -0
- datex_studio_cli-0.3.3/src/dxs/web/static/_next/static/css/a4597027cb06b77d.css → datex_studio_cli-0.4.0/src/dxs/web/static/_next/static/css/360c657de5e04df8.css +1 -1
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/design/capture/index.html +1 -1
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/design/capture/index.txt +3 -3
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/design/index.html +1 -1
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/design/index.txt +3 -3
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/index.html +1 -1
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/index.txt +2 -2
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/render-cli.js +200 -71
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/report-preview.html +53 -1
- datex_studio_cli-0.3.3/examples/bill-of-lading/01-schema-exploration.md +0 -303
- datex_studio_cli-0.3.3/examples/bill-of-lading/02-field-mapping.md +0 -192
- datex_studio_cli-0.3.3/examples/bill-of-lading/bill-of-lading.data.json +0 -105
- datex_studio_cli-0.3.3/examples/bill-of-lading/bill-of-lading.rdlx-json +0 -857
- datex_studio_cli-0.3.3/src/dxs/commands/datasource.py +0 -564
- datex_studio_cli-0.3.3/src/dxs/commands/report/api.py +0 -716
- datex_studio_cli-0.3.3/src/dxs/core/datasource/__init__.py +0 -10
- datex_studio_cli-0.3.3/src/dxs/report/owned_datasource.py +0 -340
- datex_studio_cli-0.3.3/src/dxs/web/static/_next/static/chunks/538-0c48d257061c2519.js +0 -1
- datex_studio_cli-0.3.3/test-preview.svg +0 -1
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/README.md +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/report-creator.skill +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/scripts/analyze_corpus.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/scripts/generate_rdlx_schema.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/__main__.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/__init__.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/api.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/auth.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/config.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/crm.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/env.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/marketplace.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/odata.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/organization.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/proxy.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/repo.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/report/schema_cmds.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/commands/user.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/__init__.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/api/__init__.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/api/client.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/api/metadata_models.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/auth/__init__.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/auth/decorators.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/auth/msal_client.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/auth/token_cache.py +0 -0
- {datex_studio_cli-0.3.3/src/dxs/report/templates → datex_studio_cli-0.4.0/src/dxs/core/designer}/__init__.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/devops/__init__.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/devops/client.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/devops/helpers.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/dynamics/__init__.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/dynamics/client.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/footprint/__init__.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/footprint/client.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/footprint/edmx_parser.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/footprint/metadata.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/output/__init__.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/output/csv_fmt.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/output/formatter.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/output/json_fmt.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/output/redaction.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/output/yaml_fmt.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/release_notes.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/core/responses.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/models/__init__.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/report/__init__.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/report/field_parser.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/report/svg_renderer.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/report/templates/bill_of_lading.rdlx-json +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/report/templates/shipping_label.rdlx-json +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/report/validate_arjs.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/utils/__init__.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/utils/config.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/utils/errors.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/utils/filtering.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/utils/image.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/utils/responses.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/utils/restricted.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/utils/sorting.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/__init__.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/app.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/design.py +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/proxy_app.py +0 -0
- {datex_studio_cli-0.3.3/src/dxs/web/static/_next/static/tKckXNZJLjE7JZyWX_89t → datex_studio_cli-0.4.0/src/dxs/web/static/_next/static/UgyAMtPjH-Pt0RYWOHzIT}/_buildManifest.js +0 -0
- {datex_studio_cli-0.3.3/src/dxs/web/static/_next/static/tKckXNZJLjE7JZyWX_89t → datex_studio_cli-0.4.0/src/dxs/web/static/_next/static/UgyAMtPjH-Pt0RYWOHzIT}/_ssgManifest.js +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/0b24cca5-c1e1c8810348f107.js +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/255-102f2e5b2e3dc2ef.js +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/697-0e000ab410d9f470.js +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/871-1acacdb7b4022abf.js +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/8b8f67fc-6295949a4b5485a4.js +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/909-c88abba3cf0caec3.js +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/app/_not-found/page-ea1be7001c230704.js +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/app/design/capture/page-a5e129c2d223432c.js +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/app/design/page-21ba66a3dd3b03f0.js +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/app/layout-2cc6eac09e476c91.js +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/app/page-20c358395882cdac.js +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/framework-de98b93a850cfc71.js +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/main-0f18f91200dac003.js +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/main-app-b69998d8941231d8.js +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/pages/_app-7d307437aca18ad4.js +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/_next/static/chunks/webpack-2b297ada5306c17f.js +0 -0
- {datex_studio_cli-0.3.3 → datex_studio_cli-0.4.0}/src/dxs/web/static/report-validate.html +0 -0
|
@@ -3,6 +3,7 @@ exploration/
|
|
|
3
3
|
reviews/
|
|
4
4
|
.tap/
|
|
5
5
|
.superpowers/
|
|
6
|
+
.worktrees/
|
|
6
7
|
|
|
7
8
|
node_modules/
|
|
8
9
|
|
|
@@ -92,3 +93,9 @@ Thumbs.db
|
|
|
92
93
|
|
|
93
94
|
#Claude
|
|
94
95
|
.claude/settings.local.json
|
|
96
|
+
.claude/skills
|
|
97
|
+
|
|
98
|
+
# Report generation assets
|
|
99
|
+
/reports/
|
|
100
|
+
test-preview.svg
|
|
101
|
+
test-preview.svg
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datex-studio-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
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
|
|
@@ -29,6 +29,7 @@ Requires-Dist: pydantic>=2.5.0
|
|
|
29
29
|
Requires-Dist: pyyaml>=6.0
|
|
30
30
|
Provides-Extra: dev
|
|
31
31
|
Requires-Dist: fastapi>=0.115.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: jsonschema>=4.20.0; extra == 'dev'
|
|
32
33
|
Requires-Dist: mypy>=1.8.0; extra == 'dev'
|
|
33
34
|
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
34
35
|
Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "datex-studio-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
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"
|
|
@@ -59,6 +59,7 @@ dev = [
|
|
|
59
59
|
"ruff>=0.4.0",
|
|
60
60
|
"mypy>=1.8.0",
|
|
61
61
|
"types-PyYAML>=6.0.0",
|
|
62
|
+
"jsonschema>=4.20.0",
|
|
62
63
|
# Include studio dependencies for testing studio/proxy commands
|
|
63
64
|
"fastapi>=0.115.0",
|
|
64
65
|
"uvicorn[standard]>=0.32.0",
|
|
@@ -81,7 +82,7 @@ exclude = ["src/dxs/web/frontend/", "example-reports/"]
|
|
|
81
82
|
artifacts = ["src/dxs/web/static/"]
|
|
82
83
|
exclude = [
|
|
83
84
|
".claude/",
|
|
84
|
-
"
|
|
85
|
+
"examples/",
|
|
85
86
|
".mypy_cache/",
|
|
86
87
|
".pytest_cache/",
|
|
87
88
|
".ruff_cache/",
|
|
@@ -162,6 +162,13 @@ class DxsGroup(click.Group):
|
|
|
162
162
|
default=False,
|
|
163
163
|
help="Show authentication environment variables for debugging",
|
|
164
164
|
)
|
|
165
|
+
@click.option(
|
|
166
|
+
"--no-update-check",
|
|
167
|
+
"no_update_check",
|
|
168
|
+
is_flag=True,
|
|
169
|
+
default=False,
|
|
170
|
+
help="Skip the PyPI update check for this invocation",
|
|
171
|
+
)
|
|
165
172
|
@click.pass_context
|
|
166
173
|
def cli(
|
|
167
174
|
ctx: click.Context,
|
|
@@ -177,6 +184,7 @@ def cli(
|
|
|
177
184
|
save_path: str | None,
|
|
178
185
|
force_overwrite: bool,
|
|
179
186
|
diagnose_auth: bool,
|
|
187
|
+
no_update_check: bool,
|
|
180
188
|
) -> None:
|
|
181
189
|
"""Datex Studio CLI - Command-line interface for Datex Studio platform.
|
|
182
190
|
|
|
@@ -249,6 +257,9 @@ def cli(
|
|
|
249
257
|
if diagnose_auth:
|
|
250
258
|
_show_auth_diagnostics()
|
|
251
259
|
|
|
260
|
+
# Stash for the result_callback to honor --no-update-check
|
|
261
|
+
dxs_ctx.no_update_check = no_update_check
|
|
262
|
+
|
|
252
263
|
|
|
253
264
|
def _mask_token(token: str | None, visible_chars: int = 8) -> str:
|
|
254
265
|
"""Mask a token for display, showing only first/last few characters.
|
|
@@ -328,7 +339,9 @@ def register_commands() -> None:
|
|
|
328
339
|
crm,
|
|
329
340
|
datasource,
|
|
330
341
|
devops,
|
|
342
|
+
endpoint,
|
|
331
343
|
env,
|
|
344
|
+
function,
|
|
332
345
|
marketplace,
|
|
333
346
|
odata,
|
|
334
347
|
organization,
|
|
@@ -345,7 +358,9 @@ def register_commands() -> None:
|
|
|
345
358
|
cli.add_command(crm.crm)
|
|
346
359
|
cli.add_command(datasource.datasource)
|
|
347
360
|
cli.add_command(devops.devops)
|
|
361
|
+
cli.add_command(endpoint.endpoint)
|
|
348
362
|
cli.add_command(env.env)
|
|
363
|
+
cli.add_command(function.function)
|
|
349
364
|
cli.add_command(marketplace.marketplace)
|
|
350
365
|
cli.add_command(odata.odata)
|
|
351
366
|
cli.add_command(organization.organization)
|
|
@@ -362,6 +377,59 @@ def register_commands() -> None:
|
|
|
362
377
|
cli.add_command(schema.schema)
|
|
363
378
|
|
|
364
379
|
|
|
380
|
+
@cli.result_callback()
|
|
381
|
+
@click.pass_context
|
|
382
|
+
def _after_command(ctx: click.Context, result: Any, **kwargs: Any) -> Any:
|
|
383
|
+
"""Run post-command hooks (currently: PyPI update notice)."""
|
|
384
|
+
from dxs.utils.update_check import maybe_show_update_notice
|
|
385
|
+
|
|
386
|
+
dxs_ctx: DxsContext | None = ctx.find_object(DxsContext)
|
|
387
|
+
skip = bool(dxs_ctx and dxs_ctx.no_update_check)
|
|
388
|
+
try:
|
|
389
|
+
maybe_show_update_notice(skip=skip)
|
|
390
|
+
except Exception: # noqa: BLE001 - never let the update check break the CLI
|
|
391
|
+
pass
|
|
392
|
+
return result
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
@cli.command("update")
|
|
396
|
+
@click.option(
|
|
397
|
+
"--check",
|
|
398
|
+
"check_only",
|
|
399
|
+
is_flag=True,
|
|
400
|
+
default=False,
|
|
401
|
+
help="Print whether an update is available without prompting to install",
|
|
402
|
+
)
|
|
403
|
+
def update_cmd(check_only: bool) -> None:
|
|
404
|
+
"""Check PyPI for a newer release and optionally upgrade in place."""
|
|
405
|
+
from dxs.utils.update_check import (
|
|
406
|
+
_fetch_latest_version,
|
|
407
|
+
_run_upgrade,
|
|
408
|
+
detect_upgrade_command,
|
|
409
|
+
is_newer,
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
click.echo(f"Current version: {__version__}", err=True)
|
|
413
|
+
click.echo("Checking PyPI...", err=True)
|
|
414
|
+
latest = _fetch_latest_version()
|
|
415
|
+
if latest is None:
|
|
416
|
+
click.echo("Could not reach PyPI. Check your network and try again.", err=True)
|
|
417
|
+
sys.exit(1)
|
|
418
|
+
click.echo(f"Latest on PyPI: {latest}", err=True)
|
|
419
|
+
|
|
420
|
+
if not is_newer(latest, __version__):
|
|
421
|
+
click.echo("You are on the latest version.", err=True)
|
|
422
|
+
return
|
|
423
|
+
|
|
424
|
+
upgrade_cmd = detect_upgrade_command()
|
|
425
|
+
click.echo(f"Upgrade command: {upgrade_cmd}", err=True)
|
|
426
|
+
if check_only:
|
|
427
|
+
return
|
|
428
|
+
|
|
429
|
+
if click.confirm("Upgrade now?", default=True, err=True):
|
|
430
|
+
sys.exit(_run_upgrade(upgrade_cmd))
|
|
431
|
+
|
|
432
|
+
|
|
365
433
|
# Register commands when module loads
|
|
366
434
|
register_commands()
|
|
367
435
|
|
|
@@ -15,9 +15,11 @@ from dxs.core.api import (
|
|
|
15
15
|
SourceControlEndpoints,
|
|
16
16
|
)
|
|
17
17
|
from dxs.core.api.models import (
|
|
18
|
+
APPLICATION_DEFINITION_TYPE_NAMES,
|
|
18
19
|
BRANCH_STATUS_ALIASES,
|
|
19
20
|
BRANCH_STATUS_CONTEXT,
|
|
20
21
|
BRANCH_STATUS_NAMES,
|
|
22
|
+
ApplicationDefinitionType,
|
|
21
23
|
BranchStatus,
|
|
22
24
|
)
|
|
23
25
|
from dxs.core.auth import require_auth, verify_org_match
|
|
@@ -44,10 +46,14 @@ def enrich_branch_with_status(branch: dict[str, Any]) -> dict[str, Any]:
|
|
|
44
46
|
- statusName: Human-readable status (e.g., "Main", "WorkspaceActive")
|
|
45
47
|
- statusContext: Semantic description for LLM understanding
|
|
46
48
|
- isCommit: True if this is a commit snapshot (WorkspaceHistory)
|
|
47
|
-
- isRelease: True if this is a published release (PublishedMain
|
|
48
|
-
- isCurrentRelease: True if this is the
|
|
49
|
+
- isRelease: True if this is a published release (PublishedMain)
|
|
50
|
+
- isCurrentRelease: True if this is the latest release (PublishedMain with isLatest=true)
|
|
49
51
|
- isFeatureBranch: True if this is a feature branch (WorkspaceActive)
|
|
50
52
|
- isMainBranch: True if this is the main branch (Main)
|
|
53
|
+
- appTypeName: Human-readable application type (e.g., "API", "Web", "Mobile").
|
|
54
|
+
Only set when the upstream payload carries applicationDefinition.applicationDefinitionTypeId
|
|
55
|
+
(e.g. `branch show`); omitted on payloads that don't (e.g. `branch list`).
|
|
56
|
+
- isApiApplication: True if this is an API application (type 6). Same presence rule as appTypeName.
|
|
51
57
|
|
|
52
58
|
Args:
|
|
53
59
|
branch: Branch data dictionary from the API
|
|
@@ -62,11 +68,21 @@ def enrich_branch_with_status(branch: dict[str, Any]) -> dict[str, Any]:
|
|
|
62
68
|
|
|
63
69
|
# Add semantic flags for easy LLM filtering/understanding
|
|
64
70
|
branch["isCommit"] = status_id == BranchStatus.WORKSPACE_HISTORY
|
|
65
|
-
branch["isRelease"] = status_id
|
|
66
|
-
branch["isCurrentRelease"] = status_id == BranchStatus.PUBLISHED_MAIN
|
|
71
|
+
branch["isRelease"] = status_id == BranchStatus.PUBLISHED_MAIN
|
|
72
|
+
branch["isCurrentRelease"] = status_id == BranchStatus.PUBLISHED_MAIN and branch.get(
|
|
73
|
+
"isLatest", False
|
|
74
|
+
)
|
|
67
75
|
branch["isFeatureBranch"] = status_id == BranchStatus.WORKSPACE_ACTIVE
|
|
68
76
|
branch["isMainBranch"] = status_id == BranchStatus.MAIN
|
|
69
77
|
|
|
78
|
+
# Add application type info only when the upstream payload carries it.
|
|
79
|
+
app_def = branch.get("applicationDefinition") or {}
|
|
80
|
+
app_type_id = app_def.get("applicationDefinitionTypeId")
|
|
81
|
+
if app_type_id is not None:
|
|
82
|
+
app_type_id_int = int(app_type_id)
|
|
83
|
+
branch["appTypeName"] = APPLICATION_DEFINITION_TYPE_NAMES.get(app_type_id_int, "Unknown")
|
|
84
|
+
branch["isApiApplication"] = app_type_id_int == ApplicationDefinitionType.API
|
|
85
|
+
|
|
70
86
|
return branch
|
|
71
87
|
|
|
72
88
|
|
|
@@ -230,40 +246,67 @@ def _fetch_branches_for_repo(
|
|
|
230
246
|
client: ApiClient,
|
|
231
247
|
repo_id: int,
|
|
232
248
|
status_ids: list[int] | None = None,
|
|
249
|
+
group_id: int | None = None,
|
|
250
|
+
default_group_only: bool = False,
|
|
233
251
|
) -> list[dict[str, Any]]:
|
|
234
252
|
"""Fetch branches for a repository via its application groups.
|
|
235
253
|
|
|
236
254
|
The API requires fetching branches through application groups, not directly
|
|
237
255
|
via repository ID. Each repository has one or more groups (typically a
|
|
238
|
-
"Default" group
|
|
256
|
+
"Default" group plus zero-or-more Service Pack groups).
|
|
239
257
|
|
|
240
258
|
Args:
|
|
241
259
|
client: API client instance
|
|
242
260
|
repo_id: Repository (ApplicationDefinition) ID
|
|
243
261
|
status_ids: Optional list of status IDs for server-side filtering
|
|
262
|
+
group_id: If provided, fetch branches only from this application group
|
|
263
|
+
(skipping the "iterate all groups" flow). Mutually exclusive with
|
|
264
|
+
default_group_only.
|
|
265
|
+
default_group_only: If True, fetch branches only from the repository's
|
|
266
|
+
Default group (applicationGroupTypeId=1). Takes precedence when
|
|
267
|
+
group_id is also provided.
|
|
244
268
|
|
|
245
269
|
Returns:
|
|
246
|
-
List of branch dictionaries
|
|
270
|
+
List of branch dictionaries. Each branch is stamped with the
|
|
271
|
+
``applicationGroupId`` of the group it was fetched from — the server's
|
|
272
|
+
``applicationGroupId`` field in list responses is unreliable (it sometimes
|
|
273
|
+
points at a child Service Pack group instead of the branch's own group).
|
|
247
274
|
"""
|
|
248
|
-
#
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
275
|
+
# Resolve the target groups. Three modes:
|
|
276
|
+
# 1. group_id given + default_group_only False -> fetch from that one group
|
|
277
|
+
# 2. default_group_only True -> find Default group, fetch only that
|
|
278
|
+
# 3. Neither -> iterate all groups (legacy flow)
|
|
279
|
+
target_groups: list[dict[str, Any]]
|
|
280
|
+
|
|
281
|
+
if default_group_only:
|
|
282
|
+
groups = client.get(*ApplicationGroupEndpoints.list(repo_id, group_type_id=1))
|
|
283
|
+
if not isinstance(groups, list):
|
|
284
|
+
groups = [groups] if groups else []
|
|
285
|
+
target_groups = [g for g in groups if g.get("id")]
|
|
286
|
+
elif group_id is not None:
|
|
287
|
+
target_groups = [{"id": group_id}]
|
|
288
|
+
else:
|
|
289
|
+
groups = client.get(*ApplicationGroupEndpoints.list(repo_id))
|
|
290
|
+
if not isinstance(groups, list):
|
|
291
|
+
groups = [groups] if groups else []
|
|
292
|
+
target_groups = [g for g in groups if g.get("id")]
|
|
252
293
|
|
|
253
|
-
if not
|
|
294
|
+
if not target_groups:
|
|
254
295
|
return []
|
|
255
296
|
|
|
256
|
-
# Fetch branches from all groups
|
|
257
297
|
all_branches: list[dict[str, Any]] = []
|
|
258
|
-
for group in
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
continue
|
|
262
|
-
|
|
263
|
-
branches = client.get(*BranchEndpoints.list(group_id, status_ids))
|
|
298
|
+
for group in target_groups:
|
|
299
|
+
gid = group["id"]
|
|
300
|
+
branches = client.get(*BranchEndpoints.list(gid, status_ids))
|
|
264
301
|
if not isinstance(branches, list):
|
|
265
302
|
branches = [branches] if branches else []
|
|
266
303
|
|
|
304
|
+
# Stamp the known group id on each branch — overrides the server's
|
|
305
|
+
# sometimes-misleading applicationGroupId field (which can reference a
|
|
306
|
+
# descendant Service Pack group rather than the branch's own group).
|
|
307
|
+
for branch in branches:
|
|
308
|
+
branch["applicationGroupId"] = gid
|
|
309
|
+
|
|
267
310
|
all_branches.extend(branches)
|
|
268
311
|
|
|
269
312
|
return all_branches
|
|
@@ -296,11 +339,25 @@ def _fetch_branches_for_repo(
|
|
|
296
339
|
"--status",
|
|
297
340
|
"status_filter",
|
|
298
341
|
type=click.Choice(
|
|
299
|
-
["main", "
|
|
342
|
+
["main", "published", "history", "feature", "all"],
|
|
300
343
|
case_sensitive=False,
|
|
301
344
|
),
|
|
302
345
|
default=None,
|
|
303
|
-
help="Filter by branch status type (main,
|
|
346
|
+
help="Filter by branch status type (main, published, history, feature)",
|
|
347
|
+
)
|
|
348
|
+
@click.option(
|
|
349
|
+
"--group-id",
|
|
350
|
+
type=int,
|
|
351
|
+
default=None,
|
|
352
|
+
help="Filter to a specific application group ID (skips iterating all groups). "
|
|
353
|
+
"Incompatible with --all-repos.",
|
|
354
|
+
)
|
|
355
|
+
@click.option(
|
|
356
|
+
"--default-group",
|
|
357
|
+
is_flag=True,
|
|
358
|
+
default=False,
|
|
359
|
+
help="Filter to the repository's Default application group only (excludes "
|
|
360
|
+
"Service Pack groups). Incompatible with --all-repos and --group-id.",
|
|
304
361
|
)
|
|
305
362
|
@pagination_options(default_limit=10)
|
|
306
363
|
@sorting_options(
|
|
@@ -325,6 +382,8 @@ def branch_list(
|
|
|
325
382
|
org: str | None,
|
|
326
383
|
all_repos: bool,
|
|
327
384
|
status_filter: str | None,
|
|
385
|
+
group_id: int | None,
|
|
386
|
+
default_group: bool,
|
|
328
387
|
limit: int,
|
|
329
388
|
sort: str,
|
|
330
389
|
sort_direction: str | None,
|
|
@@ -344,8 +403,7 @@ def branch_list(
|
|
|
344
403
|
\b
|
|
345
404
|
Status Types:
|
|
346
405
|
main Main development branch (receives commits)
|
|
347
|
-
|
|
348
|
-
published Current published release
|
|
406
|
+
published Published releases (multiple can coexist in a group; use isLatest to find the current one)
|
|
349
407
|
history Commit snapshots (frozen after commit)
|
|
350
408
|
feature Feature branches (active development)
|
|
351
409
|
all All branches (default if not specified)
|
|
@@ -357,6 +415,8 @@ def branch_list(
|
|
|
357
415
|
--org Organization to scope repo search
|
|
358
416
|
--all-repos Search across all repositories
|
|
359
417
|
--status Filter by status type
|
|
418
|
+
--group-id Filter to a specific application group ID
|
|
419
|
+
--default-group Filter to the repository's Default group only (excludes Service Packs)
|
|
360
420
|
--limit, -n Maximum results (default: 10, 0 for unlimited)
|
|
361
421
|
--with-changes Include change counts (enables --sort changes)
|
|
362
422
|
--sort Sort by: name, created, modified, commit-date, status, changes
|
|
@@ -371,6 +431,8 @@ def branch_list(
|
|
|
371
431
|
Examples:
|
|
372
432
|
dxs source branch list --repo 10
|
|
373
433
|
dxs source branch list --repo 10 --status feature
|
|
434
|
+
dxs source branch list --repo 10 --default-group --status published
|
|
435
|
+
dxs source branch list --repo 10 --group-id 42 --status published
|
|
374
436
|
dxs source branch list --all-repos --status feature --modified-after 2024-01-01
|
|
375
437
|
dxs source branch list --repo 10 --status history --sort commit-date
|
|
376
438
|
dxs source branch list --repo-name "MyApp" --org "Datex"
|
|
@@ -382,6 +444,24 @@ def branch_list(
|
|
|
382
444
|
resolver = EntityResolver()
|
|
383
445
|
client = ApiClient()
|
|
384
446
|
|
|
447
|
+
# Validate mutually-exclusive group flags.
|
|
448
|
+
if all_repos and (group_id is not None or default_group):
|
|
449
|
+
raise ValidationError(
|
|
450
|
+
message="--group-id and --default-group cannot be combined with --all-repos",
|
|
451
|
+
code="DXS-VAL-002",
|
|
452
|
+
suggestions=[
|
|
453
|
+
"Drop --all-repos and scope to a single repo with --repo",
|
|
454
|
+
],
|
|
455
|
+
)
|
|
456
|
+
if group_id is not None and default_group:
|
|
457
|
+
raise ValidationError(
|
|
458
|
+
message="--group-id and --default-group are mutually exclusive",
|
|
459
|
+
code="DXS-VAL-002",
|
|
460
|
+
suggestions=[
|
|
461
|
+
"Pick one: --group-id <id> (explicit) or --default-group (auto-resolved)",
|
|
462
|
+
],
|
|
463
|
+
)
|
|
464
|
+
|
|
385
465
|
# Handle --all-repos mode
|
|
386
466
|
if all_repos:
|
|
387
467
|
# Scope to org if provided via --org or active org from config
|
|
@@ -436,7 +516,13 @@ def branch_list(
|
|
|
436
516
|
ctx.log(f"Filtering by status: {status_filter} (id={status_id})...")
|
|
437
517
|
|
|
438
518
|
# Fetch branches via application groups (API requires this flow)
|
|
439
|
-
branches = _fetch_branches_for_repo(
|
|
519
|
+
branches = _fetch_branches_for_repo(
|
|
520
|
+
client,
|
|
521
|
+
repo_id,
|
|
522
|
+
server_status_ids,
|
|
523
|
+
group_id=group_id,
|
|
524
|
+
default_group_only=default_group,
|
|
525
|
+
)
|
|
440
526
|
|
|
441
527
|
total_count = len(branches)
|
|
442
528
|
|
|
@@ -551,6 +637,10 @@ def branch_list(
|
|
|
551
637
|
# Add applied filters to metadata if any
|
|
552
638
|
if status_filter and status_filter.lower() != "all":
|
|
553
639
|
metadata_kwargs["status_filter"] = status_filter
|
|
640
|
+
if group_id is not None:
|
|
641
|
+
metadata_kwargs["group_id_filter"] = group_id
|
|
642
|
+
if default_group:
|
|
643
|
+
metadata_kwargs["default_group_only"] = True
|
|
554
644
|
if created_after:
|
|
555
645
|
metadata_kwargs["created_after"] = created_after.isoformat()
|
|
556
646
|
if created_before:
|
|
@@ -799,63 +889,96 @@ def delete(ctx: DxsContext, branch_id: int, force: bool) -> None:
|
|
|
799
889
|
def baseline(ctx: DxsContext, branch_id: int) -> None:
|
|
800
890
|
"""Find the baseline (previous release) for a branch.
|
|
801
891
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
generating release notes comparing
|
|
892
|
+
Among PublishedMain branches in the same application group as the target branch,
|
|
893
|
+
returns the one with the largest `releaseDate` strictly less than the target's
|
|
894
|
+
`releaseDate`. Useful for generating release notes comparing a release to its
|
|
895
|
+
predecessor.
|
|
896
|
+
|
|
897
|
+
Scoping by application group (rather than repository-wide) ensures that Service
|
|
898
|
+
Pack maintenance releases are compared against the previous SP release, and
|
|
899
|
+
Default group releases are compared against the previous Default release.
|
|
805
900
|
|
|
806
901
|
\b
|
|
807
902
|
Arguments:
|
|
808
|
-
BRANCH_ID Branch ID of the target
|
|
903
|
+
BRANCH_ID Branch ID of the target release
|
|
809
904
|
|
|
810
905
|
\b
|
|
811
906
|
Returns:
|
|
812
|
-
The most recent
|
|
907
|
+
The most recent PublishedMain branch released before the target within the
|
|
908
|
+
same application group, or a message if no prior release exists.
|
|
813
909
|
|
|
814
910
|
\b
|
|
815
911
|
Example:
|
|
816
912
|
dxs source branch baseline 63338
|
|
817
|
-
# Returns branch 62582 (the previous published version)
|
|
913
|
+
# Returns branch 62582 (the previous published version in the same group)
|
|
818
914
|
"""
|
|
819
915
|
client = ApiClient()
|
|
820
916
|
ctx.log(f"Finding baseline for branch {branch_id}...")
|
|
821
917
|
|
|
822
|
-
# Get the target branch to find its
|
|
823
|
-
|
|
824
|
-
repo_id =
|
|
918
|
+
# Get the target branch to find its group.
|
|
919
|
+
target_branch = client.get(BranchEndpoints.get(branch_id))
|
|
920
|
+
repo_id = target_branch.get("applicationDefinitionId")
|
|
921
|
+
group_id = target_branch.get("applicationGroupId")
|
|
825
922
|
|
|
826
|
-
if not repo_id:
|
|
923
|
+
if not repo_id or not group_id:
|
|
827
924
|
raise ValidationError(
|
|
828
|
-
message=f"Could not determine repository for branch {branch_id}",
|
|
925
|
+
message=f"Could not determine repository/group for branch {branch_id}",
|
|
829
926
|
code="DXS-VAL-001",
|
|
830
927
|
)
|
|
831
928
|
|
|
832
|
-
|
|
929
|
+
if target_branch.get("applicationStatusId") != BranchStatus.PUBLISHED_MAIN:
|
|
930
|
+
raise ValidationError(
|
|
931
|
+
message=f"Branch {branch_id} is not a published release",
|
|
932
|
+
code="DXS-VAL-002",
|
|
933
|
+
suggestions=[
|
|
934
|
+
"baseline is only meaningful for PublishedMain branches",
|
|
935
|
+
"Use `dxs source branch show <id>` to inspect a branch's status",
|
|
936
|
+
],
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
ctx.log(f"Fetching PublishedMain branches in group {group_id}...")
|
|
940
|
+
|
|
941
|
+
# Fetch all PublishedMain branches in the same application group. The list
|
|
942
|
+
# endpoint includes releaseDate (the single-branch GET does not), so we use
|
|
943
|
+
# releaseDate when present and fall back to createdDate otherwise.
|
|
944
|
+
group_releases = client.get(*BranchEndpoints.list(group_id, [int(BranchStatus.PUBLISHED_MAIN)]))
|
|
945
|
+
if not isinstance(group_releases, list):
|
|
946
|
+
group_releases = [group_releases] if group_releases else []
|
|
947
|
+
|
|
948
|
+
def _sort_key(branch: dict) -> str:
|
|
949
|
+
return branch.get("releaseDate") or branch.get("createdDate") or ""
|
|
833
950
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
client, repo_id, [inactive_status_id] if inactive_status_id else None
|
|
951
|
+
target_key = next(
|
|
952
|
+
(_sort_key(b) for b in group_releases if b.get("id") == branch_id),
|
|
953
|
+
"",
|
|
838
954
|
)
|
|
955
|
+
if not target_key:
|
|
956
|
+
raise ValidationError(
|
|
957
|
+
message=f"Branch {branch_id} has no releaseDate or createdDate in group listing",
|
|
958
|
+
code="DXS-VAL-003",
|
|
959
|
+
)
|
|
960
|
+
|
|
961
|
+
# Keep only releases older than the target, sorted desc by the same key.
|
|
962
|
+
older = [
|
|
963
|
+
b
|
|
964
|
+
for b in group_releases
|
|
965
|
+
if b.get("id") != branch_id and _sort_key(b) and _sort_key(b) < target_key
|
|
966
|
+
]
|
|
839
967
|
|
|
840
|
-
if not
|
|
968
|
+
if not older:
|
|
841
969
|
ctx.output(
|
|
842
970
|
single(
|
|
843
|
-
item={"message": "No baseline found (no
|
|
971
|
+
item={"message": "No baseline found (no prior release in this group)"},
|
|
844
972
|
semantic_key="baseline",
|
|
845
973
|
branch_id=branch_id,
|
|
846
974
|
repository_id=repo_id,
|
|
975
|
+
application_group_id=group_id,
|
|
847
976
|
)
|
|
848
977
|
)
|
|
849
978
|
return
|
|
850
979
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
# Get the most recent inactive branch
|
|
855
|
-
baseline_branch = inactive_branches[0]
|
|
856
|
-
|
|
857
|
-
# Enrich with status info
|
|
858
|
-
baseline_branch = enrich_branch_with_status(baseline_branch)
|
|
980
|
+
older.sort(key=_sort_key, reverse=True)
|
|
981
|
+
baseline_branch = enrich_branch_with_status(older[0])
|
|
859
982
|
|
|
860
983
|
ctx.output(
|
|
861
984
|
single(
|
|
@@ -863,6 +986,7 @@ def baseline(ctx: DxsContext, branch_id: int) -> None:
|
|
|
863
986
|
semantic_key="baseline",
|
|
864
987
|
target_branch_id=branch_id,
|
|
865
988
|
repository_id=repo_id,
|
|
989
|
+
application_group_id=group_id,
|
|
866
990
|
)
|
|
867
991
|
)
|
|
868
992
|
|