Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,21 @@ jobs:
run: python -m pip install --upgrade pip && python -m pip install -e ".[dev]"
- name: Run tests
run: pytest

smoke:
name: smoke (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
cache: pip
- name: Install package and smoke-test tools
run: python -m pip install --upgrade pip && python -m pip install . pytest pytest-cov
- name: Run installed CLI smoke tests
run: pytest -q tests/test_cli_smoke.py --no-cov
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ All notable changes to this project are documented in this file. The format is b
- Scriptable `title`, `tags`, and `rename` subcommands.
- Safe rename previews, confirmation, recovery manifests, and an `undo` command.
- Machine-readable JSON output for every scriptable subcommand.
- Installed CLI smoke coverage on Windows, macOS, and Linux.
- Python 3.11–3.13 test matrix, linting, coverage, and distribution checks.

### Changed
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ or normalize a folder of image names from an interactive menu or scriptable subc
## Requirements

- Python 3.11+
- A current Windows, macOS, or Linux system

See the [platform support policy](https://github.com/HoungDev/creator-toolkit-cli/blob/main/docs/platform-support.md)
for the tested OS/Python matrix and support expectations.

## Installation

Expand Down
28 changes: 28 additions & 0 deletions docs/platform-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Platform support

Creator Toolkit CLI supports Python 3.11–3.13 on current Windows, macOS, and Linux systems.

## Continuous integration policy

The CI suite uses two complementary matrices:

- Unit, coverage, lint, audit, and distribution checks run on Ubuntu across Python 3.11, 3.12,
and 3.13.
- Installed CLI smoke tests run on the current GitHub-hosted Ubuntu, macOS, and Windows images
with Python 3.12.

The cross-platform jobs install the package with `pip install .` and invoke the generated
`creator-toolkit` console entrypoint. They cover command help, title generation, tag generation,
non-mutating image rename previews, structured diagnostics, and failure exit codes.

## Support expectations

- Bugs reproduced on the tested matrix are treated as supported-platform defects.
- Other Python 3 implementations, end-of-life Python releases, and older operating system
versions may work but are not continuously verified.
- Filesystem behavior can differ by platform. Rename safety and rollback tests therefore run in
both the unit suite and installed CLI smoke suite.

When reporting a platform-specific issue, include the operating system, Python version, install
method, command, exit code, and complete stdout/stderr output. Do not include sensitive paths or
file contents.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: Console",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand Down
76 changes: 76 additions & 0 deletions tests/test_cli_smoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import json
import shutil
import subprocess
import sys
from pathlib import Path


def installed_cli() -> str:
executable = shutil.which("creator-toolkit")
if executable is not None:
return executable

script_name = "creator-toolkit.exe" if sys.platform == "win32" else "creator-toolkit"
candidate = Path(sys.executable).with_name(script_name)
assert candidate.is_file(), f"Installed CLI not found at {candidate}"
return str(candidate)


def run_cli(*arguments: str) -> subprocess.CompletedProcess[str]:
return subprocess.run(
[installed_cli(), *arguments],
check=False,
capture_output=True,
text=True,
)


def assert_success(result: subprocess.CompletedProcess[str]) -> None:
assert result.returncode == 0, f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}"


def test_installed_cli_help_lists_commands():
result = run_cli("--help")

assert_success(result)
assert result.stderr == ""
for command in ("title", "tags", "rename", "undo"):
assert command in result.stdout


def test_installed_cli_generates_title_and_tags_json():
title_result = run_cli("title", "creator workflow", "--json")
assert_success(title_result)
title = json.loads(title_result.stdout)
assert title["command"] == "title"
assert "Creator Workflow" in title["result"]["title"]

tags_result = run_cli("tags", "--count", "3", "--json")
assert_success(tags_result)
tags = json.loads(tags_result.stdout)
assert tags["command"] == "tags"
assert tags["result"]["count"] == 3


def test_installed_cli_rename_preview_does_not_change_files(tmp_path):
image = tmp_path / "photo.jpg"
image.write_bytes(b"photo")

result = run_cli("rename", str(tmp_path), "--dry-run", "--json")

assert_success(result)
assert result.stderr == ""
payload = json.loads(result.stdout)
assert payload["status"] == "preview"
assert image.read_bytes() == b"photo"
assert not (tmp_path / "image_1.jpg").exists()


def test_installed_cli_failure_preserves_exit_code_and_diagnostics(tmp_path):
result = run_cli("rename", str(tmp_path / "missing"), "--dry-run", "--json")

assert result.returncode == 1
assert result.stdout == ""
payload = json.loads(result.stderr)
assert payload["ok"] is False
assert payload["error"]["type"] == "FileNotFoundError"