doover-cli 0.5.2__tar.gz → 0.5.4__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.
- {doover_cli-0.5.2 → doover_cli-0.5.4}/PKG-INFO +1 -1
- {doover_cli-0.5.2 → doover_cli-0.5.4}/pyproject.toml +4 -1
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/__init__.py +2 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/apps/apps.py +25 -2
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/config_schema.py +17 -3
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/ui_schema.py +16 -2
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/test_apps.py +45 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/uv.lock +0 -1
- {doover_cli-0.5.2 → doover_cli-0.5.4}/.github/CONTRIBUTING.md +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/.github/ISSUE_TEMPLATE/bug-report.yaml +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/.github/workflows/lint-test.yml +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/.github/workflows/release-brew.yml +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/.github/workflows/release-pypi.yml +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/.github/workflows/release.yml +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/.gitignore +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/.pre-commit-config.yaml +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/AGENT.md +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/LICENSE +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/README.md +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/debian/changelog +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/debian/control +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/debian/rules +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/docs/user_stories.md +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/agent.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/api/__init__.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/api/auth.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/api/errors.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/api/session.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/apps/app_install.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/apps/device.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/apps/device_type.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/apps/tunnel.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/channel.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/colours.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/dda_logs.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/doover_config.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/grpc.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/login.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/renderer/__init__.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/renderer/_base.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/renderer/_basic.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/renderer/_default.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/renderer/_json.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/report.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/simulator.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/user.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/utils/__init__.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/utils/api.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/utils/apps.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/utils/context.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/utils/crud/__init__.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/utils/crud/commands.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/utils/crud/lookup.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/utils/crud/prompting.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/utils/crud/schema.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/utils/crud/values.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/utils/errors.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/utils/formatters.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/utils/misc.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/utils/parsers.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/utils/prompt.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/utils/sentry.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/utils/shell_commands.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/src/doover_cli/utils/state.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/__init__.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/conftest.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/test_agent.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/test_app_install.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/test_auth_integration.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/test_auth_unit.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/test_basic.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/test_crud_commands.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/test_crud_lookup.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/test_crud_prompting.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/test_crud_schema.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/test_crud_values.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/test_default_renderer.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/test_device.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/test_device_cli_integration.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/test_device_type.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/test_device_type_cli_integration.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/test_prompt.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/test_sentry.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/test_user.py +0 -0
- {doover_cli-0.5.2 → doover_cli-0.5.4}/tests/test_utils_apps.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "doover-cli"
|
|
3
|
-
|
|
3
|
+
dynamic = ["version"]
|
|
4
4
|
description = "CLI for Doover"
|
|
5
5
|
requires-python = ">=3.11"
|
|
6
6
|
dependencies = [
|
|
@@ -26,6 +26,9 @@ dependencies = [
|
|
|
26
26
|
requires = ["hatchling"]
|
|
27
27
|
build-backend = "hatchling.build"
|
|
28
28
|
|
|
29
|
+
[tool.hatch.version]
|
|
30
|
+
path = "src/doover_cli/__init__.py"
|
|
31
|
+
|
|
29
32
|
[project.scripts]
|
|
30
33
|
doover = "doover_cli:main"
|
|
31
34
|
|
|
@@ -938,6 +938,14 @@ def publish(
|
|
|
938
938
|
help="Upload the widget when publishing. Disable with --no-put-widget.",
|
|
939
939
|
),
|
|
940
940
|
] = True,
|
|
941
|
+
build_package: Annotated[
|
|
942
|
+
bool,
|
|
943
|
+
typer.Option(
|
|
944
|
+
help="Build package.zip (./build.sh) before uploading a processor. "
|
|
945
|
+
"Use --no-build-package to upload an existing package.zip -- e.g. "
|
|
946
|
+
"when publishing several apps that share a single build.",
|
|
947
|
+
),
|
|
948
|
+
] = True,
|
|
941
949
|
app_name: Annotated[
|
|
942
950
|
str | None,
|
|
943
951
|
typer.Option(
|
|
@@ -999,6 +1007,16 @@ def publish(
|
|
|
999
1007
|
|
|
1000
1008
|
client, renderer = get_state()
|
|
1001
1009
|
|
|
1010
|
+
# The publish endpoints are organisation-scoped: without the
|
|
1011
|
+
# X-Doover-Organisation header the control plane rejects the request with
|
|
1012
|
+
# 401 "X-Doover-Organisation header missing" for any non-staff account.
|
|
1013
|
+
# Pin the client to the app's organisation (staging-aware, resolved into the
|
|
1014
|
+
# payload above) so every publish sub-call - create/partial, widget upload,
|
|
1015
|
+
# processor source + version release - carries the header.
|
|
1016
|
+
publish_org_id = payload.get("organisation_id")
|
|
1017
|
+
if publish_org_id is not None:
|
|
1018
|
+
client.organisation_id = int(publish_org_id)
|
|
1019
|
+
|
|
1002
1020
|
rich.print(
|
|
1003
1021
|
f"Updating application on doover site ({_control_base_url() or 'unknown base URL'})...\n"
|
|
1004
1022
|
)
|
|
@@ -1062,8 +1080,13 @@ def publish(
|
|
|
1062
1080
|
rich.print("[green]Widget uploaded.[/green]")
|
|
1063
1081
|
|
|
1064
1082
|
if app_config.type in ("PRO", "REP", "INT"):
|
|
1065
|
-
|
|
1066
|
-
|
|
1083
|
+
if build_package:
|
|
1084
|
+
print("\nBuilding package.zip for upload...")
|
|
1085
|
+
shell_run("./build.sh", cwd=root_fp)
|
|
1086
|
+
else:
|
|
1087
|
+
print(
|
|
1088
|
+
"\nSkipping package.zip build (--no-build-package); using existing package.zip."
|
|
1089
|
+
)
|
|
1067
1090
|
print("Uploading package.zip to Doover...")
|
|
1068
1091
|
processor_response = _publish_processor_package(
|
|
1069
1092
|
client,
|
|
@@ -38,18 +38,25 @@ def export(
|
|
|
38
38
|
file_okay=True,
|
|
39
39
|
),
|
|
40
40
|
] = None,
|
|
41
|
+
app_name: Annotated[
|
|
42
|
+
str | None,
|
|
43
|
+
typer.Option(
|
|
44
|
+
help="Application name in doover_config.json. Required to pick one "
|
|
45
|
+
"non-interactively when the repo defines multiple apps.",
|
|
46
|
+
),
|
|
47
|
+
] = None,
|
|
41
48
|
):
|
|
42
49
|
"""Export the application configuration to the doover config json file."""
|
|
43
50
|
if config_fp is None:
|
|
44
51
|
root_fp = get_app_directory(app_fp)
|
|
45
|
-
app_config = get_app_config(root_fp)
|
|
52
|
+
app_config = get_app_config(root_fp, app_name=app_name)
|
|
46
53
|
call_with_uv(
|
|
47
54
|
app_config.export_config_command or "export-config",
|
|
48
55
|
in_shell=True,
|
|
49
56
|
cwd=app_fp,
|
|
50
57
|
)
|
|
51
58
|
else:
|
|
52
|
-
config = get_app_config(app_fp)
|
|
59
|
+
config = get_app_config(app_fp, app_name=app_name)
|
|
53
60
|
call_with_uv(config.src_directory / "app_config.py", in_shell=True)
|
|
54
61
|
|
|
55
62
|
print("Exporting application configuration...")
|
|
@@ -74,6 +81,13 @@ def validate(
|
|
|
74
81
|
"afterwards, so validation never writes to the working tree.",
|
|
75
82
|
),
|
|
76
83
|
] = True,
|
|
84
|
+
app_name: Annotated[
|
|
85
|
+
str | None,
|
|
86
|
+
typer.Option(
|
|
87
|
+
help="Application name in doover_config.json. Required to pick one "
|
|
88
|
+
"non-interactively when the repo defines multiple apps.",
|
|
89
|
+
),
|
|
90
|
+
] = None,
|
|
77
91
|
):
|
|
78
92
|
"""Validate application config is a valid JSON schema."""
|
|
79
93
|
root_fp = get_app_directory(app_fp)
|
|
@@ -83,7 +97,7 @@ def validate(
|
|
|
83
97
|
# Validate the schema the Python *currently* generates, without leaving
|
|
84
98
|
# doover_config.json modified (e.g. when run as a pre-commit hook).
|
|
85
99
|
with preserve_file(config_file):
|
|
86
|
-
ctx.invoke(export, ctx, app_fp=root_fp, validate_=False)
|
|
100
|
+
ctx.invoke(export, ctx, app_fp=root_fp, validate_=False, app_name=app_name)
|
|
87
101
|
_validate_config_file(config_file)
|
|
88
102
|
else:
|
|
89
103
|
_validate_config_file(config_file)
|
|
@@ -29,10 +29,17 @@ def export(
|
|
|
29
29
|
help="Validate the UI schema before exporting.",
|
|
30
30
|
),
|
|
31
31
|
] = True,
|
|
32
|
+
app_name: Annotated[
|
|
33
|
+
str | None,
|
|
34
|
+
typer.Option(
|
|
35
|
+
help="Application name in doover_config.json. Required to pick one "
|
|
36
|
+
"non-interactively when the repo defines multiple apps.",
|
|
37
|
+
),
|
|
38
|
+
] = None,
|
|
32
39
|
):
|
|
33
40
|
"""Export the application UI schema to the doover config json file."""
|
|
34
41
|
root_fp = get_app_directory(app_fp)
|
|
35
|
-
app_config = get_app_config(root_fp)
|
|
42
|
+
app_config = get_app_config(root_fp, app_name=app_name)
|
|
36
43
|
export_command = app_config.export_ui_command or "export-ui"
|
|
37
44
|
if export_command == "NO_EXPORT":
|
|
38
45
|
print("App requested no ui export. Skipping...")
|
|
@@ -66,6 +73,13 @@ def validate(
|
|
|
66
73
|
"afterwards, so validation never writes to the working tree.",
|
|
67
74
|
),
|
|
68
75
|
] = True,
|
|
76
|
+
app_name: Annotated[
|
|
77
|
+
str | None,
|
|
78
|
+
typer.Option(
|
|
79
|
+
help="Application name in doover_config.json. Required to pick one "
|
|
80
|
+
"non-interactively when the repo defines multiple apps.",
|
|
81
|
+
),
|
|
82
|
+
] = None,
|
|
69
83
|
):
|
|
70
84
|
"""Validate application UI schema is valid JSON."""
|
|
71
85
|
root_fp = get_app_directory(app_fp)
|
|
@@ -75,7 +89,7 @@ def validate(
|
|
|
75
89
|
# Validate the schema the Python *currently* generates, without leaving
|
|
76
90
|
# doover_config.json modified (e.g. when run as a pre-commit hook).
|
|
77
91
|
with preserve_file(config_file):
|
|
78
|
-
ctx.invoke(export, ctx, app_fp=root_fp, validate_=False)
|
|
92
|
+
ctx.invoke(export, ctx, app_fp=root_fp, validate_=False, app_name=app_name)
|
|
79
93
|
_validate_ui_file(config_file)
|
|
80
94
|
else:
|
|
81
95
|
_validate_ui_file(config_file)
|
|
@@ -491,6 +491,51 @@ def test_app_publish_updates_existing_application(monkeypatch, tmp_path):
|
|
|
491
491
|
assert renderer.render_calls == [{"id": 101, "published": True}]
|
|
492
492
|
|
|
493
493
|
|
|
494
|
+
def test_app_publish_pins_client_to_app_organisation(monkeypatch, tmp_path):
|
|
495
|
+
# Regression: publish must set the org context on the client so the
|
|
496
|
+
# X-Doover-Organisation header is sent. Without it the control plane
|
|
497
|
+
# returns 401 "X-Doover-Organisation header missing" for non-staff accounts.
|
|
498
|
+
captured = {}
|
|
499
|
+
renderer = FakeRenderer()
|
|
500
|
+
app_config = FakeAppConfig(app_id=101)
|
|
501
|
+
|
|
502
|
+
class FakeApplicationsClient:
|
|
503
|
+
@staticmethod
|
|
504
|
+
def partial(application_id, body):
|
|
505
|
+
captured["partial"] = (application_id, body)
|
|
506
|
+
return {"id": int(application_id), "published": True}
|
|
507
|
+
|
|
508
|
+
class FakeControlClient:
|
|
509
|
+
organisation_id = None
|
|
510
|
+
applications = FakeApplicationsClient()
|
|
511
|
+
|
|
512
|
+
client = FakeControlClient()
|
|
513
|
+
|
|
514
|
+
monkeypatch.setattr(
|
|
515
|
+
"doover_cli.apps.apps.get_app_directory", lambda root=None: tmp_path
|
|
516
|
+
)
|
|
517
|
+
monkeypatch.setattr(
|
|
518
|
+
"doover_cli.apps.apps.get_app_config", lambda root_fp, app_name=None: app_config
|
|
519
|
+
)
|
|
520
|
+
monkeypatch.setattr(
|
|
521
|
+
"doover_cli.apps.apps.get_state", lambda: (client, renderer)
|
|
522
|
+
)
|
|
523
|
+
monkeypatch.setattr(
|
|
524
|
+
"doover_cli.apps.apps.export_config_command",
|
|
525
|
+
lambda ctx, app_fp, validate_: None,
|
|
526
|
+
)
|
|
527
|
+
monkeypatch.setattr(
|
|
528
|
+
"doover_cli.apps.apps.export_ui_command",
|
|
529
|
+
lambda ctx, app_fp, validate_: None,
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
result = runner.invoke(app, ["app", "publish", str(tmp_path)])
|
|
533
|
+
|
|
534
|
+
assert result.exit_code == 0
|
|
535
|
+
assert client.organisation_id == 17
|
|
536
|
+
assert captured["partial"][0] == "101"
|
|
537
|
+
|
|
538
|
+
|
|
494
539
|
def test_app_publish_creates_then_updates_when_missing(monkeypatch, tmp_path):
|
|
495
540
|
captured = {}
|
|
496
541
|
renderer = FakeRenderer()
|
|
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
|
|
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
|
|
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
|