datex-studio-cli 0.4.4__tar.gz → 0.4.5__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.4 → datex_studio_cli-0.4.5}/PKG-INFO +1 -1
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/pyproject.toml +1 -1
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/__init__.py +1 -1
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/cli.py +25 -16
- datex_studio_cli-0.4.5/src/dxs/utils/update_check.py +208 -0
- datex_studio_cli-0.4.4/src/dxs/utils/update_check.py +0 -310
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/.gitignore +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/README.md +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/report-creator.skill +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/scripts/analyze_corpus.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/scripts/generate_rdlx_schema.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/__main__.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/__init__.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/api.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/auth.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/branch.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/config.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/configuration.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/crm.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/datasource.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/devops.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/document.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/endpoint.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/env.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/explore.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/function.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/marketplace.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/odata.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/organization.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/proxy.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/release_notes.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/repo.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/report/__init__.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/report/add_cmds.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/report/api.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/report/batch.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/report/create.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/report/data.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/report/dataset.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/report/edit.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/report/folder.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/report/preview.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/report/schema_cmds.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/schema.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/servicepack.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/source.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/studio.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/telemetry.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/commands/user.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/context.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/__init__.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/api/__init__.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/api/app_config.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/api/client.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/api/endpoints.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/api/metadata_models.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/api/models.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/auth/__init__.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/auth/decorators.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/auth/msal_client.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/auth/token_cache.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/cache.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/datasource/__init__.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/datasource/flow_generator.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/datasource/flow_models.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/datasource/generator.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/datasource/models.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/datasource/parsers.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/datasource/resolver.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/datasource/type_def_parser.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/datasource/validator.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/designer/__init__.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/designer/flow_models.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/designer/models.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/designer/parsers.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/devops/__init__.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/devops/client.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/devops/helpers.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/devops/models.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/dynamics/__init__.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/dynamics/client.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/dynamics/metadata_cache.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/footprint/__init__.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/footprint/client.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/footprint/edmx_parser.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/footprint/metadata.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/function/__init__.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/function/generator.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/function/models.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/function/validator.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/graph.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/output/__init__.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/output/csv_fmt.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/output/formatter.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/output/json_fmt.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/output/redaction.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/output/yaml_fmt.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/release_notes.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/responses.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/telemetry/__init__.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/telemetry/config.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/telemetry/identity.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/telemetry/recorder.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/telemetry/scrubber.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/core/telemetry/sender.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/models/__init__.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/report/__init__.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/report/datasource_binding.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/report/engine.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/report/field_parser.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/report/folder.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/report/manifest.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/report/models.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/report/preview.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/report/preview_arjs.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/report/schema.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/report/svg_renderer.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/report/templates/__init__.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/report/templates/bill_of_lading.rdlx-json +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/report/templates/shipping_label.rdlx-json +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/report/validate_arjs.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/report/validator.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/report/wrapper.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/utils/__init__.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/utils/click_options.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/utils/config.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/utils/errors.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/utils/filtering.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/utils/image.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/utils/paths.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/utils/resolvers.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/utils/responses.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/utils/restricted.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/utils/sorting.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/__init__.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/app.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/design.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/proxy_app.py +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/404/index.html +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/404.html +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/UgyAMtPjH-Pt0RYWOHzIT/_buildManifest.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/UgyAMtPjH-Pt0RYWOHzIT/_ssgManifest.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/chunks/0b24cca5-c1e1c8810348f107.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/chunks/255-102f2e5b2e3dc2ef.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/chunks/538-84e2e111415f1fda.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/chunks/697-0e000ab410d9f470.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/chunks/871-1acacdb7b4022abf.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/chunks/8b8f67fc-6295949a4b5485a4.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/chunks/909-c88abba3cf0caec3.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/chunks/app/_not-found/page-ea1be7001c230704.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/chunks/app/design/capture/page-a5e129c2d223432c.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/chunks/app/design/page-21ba66a3dd3b03f0.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/chunks/app/layout-2cc6eac09e476c91.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/chunks/app/page-20c358395882cdac.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/chunks/framework-de98b93a850cfc71.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/chunks/main-0f18f91200dac003.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/chunks/main-app-b69998d8941231d8.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/chunks/pages/_app-7d307437aca18ad4.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/chunks/webpack-2b297ada5306c17f.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/_next/static/css/360c657de5e04df8.css +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/design/capture/index.html +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/design/capture/index.txt +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/design/index.html +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/design/index.txt +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/index.html +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/index.txt +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/render-cli.js +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/report-preview.html +0 -0
- {datex_studio_cli-0.4.4 → datex_studio_cli-0.4.5}/src/dxs/web/static/report-validate.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datex-studio-cli
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.5
|
|
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
|
|
@@ -507,18 +507,20 @@ def _ctx_extras(dxs_ctx: "DxsContext | None") -> dict[str, Any]:
|
|
|
507
507
|
|
|
508
508
|
|
|
509
509
|
@cli.command("update")
|
|
510
|
-
|
|
511
|
-
"
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
510
|
+
def update_cmd() -> None:
|
|
511
|
+
"""Check PyPI for a newer release and print the upgrade command.
|
|
512
|
+
|
|
513
|
+
This command never performs the upgrade itself — run the printed
|
|
514
|
+
command in a fresh shell. This avoids the running-binary file-lock
|
|
515
|
+
issues that any in-process upgrade hits on Windows.
|
|
516
|
+
"""
|
|
517
|
+
import time
|
|
518
|
+
|
|
519
519
|
from dxs.utils.update_check import (
|
|
520
|
+
PACKAGE_NAME,
|
|
520
521
|
_fetch_latest_version,
|
|
521
|
-
|
|
522
|
+
_read_cache,
|
|
523
|
+
_write_cache,
|
|
522
524
|
detect_upgrade_command,
|
|
523
525
|
is_newer,
|
|
524
526
|
)
|
|
@@ -536,12 +538,19 @@ def update_cmd(check_only: bool) -> None:
|
|
|
536
538
|
return
|
|
537
539
|
|
|
538
540
|
upgrade_cmd = detect_upgrade_command()
|
|
539
|
-
click.echo(
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
541
|
+
click.echo("", err=True)
|
|
542
|
+
click.echo(f" A new release of dxs is available: {__version__} → {latest}", err=True)
|
|
543
|
+
click.echo(f" To upgrade, run: {upgrade_cmd}", err=True)
|
|
544
|
+
click.echo(f" https://pypi.org/project/{PACKAGE_NAME}/{latest}/", err=True)
|
|
545
|
+
|
|
546
|
+
# Refresh the cache and suppress the auto-banner for the next dedupe
|
|
547
|
+
# window — the user has explicitly seen the news.
|
|
548
|
+
now = time.time()
|
|
549
|
+
cache = _read_cache() or {}
|
|
550
|
+
cache["latest"] = latest
|
|
551
|
+
cache["checked_at"] = now
|
|
552
|
+
cache["last_notified_at"] = now
|
|
553
|
+
_write_cache(cache)
|
|
545
554
|
|
|
546
555
|
|
|
547
556
|
# Register commands when module loads
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""PyPI update notifier.
|
|
2
|
+
|
|
3
|
+
After each command, if the on-disk cache says a newer version exists on
|
|
4
|
+
PyPI, print a one-time banner to stderr telling the user how to upgrade.
|
|
5
|
+
The actual upgrade is the user's responsibility — we never exec, spawn,
|
|
6
|
+
or otherwise touch the running install. This sidesteps every file-lock,
|
|
7
|
+
process-replacement, and platform-specific upgrade problem.
|
|
8
|
+
|
|
9
|
+
The PyPI fetch happens in a background daemon thread, so a stale cache is
|
|
10
|
+
refreshed for *next* time. The current run never blocks on the network.
|
|
11
|
+
|
|
12
|
+
Environment / flag controls:
|
|
13
|
+
- ``DXS_NO_UPDATE_CHECK=1`` disables both the check and the banner.
|
|
14
|
+
- ``--no-update-check`` (CLI flag) does the same per-invocation.
|
|
15
|
+
- ``DXS_UPDATE_CHECK_DEBUG=1`` surfaces sender errors (development only).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
import sys
|
|
24
|
+
import threading
|
|
25
|
+
import time
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
import click
|
|
30
|
+
|
|
31
|
+
from dxs import __version__
|
|
32
|
+
from dxs.utils.paths import get_datex_home
|
|
33
|
+
|
|
34
|
+
PYPI_URL = "https://pypi.org/pypi/datex-studio-cli/json"
|
|
35
|
+
PACKAGE_NAME = "datex-studio-cli"
|
|
36
|
+
CACHE_TTL_SECONDS = 24 * 60 * 60
|
|
37
|
+
NOTIFY_DEDUPE_SECONDS = 60 * 60 # at most one banner per hour per machine
|
|
38
|
+
HTTP_TIMEOUT_SECONDS = 2.0
|
|
39
|
+
|
|
40
|
+
_CI_ENV_VARS = ("CI", "CONTINUOUS_INTEGRATION", "GITHUB_ACTIONS", "BUILDKITE", "TF_BUILD")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _cache_path() -> Path:
|
|
44
|
+
return get_datex_home() / "update_check.json"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _debug(msg: str) -> None:
|
|
48
|
+
if os.environ.get("DXS_UPDATE_CHECK_DEBUG", "").lower() in ("1", "true", "yes"):
|
|
49
|
+
click.echo(f"[update-check] {msg}", err=True)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def is_disabled() -> bool:
|
|
53
|
+
"""Return True if the update check has been disabled via env var."""
|
|
54
|
+
return os.environ.get("DXS_NO_UPDATE_CHECK", "").lower() in ("1", "true", "yes")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _is_ci() -> bool:
|
|
58
|
+
return any(os.environ.get(v) for v in _CI_ENV_VARS)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _read_cache() -> dict[str, Any] | None:
|
|
62
|
+
path = _cache_path()
|
|
63
|
+
if not path.exists():
|
|
64
|
+
return None
|
|
65
|
+
try:
|
|
66
|
+
data = json.loads(path.read_text())
|
|
67
|
+
if isinstance(data, dict):
|
|
68
|
+
return data
|
|
69
|
+
except (json.JSONDecodeError, OSError) as e:
|
|
70
|
+
_debug(f"cache read failed: {e}")
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _write_cache(data: dict[str, Any]) -> None:
|
|
75
|
+
try:
|
|
76
|
+
_cache_path().write_text(json.dumps(data))
|
|
77
|
+
except OSError as e:
|
|
78
|
+
_debug(f"cache write failed: {e}")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _fetch_latest_version() -> str | None:
|
|
82
|
+
"""Fetch the latest version from PyPI. Returns None on any failure."""
|
|
83
|
+
try:
|
|
84
|
+
import httpx
|
|
85
|
+
|
|
86
|
+
response = httpx.get(PYPI_URL, timeout=HTTP_TIMEOUT_SECONDS)
|
|
87
|
+
response.raise_for_status()
|
|
88
|
+
latest = response.json()["info"]["version"]
|
|
89
|
+
return str(latest) if latest else None
|
|
90
|
+
except Exception as e: # noqa: BLE001 - never let telemetry break the CLI
|
|
91
|
+
_debug(f"fetch failed: {e}")
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _refresh_cache_worker() -> None:
|
|
96
|
+
latest = _fetch_latest_version()
|
|
97
|
+
if not latest:
|
|
98
|
+
return
|
|
99
|
+
cache = _read_cache() or {}
|
|
100
|
+
cache["latest"] = latest
|
|
101
|
+
cache["checked_at"] = time.time()
|
|
102
|
+
_write_cache(cache)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _maybe_refresh_async(cache: dict[str, Any] | None) -> None:
|
|
106
|
+
"""Spawn a daemon thread to refresh the cache if it's missing or stale."""
|
|
107
|
+
needs_refresh = (
|
|
108
|
+
cache is None or (time.time() - float(cache.get("checked_at", 0))) > CACHE_TTL_SECONDS
|
|
109
|
+
)
|
|
110
|
+
if not needs_refresh:
|
|
111
|
+
return
|
|
112
|
+
thread = threading.Thread(target=_refresh_cache_worker, daemon=True, name="dxs-update-check")
|
|
113
|
+
thread.start()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
_VERSION_RE = re.compile(r"^(\d+)(?:\.(\d+))?(?:\.(\d+))?")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _parse_version(v: str) -> tuple[int, int, int]:
|
|
120
|
+
"""Parse an X.Y.Z version into a tuple. Pre-release suffixes are ignored.
|
|
121
|
+
|
|
122
|
+
Returns (0, 0, 0) on unparseable input — treated as oldest possible.
|
|
123
|
+
"""
|
|
124
|
+
m = _VERSION_RE.match(v.strip())
|
|
125
|
+
if not m:
|
|
126
|
+
return (0, 0, 0)
|
|
127
|
+
return (int(m.group(1) or 0), int(m.group(2) or 0), int(m.group(3) or 0))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def is_newer(candidate: str, current: str) -> bool:
|
|
131
|
+
"""Return True if `candidate` is a newer version than `current`."""
|
|
132
|
+
return _parse_version(candidate) > _parse_version(current)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def detect_upgrade_command() -> str:
|
|
136
|
+
"""Best-effort detection of how dxs was installed.
|
|
137
|
+
|
|
138
|
+
Returns the shell command the user should run to upgrade.
|
|
139
|
+
"""
|
|
140
|
+
exe = sys.executable
|
|
141
|
+
# uv tool installs land under ~/.local/share/uv/tools/<pkg>/...
|
|
142
|
+
if "/uv/tools/" in exe or "\\uv\\tools\\" in exe:
|
|
143
|
+
return f"uv tool upgrade {PACKAGE_NAME}"
|
|
144
|
+
# pipx installs land under ~/.local/pipx/venvs/<pkg>/...
|
|
145
|
+
if "/pipx/" in exe or "\\pipx\\" in exe:
|
|
146
|
+
return f"pipx upgrade {PACKAGE_NAME}"
|
|
147
|
+
# Fallback: pip in whatever interpreter the user is on
|
|
148
|
+
return f"{exe} -m pip install --upgrade {PACKAGE_NAME}"
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _stderr_is_tty() -> bool:
|
|
152
|
+
"""True when stderr is attached to a TTY.
|
|
153
|
+
|
|
154
|
+
Only stderr matters — the banner is written there. Piping stdout (e.g.
|
|
155
|
+
``dxs odata ... | jq``) leaves stderr untouched, so the user still sees
|
|
156
|
+
the banner in their terminal. Redirecting stderr (``2>log``) suppresses
|
|
157
|
+
it, as intended.
|
|
158
|
+
"""
|
|
159
|
+
return sys.stderr.isatty()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _print_banner(latest: str, upgrade_cmd: str) -> None:
|
|
163
|
+
"""Print the update banner to stderr."""
|
|
164
|
+
click.echo("", err=True)
|
|
165
|
+
click.echo(
|
|
166
|
+
f" A new release of dxs is available: {__version__} → {latest}",
|
|
167
|
+
err=True,
|
|
168
|
+
)
|
|
169
|
+
click.echo(f" To upgrade, run: {upgrade_cmd}", err=True)
|
|
170
|
+
click.echo(f" https://pypi.org/project/{PACKAGE_NAME}/{latest}/", err=True)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def maybe_show_update_notice(skip: bool = False) -> None:
|
|
174
|
+
"""If the cache shows a newer version, print a one-time banner to stderr.
|
|
175
|
+
|
|
176
|
+
Always returns quickly — the PyPI fetch happens in a background thread
|
|
177
|
+
that only writes to the cache. The first run on a stale cache prints
|
|
178
|
+
nothing; the next run sees the refreshed cache and prints the banner.
|
|
179
|
+
Dedupe keeps it to one banner per ``NOTIFY_DEDUPE_SECONDS`` window so
|
|
180
|
+
fast-fire commands don't spam.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
skip: If True, skip both the check and any cache refresh. Used to
|
|
184
|
+
honor ``--no-update-check``.
|
|
185
|
+
"""
|
|
186
|
+
if skip or is_disabled() or _is_ci():
|
|
187
|
+
return
|
|
188
|
+
if not _stderr_is_tty():
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
cache = _read_cache()
|
|
192
|
+
_maybe_refresh_async(cache)
|
|
193
|
+
|
|
194
|
+
if not cache:
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
latest = cache.get("latest")
|
|
198
|
+
if not isinstance(latest, str) or not is_newer(latest, __version__):
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
last_notified = float(cache.get("last_notified_at", 0))
|
|
202
|
+
if time.time() - last_notified < NOTIFY_DEDUPE_SECONDS:
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
_print_banner(latest, detect_upgrade_command())
|
|
206
|
+
|
|
207
|
+
cache["last_notified_at"] = time.time()
|
|
208
|
+
_write_cache(cache)
|
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
"""PyPI update check.
|
|
2
|
-
|
|
3
|
-
On each invocation, reads a cached "latest version on PyPI" entry. If a newer
|
|
4
|
-
version is available and we're attached to an interactive terminal, prints a
|
|
5
|
-
banner and offers to upgrade in place. The PyPI fetch happens in a background
|
|
6
|
-
daemon thread so the user's command is never delayed.
|
|
7
|
-
|
|
8
|
-
Environment / flag controls:
|
|
9
|
-
- ``DXS_NO_UPDATE_CHECK=1`` disables both the check and the banner.
|
|
10
|
-
- ``--no-update-check`` (CLI flag) does the same per-invocation.
|
|
11
|
-
- ``DXS_UPDATE_CHECK_DEBUG=1`` surfaces sender errors (development only).
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
|
-
import json
|
|
17
|
-
import os
|
|
18
|
-
import platform
|
|
19
|
-
import re
|
|
20
|
-
import shlex
|
|
21
|
-
import subprocess
|
|
22
|
-
import sys
|
|
23
|
-
import tempfile
|
|
24
|
-
import threading
|
|
25
|
-
import time
|
|
26
|
-
from pathlib import Path
|
|
27
|
-
from typing import Any
|
|
28
|
-
|
|
29
|
-
import click
|
|
30
|
-
|
|
31
|
-
from dxs import __version__
|
|
32
|
-
from dxs.utils.paths import get_datex_home
|
|
33
|
-
|
|
34
|
-
PYPI_URL = "https://pypi.org/pypi/datex-studio-cli/json"
|
|
35
|
-
PACKAGE_NAME = "datex-studio-cli"
|
|
36
|
-
CACHE_TTL_SECONDS = 24 * 60 * 60
|
|
37
|
-
PROMPT_DEDUPE_SECONDS = 60 * 60 # at most one prompt per hour per machine
|
|
38
|
-
HTTP_TIMEOUT_SECONDS = 2.0
|
|
39
|
-
|
|
40
|
-
_CI_ENV_VARS = ("CI", "CONTINUOUS_INTEGRATION", "GITHUB_ACTIONS", "BUILDKITE", "TF_BUILD")
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _cache_path() -> Path:
|
|
44
|
-
return get_datex_home() / "update_check.json"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def _debug(msg: str) -> None:
|
|
48
|
-
if os.environ.get("DXS_UPDATE_CHECK_DEBUG", "").lower() in ("1", "true", "yes"):
|
|
49
|
-
click.echo(f"[update-check] {msg}", err=True)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def is_disabled() -> bool:
|
|
53
|
-
"""Return True if the update check has been disabled via env var."""
|
|
54
|
-
return os.environ.get("DXS_NO_UPDATE_CHECK", "").lower() in ("1", "true", "yes")
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def _is_ci() -> bool:
|
|
58
|
-
return any(os.environ.get(v) for v in _CI_ENV_VARS)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _read_cache() -> dict[str, Any] | None:
|
|
62
|
-
path = _cache_path()
|
|
63
|
-
if not path.exists():
|
|
64
|
-
return None
|
|
65
|
-
try:
|
|
66
|
-
data = json.loads(path.read_text())
|
|
67
|
-
if isinstance(data, dict):
|
|
68
|
-
return data
|
|
69
|
-
except (json.JSONDecodeError, OSError) as e:
|
|
70
|
-
_debug(f"cache read failed: {e}")
|
|
71
|
-
return None
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def _write_cache(data: dict[str, Any]) -> None:
|
|
75
|
-
try:
|
|
76
|
-
_cache_path().write_text(json.dumps(data))
|
|
77
|
-
except OSError as e:
|
|
78
|
-
_debug(f"cache write failed: {e}")
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def _fetch_latest_version() -> str | None:
|
|
82
|
-
"""Fetch the latest version from PyPI. Returns None on any failure."""
|
|
83
|
-
try:
|
|
84
|
-
import httpx
|
|
85
|
-
|
|
86
|
-
response = httpx.get(PYPI_URL, timeout=HTTP_TIMEOUT_SECONDS)
|
|
87
|
-
response.raise_for_status()
|
|
88
|
-
latest = response.json()["info"]["version"]
|
|
89
|
-
return str(latest) if latest else None
|
|
90
|
-
except Exception as e: # noqa: BLE001 - never let telemetry break the CLI
|
|
91
|
-
_debug(f"fetch failed: {e}")
|
|
92
|
-
return None
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def _refresh_cache_worker() -> None:
|
|
96
|
-
latest = _fetch_latest_version()
|
|
97
|
-
if not latest:
|
|
98
|
-
return
|
|
99
|
-
cache = _read_cache() or {}
|
|
100
|
-
cache["latest"] = latest
|
|
101
|
-
cache["checked_at"] = time.time()
|
|
102
|
-
_write_cache(cache)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def _maybe_refresh_async(cache: dict[str, Any] | None) -> None:
|
|
106
|
-
"""Spawn a daemon thread to refresh the cache if it's missing or stale."""
|
|
107
|
-
needs_refresh = (
|
|
108
|
-
cache is None or (time.time() - float(cache.get("checked_at", 0))) > CACHE_TTL_SECONDS
|
|
109
|
-
)
|
|
110
|
-
if not needs_refresh:
|
|
111
|
-
return
|
|
112
|
-
thread = threading.Thread(target=_refresh_cache_worker, daemon=True, name="dxs-update-check")
|
|
113
|
-
thread.start()
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
_VERSION_RE = re.compile(r"^(\d+)(?:\.(\d+))?(?:\.(\d+))?")
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def _parse_version(v: str) -> tuple[int, int, int]:
|
|
120
|
-
"""Parse an X.Y.Z version into a tuple. Pre-release suffixes are ignored.
|
|
121
|
-
|
|
122
|
-
Returns (0, 0, 0) on unparseable input — treated as oldest possible.
|
|
123
|
-
"""
|
|
124
|
-
m = _VERSION_RE.match(v.strip())
|
|
125
|
-
if not m:
|
|
126
|
-
return (0, 0, 0)
|
|
127
|
-
return (int(m.group(1) or 0), int(m.group(2) or 0), int(m.group(3) or 0))
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def is_newer(candidate: str, current: str) -> bool:
|
|
131
|
-
"""Return True if `candidate` is a newer version than `current`."""
|
|
132
|
-
return _parse_version(candidate) > _parse_version(current)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def detect_upgrade_command() -> str:
|
|
136
|
-
"""Best-effort detection of how dxs was installed.
|
|
137
|
-
|
|
138
|
-
Returns the shell command the user should run to upgrade.
|
|
139
|
-
"""
|
|
140
|
-
exe = sys.executable
|
|
141
|
-
# uv tool installs land under ~/.local/share/uv/tools/<pkg>/...
|
|
142
|
-
if "/uv/tools/" in exe or "\\uv\\tools\\" in exe:
|
|
143
|
-
return f"uv tool upgrade {PACKAGE_NAME}"
|
|
144
|
-
# pipx installs land under ~/.local/pipx/venvs/<pkg>/...
|
|
145
|
-
if "/pipx/" in exe or "\\pipx\\" in exe:
|
|
146
|
-
return f"pipx upgrade {PACKAGE_NAME}"
|
|
147
|
-
# Fallback: pip in whatever interpreter the user is on
|
|
148
|
-
return f"{exe} -m pip install --upgrade {PACKAGE_NAME}"
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def _run_upgrade(command: str) -> int:
|
|
152
|
-
"""Hand off to the upgrade command, tearing down this process first.
|
|
153
|
-
|
|
154
|
-
POSIX uses ``os.execvp``, which truly replaces the process image and
|
|
155
|
-
frees all file handles before the upgrader (uv/pipx/pip) starts rewriting
|
|
156
|
-
the venv.
|
|
157
|
-
|
|
158
|
-
Windows is different: ``os.execvp`` there spawns a child and exits, but
|
|
159
|
-
Windows holds an exclusive image-file lock on the running ``dxs.exe``
|
|
160
|
-
launcher (e.g. ``~/.local/bin/dxs.exe``) until the launcher process fully
|
|
161
|
-
terminates — uv races ahead and fails to overwrite the entry-point shim
|
|
162
|
-
with "os error 32: file in use". So on Windows we write a small batch
|
|
163
|
-
helper that waits briefly for the launcher to exit, then runs the
|
|
164
|
-
upgrade in a fresh console window, and we exit immediately to release
|
|
165
|
-
the lock.
|
|
166
|
-
|
|
167
|
-
On success this function does not return. Returns 1 only when the
|
|
168
|
-
upgrade fails to start.
|
|
169
|
-
"""
|
|
170
|
-
command = command.strip()
|
|
171
|
-
if not command:
|
|
172
|
-
click.echo("Upgrade command was empty.", err=True)
|
|
173
|
-
return 1
|
|
174
|
-
|
|
175
|
-
click.echo(f"Running: {command}", err=True)
|
|
176
|
-
try:
|
|
177
|
-
sys.stdout.flush()
|
|
178
|
-
sys.stderr.flush()
|
|
179
|
-
except Exception: # noqa: BLE001
|
|
180
|
-
pass
|
|
181
|
-
|
|
182
|
-
if platform.system() == "Windows":
|
|
183
|
-
return _spawn_windows_upgrade(command)
|
|
184
|
-
|
|
185
|
-
args = shlex.split(command)
|
|
186
|
-
try:
|
|
187
|
-
os.execvp(args[0], args)
|
|
188
|
-
except OSError as e:
|
|
189
|
-
click.echo(f"Upgrade failed to start: {e}", err=True)
|
|
190
|
-
return 1
|
|
191
|
-
return 1 # unreachable on success
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
def _spawn_windows_upgrade(command: str) -> int:
|
|
195
|
-
"""Spawn the upgrade in a new console window detached from this process.
|
|
196
|
-
|
|
197
|
-
Writes a temporary ``.cmd`` file that waits ~3 seconds for ``dxs.exe`` to
|
|
198
|
-
fully terminate (releasing Windows' image-file lock on the launcher),
|
|
199
|
-
then runs the upgrade with one short retry if the first attempt still
|
|
200
|
-
hits a file-in-use error. The current process exits immediately so the
|
|
201
|
-
lock is released as quickly as possible.
|
|
202
|
-
"""
|
|
203
|
-
script = (
|
|
204
|
-
"@echo off\r\n"
|
|
205
|
-
"echo Waiting for dxs to exit so its files can be replaced...\r\n"
|
|
206
|
-
# ping -n N sleeps roughly (N-1) seconds; portable across all
|
|
207
|
-
# Windows versions where `timeout` may not be available.
|
|
208
|
-
"ping -n 4 127.0.0.1 > nul\r\n"
|
|
209
|
-
f"echo Running: {command}\r\n"
|
|
210
|
-
f"{command}\r\n"
|
|
211
|
-
"if %ERRORLEVEL% NEQ 0 (\r\n"
|
|
212
|
-
" echo.\r\n"
|
|
213
|
-
" echo First attempt failed; waiting and retrying once...\r\n"
|
|
214
|
-
" ping -n 6 127.0.0.1 > nul\r\n"
|
|
215
|
-
f" {command}\r\n"
|
|
216
|
-
")\r\n"
|
|
217
|
-
"echo.\r\n"
|
|
218
|
-
"if %ERRORLEVEL% NEQ 0 (echo Upgrade failed.) else (echo Upgrade complete.)\r\n"
|
|
219
|
-
"echo.\r\n"
|
|
220
|
-
"pause\r\n"
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
try:
|
|
224
|
-
fd, script_path = tempfile.mkstemp(prefix="dxs-upgrade-", suffix=".cmd")
|
|
225
|
-
with os.fdopen(fd, "w", newline="") as f:
|
|
226
|
-
f.write(script)
|
|
227
|
-
except OSError as e:
|
|
228
|
-
click.echo(f"Failed to write upgrade helper script: {e}", err=True)
|
|
229
|
-
return 1
|
|
230
|
-
|
|
231
|
-
click.echo(
|
|
232
|
-
"Spawning the upgrade in a new console window so dxs can exit and release its files.",
|
|
233
|
-
err=True,
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
CREATE_NEW_CONSOLE = 0x00000010
|
|
237
|
-
try:
|
|
238
|
-
subprocess.Popen(
|
|
239
|
-
["cmd.exe", "/c", script_path],
|
|
240
|
-
creationflags=CREATE_NEW_CONSOLE,
|
|
241
|
-
close_fds=True,
|
|
242
|
-
)
|
|
243
|
-
except OSError as e:
|
|
244
|
-
click.echo(f"Failed to spawn upgrade: {e}", err=True)
|
|
245
|
-
return 1
|
|
246
|
-
|
|
247
|
-
sys.exit(0)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
def _interactive() -> bool:
|
|
251
|
-
"""True only when stdin, stderr, and stdout are all attached to a TTY.
|
|
252
|
-
|
|
253
|
-
We require stdout TTY too, because piping/redirecting stdout is the strongest
|
|
254
|
-
signal that the user is consuming structured output programmatically and
|
|
255
|
-
won't appreciate a banner mid-stream.
|
|
256
|
-
"""
|
|
257
|
-
return all(s.isatty() for s in (sys.stdin, sys.stderr, sys.stdout))
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def maybe_show_update_notice(skip: bool = False) -> None:
|
|
261
|
-
"""Check the cache and, if a newer version is available, prompt to upgrade.
|
|
262
|
-
|
|
263
|
-
Always returns quickly — the network fetch happens in a background thread.
|
|
264
|
-
|
|
265
|
-
Args:
|
|
266
|
-
skip: If True, skip both the check and any cache refresh. Used to honor
|
|
267
|
-
``--no-update-check``.
|
|
268
|
-
"""
|
|
269
|
-
if skip or is_disabled() or _is_ci():
|
|
270
|
-
return
|
|
271
|
-
if not _interactive():
|
|
272
|
-
return
|
|
273
|
-
|
|
274
|
-
cache = _read_cache()
|
|
275
|
-
_maybe_refresh_async(cache)
|
|
276
|
-
|
|
277
|
-
if not cache:
|
|
278
|
-
return
|
|
279
|
-
|
|
280
|
-
latest = cache.get("latest")
|
|
281
|
-
if not isinstance(latest, str) or not is_newer(latest, __version__):
|
|
282
|
-
return
|
|
283
|
-
|
|
284
|
-
last_prompted = float(cache.get("last_prompted_at", 0))
|
|
285
|
-
if time.time() - last_prompted < PROMPT_DEDUPE_SECONDS:
|
|
286
|
-
return
|
|
287
|
-
|
|
288
|
-
upgrade_cmd = detect_upgrade_command()
|
|
289
|
-
click.echo("", err=True)
|
|
290
|
-
click.echo(
|
|
291
|
-
f" dxs {latest} is available (you have {__version__})",
|
|
292
|
-
err=True,
|
|
293
|
-
)
|
|
294
|
-
click.echo(f" Upgrade command: {upgrade_cmd}", err=True)
|
|
295
|
-
|
|
296
|
-
cache["last_prompted_at"] = time.time()
|
|
297
|
-
_write_cache(cache)
|
|
298
|
-
|
|
299
|
-
try:
|
|
300
|
-
if click.confirm(" Upgrade now?", default=False, err=True):
|
|
301
|
-
click.echo(
|
|
302
|
-
" After upgrade completes, re-run your dxs command to use the new version.",
|
|
303
|
-
err=True,
|
|
304
|
-
)
|
|
305
|
-
exit_code = _run_upgrade(upgrade_cmd)
|
|
306
|
-
# _run_upgrade only returns when the upgrade failed to start.
|
|
307
|
-
click.echo(f" Upgrade failed to start (code {exit_code}).", err=True)
|
|
308
|
-
except (click.Abort, EOFError):
|
|
309
|
-
click.echo("", err=True)
|
|
310
|
-
return
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|