feat: add content target type for writing literal text to files
All checks were successful
Continuous Integration / Build Package (push) Successful in 43s
Continuous Integration / Lint, Check & Test (push) Successful in 1m1s

Content targets write a string directly to the output file without
invoking any AI provider. They don't require API keys and are not
archived when overwritten.

Example usage in .hokusai.yaml:
  file.txt:
    content: ABC
This commit is contained in:
Konstantin Fickel 2026-02-21 18:14:09 +01:00
parent fda28b5afa
commit bb03975ece
Signed by: kfickel
GPG key ID: A793722F9933C1A5
5 changed files with 163 additions and 10 deletions

View file

@ -614,6 +614,101 @@ class TestDownloadTarget:
assert (project_dir / "description.txt").exists()
class TestContentTarget:
"""Tests for content-type targets that write literal text."""
async def test_content_target_writes_file(
self, project_dir: Path, write_config: WriteConfig
) -> None:
config = write_config({"targets": {"file.txt": {"content": "ABC"}}})
with patch("hokusai.builder._create_providers", return_value=_fake_providers()):
result = await run_build(config, project_dir, _PROJECT)
assert result.built == ["file.txt"]
assert (project_dir / "file.txt").read_text() == "ABC"
async def test_content_target_incremental_skip(
self, project_dir: Path, write_config: WriteConfig
) -> None:
config = write_config({"targets": {"file.txt": {"content": "ABC"}}})
with patch("hokusai.builder._create_providers", return_value=_fake_providers()):
r1 = await run_build(config, project_dir, _PROJECT)
assert r1.built == ["file.txt"]
r2 = await run_build(config, project_dir, _PROJECT)
assert r2.skipped == ["file.txt"]
assert r2.built == []
async def test_content_target_rebuild_on_change(
self, project_dir: Path, write_config: WriteConfig
) -> None:
config1 = write_config({"targets": {"file.txt": {"content": "ABC"}}})
with patch("hokusai.builder._create_providers", return_value=_fake_providers()):
r1 = await run_build(config1, project_dir, _PROJECT)
assert r1.built == ["file.txt"]
config2 = write_config({"targets": {"file.txt": {"content": "XYZ"}}})
r2 = await run_build(config2, project_dir, _PROJECT)
assert r2.built == ["file.txt"]
assert (project_dir / "file.txt").read_text() == "XYZ"
async def test_content_target_no_archive(
self, project_dir: Path, write_config: WriteConfig
) -> None:
config1 = write_config(
{
"archive_folder": "archive",
"targets": {"file.txt": {"content": "v1"}},
}
)
with patch("hokusai.builder._create_providers", return_value=_fake_providers()):
r1 = await run_build(config1, project_dir, _PROJECT)
assert r1.built == ["file.txt"]
config2 = write_config(
{
"archive_folder": "archive",
"targets": {"file.txt": {"content": "v2"}},
}
)
r2 = await run_build(config2, project_dir, _PROJECT)
assert r2.built == ["file.txt"]
assert (project_dir / "file.txt").read_text() == "v2"
assert not (project_dir / "archive").exists()
async def test_content_target_as_dependency(
self, project_dir: Path, write_config: WriteConfig
) -> None:
config = write_config(
{
"targets": {
"data.txt": {"content": "some data"},
"output.md": {
"prompt": "Process the data",
"inputs": ["data.txt"],
},
}
}
)
with patch("hokusai.builder._create_providers", return_value=_fake_providers()):
result = await run_build(config, project_dir, _PROJECT)
assert "data.txt" in result.built
assert "output.md" in result.built
assert (project_dir / "data.txt").read_text() == "some data"
async def test_content_target_no_provider_needed(
self, project_dir: Path, write_config: WriteConfig
) -> None:
config = write_config({"targets": {"file.txt": {"content": "ABC"}}})
with patch("hokusai.builder._create_providers", return_value=[]):
result = await run_build(config, project_dir, _PROJECT)
assert result.built == ["file.txt"]
assert result.failed == {}
class TestPlaceholderPrompts:
"""Tests for prompt placeholder substitution in builds."""

View file

@ -7,7 +7,7 @@ from pathlib import Path
import pytest
import yaml
from hokusai.config import GenerateTargetConfig, load_config
from hokusai.config import ContentTargetConfig, GenerateTargetConfig, load_config
class TestLoadConfig:
@ -83,3 +83,14 @@ class TestLoadConfig:
with pytest.raises(Exception):
_ = load_config(config_path)
def test_content_target(self, project_dir: Path) -> None:
config_path = project_dir / "test.hokusai.yaml"
_ = config_path.write_text(
yaml.dump({"targets": {"file.txt": {"content": "ABC"}}})
)
config = load_config(config_path)
target = config.targets["file.txt"]
assert isinstance(target, ContentTargetConfig)
assert target.content == "ABC"