feat: add archive_folder support for preserving previous generations

When archive_folder is set in the project config, artifacts are moved to
numbered archive copies (e.g. x.01.jpg, x.02.jpg) instead of being
overwritten or deleted.

- Build command archives existing artifacts before rebuilding dirty targets
- Clean command moves files to archive instead of deleting them
- Subfolder structure is preserved in the archive directory
- State file is always deleted, never archived
This commit is contained in:
Konstantin Fickel 2026-02-21 11:36:45 +01:00
parent 9ace38c806
commit 24cade558a
Signed by: kfickel
GPG key ID: A793722F9933C1A5
7 changed files with 272 additions and 8 deletions

View file

@ -412,6 +412,66 @@ class TestRunBuild:
assert result.failed == {}
class TestArchiveOnBuild:
"""Test that build archives existing artifacts when archive_folder is set."""
async def test_build_archives_existing_file(
self, project_dir: Path, write_config: WriteConfig
) -> None:
config = write_config(
{
"archive_folder": "archive",
"targets": {"out.txt": {"prompt": "version 1"}},
}
)
with patch("hokusai.builder._create_providers", return_value=_fake_providers()):
r1 = await run_build(config, project_dir, _PROJECT)
assert r1.built == ["out.txt"]
v1_content = (project_dir / "out.txt").read_text()
config2 = write_config(
{
"archive_folder": "archive",
"targets": {"out.txt": {"prompt": "version 2"}},
}
)
r2 = await run_build(config2, project_dir, _PROJECT)
assert r2.built == ["out.txt"]
# v1 should be archived, v2 should be current
archived = project_dir / "archive" / "out.01.txt"
assert archived.exists()
assert archived.read_text() == v1_content
assert (project_dir / "out.txt").exists()
async def test_build_no_archive_without_setting(
self, project_dir: Path, simple_text_config: ProjectConfig
) -> None:
with patch("hokusai.builder._create_providers", return_value=_fake_providers()):
r1 = await run_build(simple_text_config, project_dir, _PROJECT)
assert r1.built == ["output.txt"]
assert not (project_dir / "archive").exists()
async def test_build_archives_increment(
self, project_dir: Path, write_config: WriteConfig
) -> None:
config_raw: dict[str, object] = {
"archive_folder": "archive",
"targets": {"out.txt": {"prompt": "v"}},
}
with patch("hokusai.builder._create_providers", return_value=_fake_providers()):
for i in range(1, 4):
cfg = write_config(
{**config_raw, "targets": {"out.txt": {"prompt": f"v{i}"}}}
)
_ = await run_build(cfg, project_dir, _PROJECT)
assert (project_dir / "archive" / "out.01.txt").exists()
assert (project_dir / "archive" / "out.02.txt").exists()
assert not (project_dir / "archive" / "out.03.txt").exists()
class TestDownloadTarget:
"""Tests for download-type targets that fetch files from URLs."""