From de4ba30ea555ffb0c5ecd4ce93c8db7c8219f4fd Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Wed, 10 Jun 2026 11:06:41 +0800 Subject: [PATCH 1/2] fix(templates): import-std-safe I/O; CI compile-checks every template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit std::println(stderr, ...) fails under `import std` (stderr/stdout/fflush are macros/functions not reachable without the header) — caught when building a project generated from llmapi@0.2.7:openai. Use std::cerr/std::cout stream overloads instead, and flush the chat prompts explicitly. tools/template_smoke.sh renders every template the way `mcpp new` does and builds it against this checkout (path dependency), so template code is compile-gated in CI before any release ships it. Version → 0.2.8 (0.2.7's default template is broken and will not be indexed). --- .github/workflows/ci.yml | 3 ++ README.md | 2 +- README.zh.hant.md | 2 +- README.zh.md | 2 +- docs/en/getting-started.md | 2 +- docs/zh-hant/getting-started.md | 2 +- docs/zh/getting-started.md | 2 +- mcpp.toml | 2 +- templates/anthropic/src/main.cpp.in | 2 +- templates/chat/src/main.cpp.in | 14 ++++--- templates/deepseek/src/main.cpp.in | 2 +- templates/openai/src/main.cpp.in | 2 +- tools/template_smoke.sh | 63 +++++++++++++++++++++++++++++ 13 files changed, 84 insertions(+), 16 deletions(-) create mode 100644 tools/template_smoke.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66f5053..b9f2f48 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,3 +37,6 @@ jobs: - name: Build with mcpp run: mcpp build + + - name: Template smoke (compile each template against this checkout) + run: bash tools/template_smoke.sh diff --git a/README.md b/README.md index ca41fb2..9ed3cf2 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ mcpp add llmapi ```toml [dependencies.mcpplibs] -llmapi = "0.2.7" +llmapi = "0.2.8" ``` ```cpp diff --git a/README.zh.hant.md b/README.zh.hant.md index 16ef282..68b0110 100644 --- a/README.zh.hant.md +++ b/README.zh.hant.md @@ -39,7 +39,7 @@ mcpp add llmapi ```toml [dependencies.mcpplibs] -llmapi = "0.2.7" +llmapi = "0.2.8" ``` ```cpp diff --git a/README.zh.md b/README.zh.md index 7da308c..e1ba0c8 100644 --- a/README.zh.md +++ b/README.zh.md @@ -39,7 +39,7 @@ mcpp add llmapi ```toml [dependencies.mcpplibs] -llmapi = "0.2.7" +llmapi = "0.2.8" ``` ```cpp diff --git a/docs/en/getting-started.md b/docs/en/getting-started.md index 366b7cb..d4f1dd9 100644 --- a/docs/en/getting-started.md +++ b/docs/en/getting-started.md @@ -33,7 +33,7 @@ Or declare it in `mcpp.toml`: ```toml [dependencies.mcpplibs] -llmapi = "0.2.7" +llmapi = "0.2.8" ``` ### Building from Source diff --git a/docs/zh-hant/getting-started.md b/docs/zh-hant/getting-started.md index 650d579..27e68e7 100644 --- a/docs/zh-hant/getting-started.md +++ b/docs/zh-hant/getting-started.md @@ -30,7 +30,7 @@ mcpp add llmapi ```toml [dependencies.mcpplibs] -llmapi = "0.2.7" +llmapi = "0.2.8" ``` ## 從原始碼建置 diff --git a/docs/zh/getting-started.md b/docs/zh/getting-started.md index b1cb873..f41abc2 100644 --- a/docs/zh/getting-started.md +++ b/docs/zh/getting-started.md @@ -30,7 +30,7 @@ mcpp add llmapi ```toml [dependencies.mcpplibs] -llmapi = "0.2.7" +llmapi = "0.2.8" ``` ## 从源码构建 diff --git a/mcpp.toml b/mcpp.toml index b70222a..53ae90a 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "llmapi" -version = "0.2.7" +version = "0.2.8" description = "Modern C++ LLM API client with openai-compatible support" license = "Apache-2.0" repo = "https://github.com/mcpplibs/llmapi" diff --git a/templates/anthropic/src/main.cpp.in b/templates/anthropic/src/main.cpp.in index 021cebf..9113cf2 100644 --- a/templates/anthropic/src/main.cpp.in +++ b/templates/anthropic/src/main.cpp.in @@ -7,7 +7,7 @@ int main() { auto apiKey = std::getenv("ANTHROPIC_API_KEY"); if (!apiKey) { - std::println(stderr, "ANTHROPIC_API_KEY not set"); + std::println(std::cerr, "ANTHROPIC_API_KEY not set"); return 1; } diff --git a/templates/chat/src/main.cpp.in b/templates/chat/src/main.cpp.in index 8d59565..1a74c91 100644 --- a/templates/chat/src/main.cpp.in +++ b/templates/chat/src/main.cpp.in @@ -8,7 +8,7 @@ int main() { auto apiKey = std::getenv("OPENAI_API_KEY"); if (!apiKey) { - std::println(stderr, "OPENAI_API_KEY not set"); + std::println(std::cerr, "OPENAI_API_KEY not set"); return 1; } @@ -21,21 +21,23 @@ int main() { std::println("{{project.name}} — chat CLI (type 'quit' to exit)"); while (true) { - std::print("\nYou: "); + std::print(std::cout, "\nYou: "); + std::cout.flush(); std::string input; if (!std::getline(std::cin, input)) break; if (input == "quit" || input == "q") break; if (input.empty()) continue; try { - std::print("AI: "); + std::print(std::cout, "AI: "); + std::cout.flush(); client.chat_stream(input, [](std::string_view chunk) { - std::print("{}", chunk); - std::fflush(stdout); + std::print(std::cout, "{}", chunk); + std::cout.flush(); }); std::println(""); } catch (const std::exception& e) { - std::println(stderr, "\nError: {}", e.what()); + std::println(std::cerr, "\nError: {}", e.what()); } } diff --git a/templates/deepseek/src/main.cpp.in b/templates/deepseek/src/main.cpp.in index 2b8aba7..ca8408d 100644 --- a/templates/deepseek/src/main.cpp.in +++ b/templates/deepseek/src/main.cpp.in @@ -8,7 +8,7 @@ int main() { auto apiKey = std::getenv("DEEPSEEK_API_KEY"); if (!apiKey) { - std::println(stderr, "DEEPSEEK_API_KEY not set"); + std::println(std::cerr, "DEEPSEEK_API_KEY not set"); return 1; } diff --git a/templates/openai/src/main.cpp.in b/templates/openai/src/main.cpp.in index 135f1f1..f452dbd 100644 --- a/templates/openai/src/main.cpp.in +++ b/templates/openai/src/main.cpp.in @@ -7,7 +7,7 @@ int main() { auto apiKey = std::getenv("OPENAI_API_KEY"); if (!apiKey) { - std::println(stderr, "OPENAI_API_KEY not set"); + std::println(std::cerr, "OPENAI_API_KEY not set"); return 1; } diff --git a/tools/template_smoke.sh b/tools/template_smoke.sh new file mode 100644 index 0000000..cf1a67b --- /dev/null +++ b/tools/template_smoke.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# Compile-check every template in templates/ against the in-repo library. +# +# Renders each template the way `mcpp new` does ({{project.name}} / +# {{self.name}} / {{self.version}}), then swaps the generated version +# dependency for a path dependency on this checkout so templates are +# verified BEFORE a release exists in the index. +# +# Usage: bash tools/template_smoke.sh (requires mcpp on PATH, or $MCPP) +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +MCPP_BIN="${MCPP:-$(command -v mcpp || true)}" +if [[ -z "$MCPP_BIN" || ! -x "$MCPP_BIN" ]]; then + echo "FATAL: set MCPP=/path/to/mcpp or put mcpp on PATH" >&2 + exit 1 +fi + +SELF_NAME="llmapi" +SELF_VERSION="$(sed -n 's/^version *= *"\([^"]*\)".*/\1/p' "$ROOT/mcpp.toml" | head -1)" + +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT + +fail=0 +for tdir in "$ROOT"/templates/*/; do + tname="$(basename "$tdir")" + proj="$TMP/smoke_$tname" + mkdir -p "$proj" + + # render: strip .in, expand the placeholder vocabulary + while IFS= read -r -d '' f; do + rel="${f#"$tdir"}" + case "$rel" in template.toml) continue ;; esac + dest="$proj/${rel%.in}" + mkdir -p "$(dirname "$dest")" + if [[ "$f" == *.in ]]; then + sed -e "s/{{project\.name}}/smoke_$tname/g" \ + -e "s/{{self\.name}}/$SELF_NAME/g" \ + -e "s/{{self\.version}}/$SELF_VERSION/g" "$f" > "$dest" + else + cp "$f" "$dest" + fi + done < <(find "$tdir" -type f -print0) + + # build against this checkout, not the (possibly unreleased) index version + python3 - "$proj/mcpp.toml" "$ROOT" <<'EOF' +import sys, re, pathlib +p, root = pathlib.Path(sys.argv[1]), sys.argv[2] +t = p.read_text() +t = re.sub(r'llmapi *= *"[^"]*"', f'llmapi = {{ path = "{root}" }}', t) +p.write_text(t) +EOF + + echo "=== template: $tname ===" + if ! (cd "$proj" && "$MCPP_BIN" build); then + echo "FAIL: template '$tname' does not compile" >&2 + fail=1 + fi +done + +[[ $fail -eq 0 ]] && echo "All templates compile." +exit $fail From 4894499ec400becc3c09558dd2ca15aad39d7b94 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Wed, 10 Jun 2026 11:10:44 +0800 Subject: [PATCH 2/2] ci: run template smoke inside the workspace (xlings shim needs the pin) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mktemp dirs live outside the repo, where the `mcpp` shim has no workspace .xlings.json to resolve — CI failed with "'mcpp' is not installed". Render/build under target/template-smoke instead. --- tools/template_smoke.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/template_smoke.sh b/tools/template_smoke.sh index cf1a67b..af1da75 100644 --- a/tools/template_smoke.sh +++ b/tools/template_smoke.sh @@ -19,7 +19,11 @@ fi SELF_NAME="llmapi" SELF_VERSION="$(sed -n 's/^version *= *"\([^"]*\)".*/\1/p' "$ROOT/mcpp.toml" | head -1)" -TMP="$(mktemp -d)" +# Stay inside the repo so the workspace mcpp pin (.xlings.json) still +# resolves the `mcpp` shim; target/ is gitignored. +TMP="$ROOT/target/template-smoke" +rm -rf "$TMP" +mkdir -p "$TMP" trap 'rm -rf "$TMP"' EXIT fail=0