milo-cli 0.3.0__tar.gz → 0.3.1__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.
- {milo_cli-0.3.0 → milo_cli-0.3.1}/PKG-INFO +2 -2
- {milo_cli-0.3.0 → milo_cli-0.3.1}/pyproject.toml +2 -2
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/__init__.py +1 -1
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/templates/components/command_list.kida +1 -1
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/templates/components/help_page.kida +1 -1
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/templates/error.kida +5 -4
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo_cli.egg-info/PKG-INFO +2 -2
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo_cli.egg-info/SOURCES.txt +1 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo_cli.egg-info/requires.txt +1 -1
- milo_cli-0.3.1/tests/test_release_status.py +193 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_templates.py +52 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/README.md +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/setup.cfg +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/_cells.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/_child.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/_cli_help.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/_command_defs.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/_compat.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/_errors.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/_jsonrpc.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/_mcp_router.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/_protocols.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/_scaffold/__init__.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/_scaffold/default/README.md +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/_scaffold/default/app.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/_scaffold/default/conftest.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/_scaffold/default/tests/__init__.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/_scaffold/default/tests/test_app.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/_types.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/app.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/cli.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/commands.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/completions.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/components_cli.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/config.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/context.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/dev.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/doctor.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/flow.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/form.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/gateway.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/groups.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/help.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/input/__init__.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/input/_platform.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/input/_reader.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/input/_sequences.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/live.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/llms.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/mcp.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/middleware.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/observability.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/output.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/pipeline.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/plugins.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/py.typed +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/reducers.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/registry.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/schema.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/state.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/streaming.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/templates/__init__.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/templates/components/_defs.kida +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/templates/field_confirm.kida +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/templates/field_select.kida +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/templates/field_text.kida +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/templates/form.kida +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/templates/help.kida +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/templates/progress.kida +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/testing/__init__.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/testing/_mcp.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/testing/_record.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/testing/_replay.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/testing/_snapshot.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/theme.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/verify.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo/version_check.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo_cli.egg-info/dependency_links.txt +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo_cli.egg-info/entry_points.txt +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/src/milo_cli.egg-info/top_level.txt +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_ai_native.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_app.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_bubbletea_patterns.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_child.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_cli.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_command_contract.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_command_defs.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_commands_core.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_commands_middleware.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_compat.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_completions.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_components.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_config.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_context.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_dev.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_docs_information_architecture.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_docs_snippets.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_doctor.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_effects.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_effects_stress.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_errors.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_flow.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_form.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_gateway.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_groups.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_help.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_input.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_lazy.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_mcp_handler.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_mcp_prompts.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_mcp_resources.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_mcp_router.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_mcp_transport.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_middleware.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_migration_docs.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_milo_init.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_mount.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_observability.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_outputgallery_example.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_pipeline.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_plugins.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_protocols.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_readme_example_index.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_reducers.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_registry_crud.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_registry_v2.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_scaffold.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_schema_v2.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_state.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_streaming.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_testing.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_testing_mcp.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_theme.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_types.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_verify.py +0 -0
- {milo_cli-0.3.0 → milo_cli-0.3.1}/tests/test_version_check.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: milo-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Template-driven CLI applications for free-threaded Python
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://lbliii.github.io/milo-cli/
|
|
@@ -18,7 +18,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
18
18
|
Classifier: Typing :: Typed
|
|
19
19
|
Requires-Python: >=3.14
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
|
-
Requires-Dist: kida-templates<0.
|
|
21
|
+
Requires-Dist: kida-templates<0.10.0,>=0.9.0
|
|
22
22
|
Provides-Extra: docs
|
|
23
23
|
Requires-Dist: bengal>=0.3.2; extra == "docs"
|
|
24
24
|
Provides-Extra: yaml
|
|
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "milo-cli"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "Template-driven CLI applications for free-threaded Python"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.14"
|
|
11
11
|
license = "MIT"
|
|
12
|
-
dependencies = ["kida-templates>=0.
|
|
12
|
+
dependencies = ["kida-templates>=0.9.0,<0.10.0"]
|
|
13
13
|
keywords = ["cli", "terminal", "forms", "free-threading", "template", "elm"]
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Development Status :: 3 - Alpha",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
title -- optional section title (default: "Commands")
|
|
6
6
|
grouped -- optional bool, if true group by first tag (default: false)
|
|
7
7
|
-#}
|
|
8
|
-
{%- from "
|
|
8
|
+
{%- from "./_defs.kida" import section, command_row, tag_list -%}
|
|
9
9
|
|
|
10
10
|
{{ section(title | default("Commands")) }}
|
|
11
11
|
{% for cmd in commands %}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
examples -- list of example dicts: {command, description} (optional)
|
|
11
11
|
epilog -- footer text (optional)
|
|
12
12
|
-#}
|
|
13
|
-
{%- from "
|
|
13
|
+
{%- from "./_defs.kida" import header, section, command_row, example_block, def_list -%}
|
|
14
14
|
|
|
15
15
|
{% set version ??= "" %}
|
|
16
16
|
{% set description ??= "" %}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
{
|
|
1
|
+
{%- set error ??= "Unknown error" -%}
|
|
2
|
+
{%- set code ??= "" -%}
|
|
3
|
+
{%- set template_name ??= "" -%}
|
|
4
|
+
{%- set hint ??= "" -%}
|
|
5
|
+
{%- set docs_url ??= "" -%}
|
|
2
6
|
{{ "error" | red | bold }}{% if code %} {{ code | yellow }}{% endif %}
|
|
3
7
|
{{ error | dim }}
|
|
4
8
|
{% if template_name %} {{ "template:" | dim }} {{ template_name }}{% endif %}
|
|
5
9
|
{% if hint %} {{ "hint:" | cyan }} {{ hint }}{% endif %}
|
|
6
10
|
{% if docs_url %} {{ "docs:" | dim }} {{ docs_url }}{% endif %}
|
|
7
|
-
{% fallback %}
|
|
8
|
-
error: {{ error }}
|
|
9
|
-
{% end %}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: milo-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Template-driven CLI applications for free-threaded Python
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://lbliii.github.io/milo-cli/
|
|
@@ -18,7 +18,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
18
18
|
Classifier: Typing :: Typed
|
|
19
19
|
Requires-Python: >=3.14
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
|
-
Requires-Dist: kida-templates<0.
|
|
21
|
+
Requires-Dist: kida-templates<0.10.0,>=0.9.0
|
|
22
22
|
Provides-Extra: docs
|
|
23
23
|
Requires-Dist: bengal>=0.3.2; extra == "docs"
|
|
24
24
|
Provides-Extra: yaml
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Tests for scripts/release_status.py."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib.util
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _load_release_status_script():
|
|
11
|
+
script = Path(__file__).parent.parent / "scripts" / "release_status.py"
|
|
12
|
+
spec = importlib.util.spec_from_file_location("release_status", script)
|
|
13
|
+
assert spec is not None
|
|
14
|
+
assert spec.loader is not None
|
|
15
|
+
module = importlib.util.module_from_spec(spec)
|
|
16
|
+
sys.modules[spec.name] = module
|
|
17
|
+
spec.loader.exec_module(module)
|
|
18
|
+
return module
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _facts(**overrides):
|
|
22
|
+
release_status = _load_release_status_script()
|
|
23
|
+
base = {
|
|
24
|
+
"package_name": "milo-cli",
|
|
25
|
+
"pyproject_version": "0.3.1",
|
|
26
|
+
"module_version": "0.3.1",
|
|
27
|
+
"lock_version": "0.3.1",
|
|
28
|
+
"head_sha": "abc1234",
|
|
29
|
+
"local_tags": ("v0.3.0",),
|
|
30
|
+
"remote_tags": ("refs/tags/v0.3.0",),
|
|
31
|
+
"pypi_version": "",
|
|
32
|
+
"changelog_versions": ("0.3.1", "0.3.0"),
|
|
33
|
+
"release_note_versions": ("0.3.1", "0.3.0"),
|
|
34
|
+
"pending_fragments": (),
|
|
35
|
+
"empty_fragments": (),
|
|
36
|
+
"changed_since_current_tag": (),
|
|
37
|
+
"changed_released_notes": (),
|
|
38
|
+
"tag_diff_warnings": (),
|
|
39
|
+
"remote_checked": True,
|
|
40
|
+
"pypi_checked": False,
|
|
41
|
+
}
|
|
42
|
+
base.update(overrides)
|
|
43
|
+
return release_status.ReleaseFacts(**base)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TestReleaseStatus:
|
|
47
|
+
def test_allows_next_patch_after_released_tag(self):
|
|
48
|
+
release_status = _load_release_status_script()
|
|
49
|
+
|
|
50
|
+
report = release_status.analyze_release_state(_facts())
|
|
51
|
+
|
|
52
|
+
messages = [check.message for check in report.checks]
|
|
53
|
+
assert report.has_errors is False
|
|
54
|
+
assert report.latest_released_version == "0.3.0"
|
|
55
|
+
assert report.suggested_next_version == "0.3.1"
|
|
56
|
+
assert any("Current version 0.3.1 is newer" in msg for msg in messages)
|
|
57
|
+
|
|
58
|
+
def test_rejects_metadata_left_on_already_released_version(self):
|
|
59
|
+
release_status = _load_release_status_script()
|
|
60
|
+
|
|
61
|
+
report = release_status.analyze_release_state(
|
|
62
|
+
_facts(
|
|
63
|
+
pyproject_version="0.3.0",
|
|
64
|
+
module_version="0.3.0",
|
|
65
|
+
lock_version="0.3.0",
|
|
66
|
+
changelog_versions=("0.3.0",),
|
|
67
|
+
release_note_versions=("0.3.0",),
|
|
68
|
+
changed_since_current_tag=("CHANGELOG.md", "site/content/releases/0.3.0.md"),
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
messages = [check.message for check in report.checks]
|
|
73
|
+
assert report.has_errors is True
|
|
74
|
+
assert any("Version 0.3.0 is already released/tagged" in msg for msg in messages)
|
|
75
|
+
assert any("Release surfaces for already-tagged 0.3.0 changed" in msg for msg in messages)
|
|
76
|
+
assert report.suggested_next_version == "0.3.1"
|
|
77
|
+
|
|
78
|
+
def test_rejects_version_drift_between_metadata_files(self):
|
|
79
|
+
release_status = _load_release_status_script()
|
|
80
|
+
|
|
81
|
+
report = release_status.analyze_release_state(_facts(module_version="0.3.0"))
|
|
82
|
+
|
|
83
|
+
assert report.has_errors is True
|
|
84
|
+
assert any("disagrees with src/milo/__init__.py" in c.message for c in report.checks)
|
|
85
|
+
|
|
86
|
+
def test_rejects_version_already_on_pypi(self):
|
|
87
|
+
release_status = _load_release_status_script()
|
|
88
|
+
|
|
89
|
+
report = release_status.analyze_release_state(
|
|
90
|
+
_facts(
|
|
91
|
+
local_tags=(),
|
|
92
|
+
remote_tags=(),
|
|
93
|
+
pypi_version="0.3.1",
|
|
94
|
+
pypi_checked=True,
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
assert report.has_errors is True
|
|
99
|
+
assert any("Version 0.3.1 is already released/tagged" in c.message for c in report.checks)
|
|
100
|
+
|
|
101
|
+
def test_warns_about_pending_fragments(self):
|
|
102
|
+
release_status = _load_release_status_script()
|
|
103
|
+
|
|
104
|
+
report = release_status.analyze_release_state(
|
|
105
|
+
_facts(pending_fragments=("template-check.fixed.md",))
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
assert any(
|
|
109
|
+
check.level == "warn" and "Pending changelog fragments" in check.message
|
|
110
|
+
for check in report.checks
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def test_rejects_empty_changelog_fragments(self):
|
|
114
|
+
release_status = _load_release_status_script()
|
|
115
|
+
|
|
116
|
+
report = release_status.analyze_release_state(
|
|
117
|
+
_facts(
|
|
118
|
+
pending_fragments=("empty.fixed.md",),
|
|
119
|
+
empty_fragments=("empty.fixed.md",),
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
assert report.has_errors is True
|
|
124
|
+
assert any("Empty changelog fragments found" in c.message for c in report.checks)
|
|
125
|
+
|
|
126
|
+
def test_warns_when_prior_release_notes_changed(self):
|
|
127
|
+
release_status = _load_release_status_script()
|
|
128
|
+
|
|
129
|
+
report = release_status.analyze_release_state(
|
|
130
|
+
_facts(changed_released_notes=("site/content/releases/0.3.0.md",))
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
assert any(
|
|
134
|
+
check.level == "warn" and "Already-tagged release note files changed" in check.message
|
|
135
|
+
for check in report.checks
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def test_warns_when_tag_diff_cannot_be_checked(self):
|
|
139
|
+
release_status = _load_release_status_script()
|
|
140
|
+
|
|
141
|
+
report = release_status.analyze_release_state(
|
|
142
|
+
_facts(tag_diff_warnings=("Release tag v0.3.0 exists on origin but is not local.",))
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
assert any(
|
|
146
|
+
check.level == "warn" and "exists on origin" in check.message for check in report.checks
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def test_remote_only_current_tag_reports_missing_local_diff(self, tmp_path):
|
|
150
|
+
release_status = _load_release_status_script()
|
|
151
|
+
|
|
152
|
+
changed, warnings = release_status._read_changed_since_current_tag(
|
|
153
|
+
tmp_path,
|
|
154
|
+
"0.3.1",
|
|
155
|
+
(),
|
|
156
|
+
("refs/tags/v0.3.1",),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
assert changed == ()
|
|
160
|
+
assert warnings == (
|
|
161
|
+
"Release tag v0.3.1 exists on origin but is not available locally; "
|
|
162
|
+
"run with --fetch-tags to check release-surface drift.",
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def test_remote_only_historical_tag_reports_missing_local_diff(self, tmp_path):
|
|
166
|
+
release_status = _load_release_status_script()
|
|
167
|
+
note = tmp_path / "site" / "content" / "releases" / "0.3.0.md"
|
|
168
|
+
note.parent.mkdir(parents=True)
|
|
169
|
+
note.write_text("release notes\n", encoding="utf-8")
|
|
170
|
+
|
|
171
|
+
changed, warnings = release_status._read_changed_released_notes(
|
|
172
|
+
tmp_path,
|
|
173
|
+
"0.3.1",
|
|
174
|
+
(),
|
|
175
|
+
("refs/tags/v0.3.0",),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
assert changed == ()
|
|
179
|
+
assert warnings == (
|
|
180
|
+
"Release tag v0.3.0 exists on origin but is not available locally; "
|
|
181
|
+
"run with --fetch-tags to check site/content/releases/0.3.0.md "
|
|
182
|
+
"for historical edits.",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def test_reads_empty_fragments_from_disk(self, tmp_path):
|
|
186
|
+
release_status = _load_release_status_script()
|
|
187
|
+
fragment_dir = tmp_path / "changelog.d"
|
|
188
|
+
fragment_dir.mkdir()
|
|
189
|
+
(fragment_dir / ".gitkeep").write_text("", encoding="utf-8")
|
|
190
|
+
(fragment_dir / "empty.fixed.md").write_text(" \n", encoding="utf-8")
|
|
191
|
+
(fragment_dir / "full.fixed.md").write_text("Fixed the release guard.\n", encoding="utf-8")
|
|
192
|
+
|
|
193
|
+
assert release_status._read_empty_fragments(tmp_path) == ("empty.fixed.md",)
|
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import importlib.util
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _load_check_templates_script():
|
|
10
|
+
script = Path(__file__).parent.parent / "scripts" / "check_templates.py"
|
|
11
|
+
spec = importlib.util.spec_from_file_location("check_templates", script)
|
|
12
|
+
assert spec is not None
|
|
13
|
+
assert spec.loader is not None
|
|
14
|
+
module = importlib.util.module_from_spec(spec)
|
|
15
|
+
spec.loader.exec_module(module)
|
|
16
|
+
return module
|
|
17
|
+
|
|
5
18
|
|
|
6
19
|
class TestGetEnv:
|
|
7
20
|
def test_returns_environment(self):
|
|
@@ -203,3 +216,42 @@ class TestComponentTemplatesIncluded:
|
|
|
203
216
|
except Exception:
|
|
204
217
|
# If kida can't parse it, that's OK -- the point is the file is found
|
|
205
218
|
pass
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class TestTemplateCheckScript:
|
|
222
|
+
def test_milo_aware_check_accepts_terminal_filters(self, tmp_path):
|
|
223
|
+
"""The local checker uses Milo's terminal environment, not raw kida defaults."""
|
|
224
|
+
check_templates = _load_check_templates_script()
|
|
225
|
+
(tmp_path / "ok.kida").write_text('{{ "ok" | bold }}', encoding="utf-8")
|
|
226
|
+
|
|
227
|
+
errors = check_templates._check_root(tmp_path, "tmp")
|
|
228
|
+
|
|
229
|
+
assert errors == []
|
|
230
|
+
|
|
231
|
+
def test_milo_aware_check_flags_unified_end_tags(self, tmp_path):
|
|
232
|
+
check_templates = _load_check_templates_script()
|
|
233
|
+
(tmp_path / "bad.kida").write_text("{% if value %}ok{% end %}", encoding="utf-8")
|
|
234
|
+
|
|
235
|
+
errors = check_templates._check_root(tmp_path, "tmp")
|
|
236
|
+
|
|
237
|
+
assert len(errors) == 1
|
|
238
|
+
assert "bad.kida:1" in errors[0]
|
|
239
|
+
assert "strict: unified {% end %} closes 'if'" in errors[0]
|
|
240
|
+
assert "prefer {% endif %}" in errors[0]
|
|
241
|
+
|
|
242
|
+
def test_milo_aware_check_flags_fragile_same_folder_imports(self, tmp_path):
|
|
243
|
+
check_templates = _load_check_templates_script()
|
|
244
|
+
components = tmp_path / "components"
|
|
245
|
+
components.mkdir()
|
|
246
|
+
(components / "_defs.kida").write_text("{% def item() %}x{% enddef %}", encoding="utf-8")
|
|
247
|
+
(components / "page.kida").write_text(
|
|
248
|
+
'{% from "components/_defs.kida" import item %}{{ item() }}',
|
|
249
|
+
encoding="utf-8",
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
errors = check_templates._check_root(tmp_path, "tmp")
|
|
253
|
+
|
|
254
|
+
assert len(errors) == 1
|
|
255
|
+
assert "components/page.kida:1" in errors[0]
|
|
256
|
+
assert "lint/fragile-path" in errors[0]
|
|
257
|
+
assert 'prefer "./_defs.kida"' in errors[0]
|
|
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
|
|
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
|