feat: INSTALL.md held to the installation instructions - prerequisites lists generated from the manifest, setup questions checked by docs verify (#154)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnAJ7Z3CpVD3PRbN73QtU2 Files changed: - CHANGES.md - INSTALL.md - VERSION - instructions/dev/doc-pull-through.md - instructions/dev/stack-close/SKILL.md - instructions/setup-instance.md - tools/CONTRACT.md - tools/README.md - tools/chemenu/cli_contract.py - tools/chemenu/commands/docs_verify.py - tools/chemenu/install_doc.py - tools/chemenu/tests/test_install_doc.py
This commit is contained in:
1 parent
b33088f64e
commit
c77bda2004
12 files changed
+633
-69
No files matched your search
@@ -270,7 +270,7 @@ GROUPS: tuple[tuple[str, tuple[str, ...]], ...] = (
|
||||
("Types, instructions and docs", (
|
||||
"types list", "types describe",
|
||||
"instructions sync", "instructions verify", "instructions list",
|
||||
"docs verify", "docs toc", "docs contract",
|
||||
"docs verify", "docs toc", "docs prerequisites", "docs contract",
|
||||
)),
|
||||
("Telemetry", (
|
||||
"eval sessions", "eval score",
|
||||
|
||||
@@ -68,7 +68,7 @@ from typing import Optional
|
||||
|
||||
import typer
|
||||
|
||||
from chemenu import blocks, cli_contract, config, conventions, kb_collections, markdown_code, toc, toolpaths, version as version_mod
|
||||
from chemenu import blocks, cli_contract, config, conventions, install_doc, kb_collections, markdown_code, toc, toolpaths, version as version_mod
|
||||
from chemenu.commands import dist_cmd
|
||||
from chemenu.commands._util import fail, rel_path, success
|
||||
|
||||
@@ -1078,6 +1078,11 @@ def check_breaking_change_for_boundary() -> list[str]:
|
||||
"file. A target's `#anchor` suffix is stripped first, and code fences and inline code "
|
||||
"spans are masked before scanning, so link syntax shown as an example is not mistaken "
|
||||
"for a real reference.",
|
||||
"`INSTALL.md` carries one generated `<!-- wikitool:prerequisites -->` region per "
|
||||
"platform value of `tools/prerequisites.txt` (`prerequisites-<platform>` for a "
|
||||
"platform-specific one), each current; and the `<!-- setup-question: <key> -->` markers "
|
||||
"in `instructions/setup-instance.md` and `INSTALL.md` name the same set of keys, so a "
|
||||
"question the agent asks is never one the human guide leaves out, nor the reverse.",
|
||||
"Read-only.",
|
||||
),
|
||||
failures=(
|
||||
@@ -1108,6 +1113,22 @@ def check_breaking_change_for_boundary() -> list[str]:
|
||||
"file",
|
||||
reaction="Fix the `../` count or the target's name",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="An `INSTALL.md` prerequisites region is stale",
|
||||
reaction="Run `docs prerequisites --apply`, then re-run",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="An `INSTALL.md` prerequisites region is missing, or names a platform no tool "
|
||||
"has",
|
||||
reaction="Add the marker pair where that list belongs (or remove the orphaned region "
|
||||
"and its introducing prose), then run `docs prerequisites --apply`",
|
||||
),
|
||||
cli_contract.Failure(
|
||||
cause="A setup question is marked in one of `instructions/setup-instance.md` and "
|
||||
"`INSTALL.md` but not the other",
|
||||
reaction="Describe the question for the human in `INSTALL.md` with the same marker, "
|
||||
"or remove the bullet for a question no longer asked",
|
||||
),
|
||||
),
|
||||
examples=(
|
||||
"tools/wikitool docs verify",
|
||||
@@ -1118,6 +1139,7 @@ def check_breaking_change_for_boundary() -> list[str]:
|
||||
see_also=(
|
||||
"`wikitool docs toc` - regenerates tables of contents",
|
||||
"`wikitool docs contract` - regenerates the commands region",
|
||||
"`wikitool docs prerequisites` - regenerates `INSTALL.md`'s prerequisites lists",
|
||||
"`wikitool instructions verify` - the same kind of check for `instructions/`",
|
||||
),
|
||||
))
|
||||
@@ -1139,6 +1161,8 @@ def verify():
|
||||
+ check_no_issue_references()
|
||||
+ check_toc_regions()
|
||||
+ check_reference_targets()
|
||||
+ install_doc.check_prerequisite_regions()
|
||||
+ install_doc.check_setup_questions()
|
||||
)
|
||||
|
||||
if issues:
|
||||
@@ -1155,6 +1179,8 @@ def verify():
|
||||
f"no issue references in {len(shipped_prose())} shipped document(s) or command help, "
|
||||
f"tables of contents current and every link resolving on "
|
||||
f"{len(toc.target_files())} reference file(s), "
|
||||
f"{install_doc.INSTALL_DOC} in step with tools/prerequisites.txt and "
|
||||
f"{install_doc.SETUP_INSTRUCTION}'s questions, "
|
||||
f"{version_mod.CHANGES_FILENAME} documents version "
|
||||
f"{(config.ROOT / version_mod.VERSION_FILENAME).read_text(encoding='utf-8').strip()}."
|
||||
)
|
||||
@@ -1235,6 +1261,79 @@ def toc_command(
|
||||
typer.echo(f"\n{len(changed)} file(s) would change. Re-run with --apply to write.")
|
||||
|
||||
|
||||
@app.command("prerequisites")
|
||||
@cli_contract.record(cli_contract.CommandRecord(
|
||||
path="docs prerequisites",
|
||||
summary="Regenerate `INSTALL.md`'s prerequisites lists from `tools/prerequisites.txt`.",
|
||||
synopsis=(cli_contract.Variant(usage="docs prerequisites [--apply]"),),
|
||||
properties=cli_contract.Properties(
|
||||
effect=cli_contract.Effect.WRITE,
|
||||
idempotent=cli_contract.Idempotent.YES,
|
||||
atomic="Yes - every region is rewritten in one file write",
|
||||
budget=cli_contract.Budget.COUNTED,
|
||||
),
|
||||
notes=(
|
||||
"Rewrites each `<!-- wikitool:prerequisites -->` region in `INSTALL.md` (tools every "
|
||||
"platform needs) and `<!-- wikitool:prerequisites-<platform> -->` region (tools only that "
|
||||
"platform needs) from the manifest: one list item per tool, its label and minimum "
|
||||
"version. The manifest's reason field stays out - it is English prose, and the region "
|
||||
"sits in a document that need not be.",
|
||||
"Never places a region: where a list belongs in the human guide is that guide's own "
|
||||
"decision. A region the manifest calls for but the file lacks is an error naming the "
|
||||
"marker pair to add.",
|
||||
"Dry-run by default (says whether the file would change); `--apply` writes.",
|
||||
"`docs verify` checks the result stays current.",
|
||||
),
|
||||
failures=(cli_contract.Failure(
|
||||
cause="`INSTALL.md` is missing, or lacks a region the manifest calls for",
|
||||
reaction="Not transient - add the marker pair the message names where that list "
|
||||
"belongs (restore the file if it is gone), then retry",
|
||||
),),
|
||||
examples=(
|
||||
"tools/wikitool docs prerequisites",
|
||||
"tools/wikitool docs prerequisites --apply",
|
||||
),
|
||||
never=(
|
||||
"Never hand-edit a prerequisites region - change `tools/prerequisites.txt` and re-run "
|
||||
"this.",
|
||||
),
|
||||
see_also=(
|
||||
"`wikitool docs verify` - checks every region is current",
|
||||
),
|
||||
))
|
||||
def prerequisites_command(
|
||||
apply: bool = typer.Option(False, "--apply", help="Write changes; default is dry-run (preview only)"),
|
||||
):
|
||||
"""Regenerate `INSTALL.md`'s prerequisites lists from `tools/prerequisites.txt`."""
|
||||
from chemenu import prerequisites
|
||||
|
||||
path = install_doc.install_doc_path()
|
||||
if not path.is_file():
|
||||
fail(f"{install_doc.INSTALL_DOC} is missing.")
|
||||
|
||||
text = path.read_text(encoding="utf-8")
|
||||
after, missing = install_doc.refresh(text, prerequisites.load_manifest())
|
||||
if missing:
|
||||
fail(
|
||||
f"{install_doc.INSTALL_DOC} lacks "
|
||||
+ ", ".join(f"`{blocks.open_marker(name)}`" for name in missing)
|
||||
+ " - add each marker pair (with its closing marker) where that list belongs, "
|
||||
"then re-run."
|
||||
)
|
||||
|
||||
if after == text:
|
||||
success(f"{install_doc.INSTALL_DOC}'s prerequisites lists are already current.")
|
||||
return
|
||||
|
||||
if not apply:
|
||||
typer.echo(f"{install_doc.INSTALL_DOC} would change.")
|
||||
typer.echo("Re-run with --apply to write.")
|
||||
return
|
||||
|
||||
path.write_text(after, encoding="utf-8", newline="\n")
|
||||
success(f"Regenerated the prerequisites lists in {install_doc.INSTALL_DOC}.")
|
||||
|
||||
|
||||
@app.command("contract")
|
||||
@cli_contract.record(cli_contract.CommandRecord(
|
||||
path="docs contract",
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
"""The two places `INSTALL.md` overlaps the installation instructions, held to them.
|
||||
|
||||
`INSTALL.md` is written for a human and in the instance's KB language;
|
||||
`instructions/setup-instance.md` is written for an agent and in English. A
|
||||
single file serving both was rejected: an agent reads every sentence as an
|
||||
instruction, and the human needs preparation and decisions where the agent
|
||||
needs steps and exit codes. So the human document does not retell the
|
||||
procedure, and what it still shares with the instructions is two enumerable
|
||||
lists - both checked here, so neither can drift silently:
|
||||
|
||||
1. **What the machine needs.** `tools/prerequisites.txt` is the one list; the
|
||||
preflight and `doctor` read it. `INSTALL.md` carries it as a generated
|
||||
region per platform value (`<!-- wikitool:prerequisites -->` for `all`,
|
||||
`<!-- wikitool:prerequisites-<platform> -->` otherwise), rendered from the
|
||||
label and minimum version only. The manifest's `why` field stays out: it is
|
||||
English prose, and the region sits in a document that is not. A region is
|
||||
never placed by the tool - where it goes is the human document's own
|
||||
decision - so a missing one is reported, not appended.
|
||||
2. **What the agent asks the user.** Every place `setup-instance.md` puts a
|
||||
question to the user carries `<!-- setup-question: <key> -->`, and the
|
||||
bullet in `INSTALL.md` that tells the human about it carries the same
|
||||
marker. The two key sets must be equal: a question the human guide does not
|
||||
mention catches them unprepared, and one it mentions that is no longer asked
|
||||
is a claim about a procedure that no longer exists. The key is the shared
|
||||
identifier precisely because the surrounding prose is in two languages.
|
||||
|
||||
Everything else in `INSTALL.md` is prose no check reads;
|
||||
`instructions/dev/doc-pull-through.md` names it as session work.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from chemenu import blocks, config, prerequisites
|
||||
|
||||
INSTALL_DOC = "INSTALL.md"
|
||||
SETUP_INSTRUCTION = "instructions/setup-instance.md"
|
||||
|
||||
REGION_PREFIX = "prerequisites"
|
||||
|
||||
QUESTION_RE = re.compile(r"<!-- setup-question: ([a-z][a-z0-9-]*) -->")
|
||||
|
||||
|
||||
def install_doc_path() -> Path:
|
||||
return config.ROOT / INSTALL_DOC
|
||||
|
||||
|
||||
def setup_instruction_path() -> Path:
|
||||
return config.ROOT / SETUP_INSTRUCTION
|
||||
|
||||
|
||||
def region_name(platform: str) -> str:
|
||||
"""`prerequisites` for the tools every platform needs, `prerequisites-<platform>`
|
||||
for the ones only that platform does."""
|
||||
return REGION_PREFIX if platform == "all" else f"{REGION_PREFIX}-{platform}"
|
||||
|
||||
|
||||
def render_lines(tools: tuple[prerequisites.Tool, ...]) -> list[str]:
|
||||
"""One list item per tool - label and minimum version, nothing in a language."""
|
||||
return [
|
||||
f"- **{tool.label}**" + (f" ≥ {tool.minimum}" if tool.minimum else "")
|
||||
for tool in tools
|
||||
]
|
||||
|
||||
|
||||
def expected_regions(manifest: prerequisites.Manifest) -> dict[str, str]:
|
||||
"""Region name -> the whole region, markers included, in manifest order of
|
||||
first appearance of each platform value."""
|
||||
platforms: list[str] = []
|
||||
for tool in manifest.tools:
|
||||
if tool.platforms not in platforms:
|
||||
platforms.append(tool.platforms)
|
||||
regions = {}
|
||||
for platform in platforms:
|
||||
name = region_name(platform)
|
||||
tools = tuple(t for t in manifest.tools if t.platforms == platform)
|
||||
regions[name] = "\n".join(
|
||||
[blocks.open_marker(name), *render_lines(tools), blocks.close_marker(name)]
|
||||
)
|
||||
return regions
|
||||
|
||||
|
||||
def _region_span(text: str, name: str) -> tuple[int, int] | None:
|
||||
"""Start and end offset of `name`'s region, markers included - without the
|
||||
blank lines `blocks` takes along, so a refresh leaves the prose around it
|
||||
exactly as it was."""
|
||||
start = text.find(blocks.open_marker(name))
|
||||
if start < 0:
|
||||
return None
|
||||
close = blocks.close_marker(name)
|
||||
end = text.find(close, start)
|
||||
if end < 0:
|
||||
return None
|
||||
return start, end + len(close)
|
||||
|
||||
|
||||
def refresh(text: str, manifest: prerequisites.Manifest) -> tuple[str, list[str]]:
|
||||
"""`text` with every prerequisites region it carries rewritten from the
|
||||
manifest, plus the names of the regions it lacks.
|
||||
|
||||
A region the manifest no longer has a platform for is left alone and
|
||||
reported by `check_prerequisite_regions`, not deleted here: the prose
|
||||
introducing it ("Unter Windows zusätzlich:") would otherwise be left
|
||||
standing over nothing.
|
||||
"""
|
||||
missing = []
|
||||
for name, region in expected_regions(manifest).items():
|
||||
span = _region_span(text, name)
|
||||
if span is None:
|
||||
missing.append(name)
|
||||
continue
|
||||
text = text[: span[0]] + region + text[span[1]:]
|
||||
return text, missing
|
||||
|
||||
|
||||
def _present_region_names(text: str) -> set[str]:
|
||||
pattern = re.compile(
|
||||
rf"<!-- wikitool:({re.escape(REGION_PREFIX)}(?:-[a-z0-9-]+)?) -->"
|
||||
)
|
||||
return set(pattern.findall(text))
|
||||
|
||||
|
||||
def check_prerequisite_regions() -> list[str]:
|
||||
"""`INSTALL.md` carries one current region per platform value of the manifest."""
|
||||
path = install_doc_path()
|
||||
if not path.is_file():
|
||||
return [f"{INSTALL_DOC} is missing - the human installation guide is a stack file"]
|
||||
text = path.read_text(encoding="utf-8")
|
||||
manifest = prerequisites.load_manifest()
|
||||
refreshed, missing = refresh(text, manifest)
|
||||
|
||||
issues = [
|
||||
f"{INSTALL_DOC} has no `{blocks.open_marker(name)}` region - add the marker pair "
|
||||
f"where that list belongs, then run `wikitool docs prerequisites --apply`"
|
||||
for name in missing
|
||||
]
|
||||
issues += [
|
||||
f"{INSTALL_DOC} carries `{blocks.open_marker(name)}`, but no tool in "
|
||||
"tools/prerequisites.txt has that platform - remove the region and the prose "
|
||||
"introducing it"
|
||||
for name in sorted(_present_region_names(text) - set(expected_regions(manifest)))
|
||||
]
|
||||
if refreshed != text:
|
||||
issues.append(
|
||||
f"{INSTALL_DOC}'s prerequisites list no longer matches tools/prerequisites.txt - "
|
||||
"run `wikitool docs prerequisites --apply`"
|
||||
)
|
||||
return issues
|
||||
|
||||
|
||||
def question_keys(text: str) -> set[str]:
|
||||
return set(QUESTION_RE.findall(text))
|
||||
|
||||
|
||||
def check_setup_questions() -> list[str]:
|
||||
"""Every question `setup-instance.md` asks is named in `INSTALL.md`, and
|
||||
`INSTALL.md` names no question that is no longer asked."""
|
||||
setup = setup_instruction_path()
|
||||
install = install_doc_path()
|
||||
if not setup.is_file() or not install.is_file():
|
||||
# A missing INSTALL.md is check_prerequisite_regions' finding; a
|
||||
# missing setup instruction leaves nothing to compare against.
|
||||
return []
|
||||
asked = question_keys(setup.read_text(encoding="utf-8"))
|
||||
named = question_keys(install.read_text(encoding="utf-8"))
|
||||
|
||||
issues = [
|
||||
f"{SETUP_INSTRUCTION} asks the user `{key}`, but {INSTALL_DOC} § \"Was der Agent dich "
|
||||
f"fragt\" does not name it - add a bullet carrying `<!-- setup-question: {key} -->`"
|
||||
for key in sorted(asked - named)
|
||||
]
|
||||
issues += [
|
||||
f"{INSTALL_DOC} names the setup question `{key}`, which {SETUP_INSTRUCTION} no longer "
|
||||
"asks - remove the bullet, or restore the marker where the question is asked"
|
||||
for key in sorted(named - asked)
|
||||
]
|
||||
return issues
|
||||
@@ -0,0 +1,173 @@
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from chemenu import config, install_doc, prerequisites
|
||||
from chemenu.cli import app
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
MANIFEST = prerequisites.Manifest(
|
||||
limits={},
|
||||
tools=(
|
||||
prerequisites.Tool("python", "3.11", "all", "Python", "why"),
|
||||
prerequisites.Tool("git", None, "all", "Git", "why"),
|
||||
prerequisites.Tool("pwsh", "7", "windows", "PowerShell 7 (pwsh)", "why"),
|
||||
),
|
||||
)
|
||||
|
||||
CURRENT = """# Installation
|
||||
|
||||
Vorher:
|
||||
|
||||
<!-- wikitool:prerequisites -->
|
||||
- **Python** ≥ 3.11
|
||||
- **Git**
|
||||
<!-- /wikitool:prerequisites -->
|
||||
|
||||
Unter Windows zusätzlich:
|
||||
|
||||
<!-- wikitool:prerequisites-windows -->
|
||||
- **PowerShell 7 (pwsh)** ≥ 7
|
||||
<!-- /wikitool:prerequisites-windows -->
|
||||
|
||||
## Was der Agent dich fragt
|
||||
|
||||
- <!-- setup-question: identity --> **Autor-Identität** - Name und E-Mail.
|
||||
"""
|
||||
|
||||
SETUP = """# Set up
|
||||
|
||||
2. <!-- setup-question: identity --> **Decision point - identity.** Ask the user.
|
||||
"""
|
||||
|
||||
|
||||
def _tree(tmp_path, monkeypatch, install=CURRENT, setup=SETUP, manifest=MANIFEST):
|
||||
monkeypatch.setattr(config, "ROOT", tmp_path)
|
||||
monkeypatch.setattr(prerequisites, "load_manifest", lambda path=None: manifest)
|
||||
(tmp_path / "instructions").mkdir()
|
||||
(tmp_path / "INSTALL.md").write_text(install, encoding="utf-8")
|
||||
(tmp_path / "instructions" / "setup-instance.md").write_text(setup, encoding="utf-8")
|
||||
return tmp_path
|
||||
|
||||
|
||||
def test_this_repos_install_doc_matches_the_manifest():
|
||||
assert install_doc.check_prerequisite_regions() == []
|
||||
|
||||
|
||||
def test_this_repos_install_doc_names_every_setup_question():
|
||||
assert install_doc.check_setup_questions() == []
|
||||
|
||||
|
||||
def test_this_repos_setup_instruction_marks_its_questions():
|
||||
"""The check compares two sets; an instruction that lost every marker would
|
||||
compare empty against empty and pass. Guard the real file's own count."""
|
||||
text = install_doc.setup_instruction_path().read_text(encoding="utf-8")
|
||||
assert install_doc.question_keys(text) >= {
|
||||
"identity", "remote", "kb-language", "domain",
|
||||
"personalization", "environment", "telemetry", "task-tracker",
|
||||
}
|
||||
|
||||
|
||||
def test_render_lines_are_label_and_minimum_only():
|
||||
assert install_doc.render_lines(MANIFEST.tools) == [
|
||||
"- **Python** ≥ 3.11",
|
||||
"- **Git**",
|
||||
"- **PowerShell 7 (pwsh)** ≥ 7",
|
||||
]
|
||||
|
||||
|
||||
def test_a_current_install_doc_passes(tmp_path, monkeypatch):
|
||||
_tree(tmp_path, monkeypatch)
|
||||
assert install_doc.check_prerequisite_regions() == []
|
||||
assert install_doc.check_setup_questions() == []
|
||||
|
||||
|
||||
def test_a_manifest_tool_missing_from_install_doc_fails(tmp_path, monkeypatch):
|
||||
"""Acceptance criterion: a tool in the manifest that INSTALL.md lacks fails `docs verify`."""
|
||||
_tree(tmp_path, monkeypatch, install=CURRENT.replace("- **Git**\n", ""))
|
||||
issues = install_doc.check_prerequisite_regions()
|
||||
assert any("no longer matches tools/prerequisites.txt" in issue for issue in issues)
|
||||
|
||||
|
||||
def test_a_new_platform_without_a_region_fails(tmp_path, monkeypatch):
|
||||
manifest = prerequisites.Manifest(
|
||||
limits={},
|
||||
tools=MANIFEST.tools + (prerequisites.Tool("brew", None, "macos", "Homebrew", "why"),),
|
||||
)
|
||||
_tree(tmp_path, monkeypatch, manifest=manifest)
|
||||
issues = install_doc.check_prerequisite_regions()
|
||||
assert any("<!-- wikitool:prerequisites-macos -->" in issue for issue in issues)
|
||||
|
||||
|
||||
def test_a_region_for_a_platform_no_tool_has_fails(tmp_path, monkeypatch):
|
||||
manifest = prerequisites.Manifest(limits={}, tools=MANIFEST.tools[:2])
|
||||
_tree(tmp_path, monkeypatch, manifest=manifest)
|
||||
issues = install_doc.check_prerequisite_regions()
|
||||
assert any("prerequisites-windows" in issue and "no tool" in issue for issue in issues)
|
||||
|
||||
|
||||
def test_a_missing_install_doc_is_reported(tmp_path, monkeypatch):
|
||||
_tree(tmp_path, monkeypatch)
|
||||
(tmp_path / "INSTALL.md").unlink()
|
||||
assert any("missing" in issue for issue in install_doc.check_prerequisite_regions())
|
||||
assert install_doc.check_setup_questions() == []
|
||||
|
||||
|
||||
def test_refresh_touches_nothing_outside_the_regions(tmp_path):
|
||||
stale = CURRENT.replace("≥ 3.11", "≥ 3.9")
|
||||
refreshed, missing = install_doc.refresh(stale, MANIFEST)
|
||||
assert missing == []
|
||||
assert refreshed == CURRENT
|
||||
|
||||
|
||||
def test_refresh_is_idempotent():
|
||||
once, _ = install_doc.refresh(CURRENT, MANIFEST)
|
||||
twice, _ = install_doc.refresh(once, MANIFEST)
|
||||
assert once == twice == CURRENT
|
||||
|
||||
|
||||
def test_a_question_the_install_doc_does_not_name_fails(tmp_path, monkeypatch):
|
||||
"""Acceptance criterion: a new question in setup-instance.md that INSTALL.md
|
||||
does not name fails `docs verify`."""
|
||||
setup = SETUP + "\n3. <!-- setup-question: remote --> **Decision point - remote.**\n"
|
||||
_tree(tmp_path, monkeypatch, setup=setup)
|
||||
issues = install_doc.check_setup_questions()
|
||||
assert len(issues) == 1
|
||||
assert "`remote`" in issues[0] and "does not name it" in issues[0]
|
||||
|
||||
|
||||
def test_a_question_no_longer_asked_fails(tmp_path, monkeypatch):
|
||||
install = CURRENT + "- <!-- setup-question: telemetry --> **Telemetrie**\n"
|
||||
_tree(tmp_path, monkeypatch, install=install)
|
||||
issues = install_doc.check_setup_questions()
|
||||
assert len(issues) == 1
|
||||
assert "`telemetry`" in issues[0] and "no longer asks" in issues[0]
|
||||
|
||||
|
||||
def test_docs_prerequisites_is_a_dry_run_by_default(tmp_path, monkeypatch):
|
||||
stale = CURRENT.replace("≥ 3.11", "≥ 3.9")
|
||||
root = _tree(tmp_path, monkeypatch, install=stale)
|
||||
result = runner.invoke(app, ["docs", "prerequisites"])
|
||||
assert result.exit_code == 0, result.output
|
||||
assert "would change" in result.output
|
||||
assert (root / "INSTALL.md").read_text(encoding="utf-8") == stale
|
||||
|
||||
|
||||
def test_docs_prerequisites_apply_rewrites_the_regions(tmp_path, monkeypatch):
|
||||
root = _tree(tmp_path, monkeypatch, install=CURRENT.replace("≥ 3.11", "≥ 3.9"))
|
||||
result = runner.invoke(app, ["docs", "prerequisites", "--apply"])
|
||||
assert result.exit_code == 0, result.output
|
||||
assert (root / "INSTALL.md").read_text(encoding="utf-8") == CURRENT
|
||||
assert install_doc.check_prerequisite_regions() == []
|
||||
|
||||
|
||||
def test_docs_prerequisites_never_places_a_missing_region(tmp_path, monkeypatch):
|
||||
without_windows = CURRENT.replace(
|
||||
"<!-- wikitool:prerequisites-windows -->\n- **PowerShell 7 (pwsh)** ≥ 7\n"
|
||||
"<!-- /wikitool:prerequisites-windows -->\n",
|
||||
"",
|
||||
)
|
||||
root = _tree(tmp_path, monkeypatch, install=without_windows)
|
||||
result = runner.invoke(app, ["docs", "prerequisites", "--apply"])
|
||||
assert result.exit_code == 1
|
||||
assert "prerequisites-windows" in result.output
|
||||
assert (root / "INSTALL.md").read_text(encoding="utf-8") == without_windows
|
||||
Reference in new issue
Block a user