Skip to content
Open
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
69 changes: 66 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,22 @@ on:

jobs:
build:
name: Build EPUB (${{ matrix.lang }})
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.continue-on-error == true }}
permissions:
contents: write
contents: read
strategy:
# Don't cancel other languages if one fails — we still want to release
# whichever languages built successfully.
fail-fast: false
matrix:
lang: [en, vi, zh]
include:
# zh/ content is scaffolded but not yet populated; allow this leg
# to fail without blocking the release of en/vi EPUBs.
- lang: zh
continue-on-error: true

steps:
- uses: actions/checkout@v4
Expand All @@ -25,11 +38,61 @@ jobs:
- name: Install Mermaid CLI
run: npm install -g @mermaid-js/mermaid-cli

- name: Install Python dependencies
run: |
uv venv
uv pip install -r scripts/requirements-dev.txt

- name: Build EPUB
run: uv run scripts/build_epub.py
run: |
echo '{"args":["--no-sandbox","--disable-setuid-sandbox"]}' > /tmp/puppeteer-ci.json
uv run scripts/build_epub.py \
--lang ${{ matrix.lang }} \
--puppeteer-config /tmp/puppeteer-ci.json

- name: Upload EPUB artifact
uses: actions/upload-artifact@v4
with:
name: epub-${{ matrix.lang }}
path: claude-howto-guide*.epub
if-no-files-found: error
retention-days: 7

release:
name: Publish GitHub Release
needs: build
# Release even if some language builds failed — we still publish the
# artifacts that did build. `needs.build.result != 'cancelled'` guards
# against manually cancelled runs.
if: ${{ always() && needs.build.result != 'cancelled' }}
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Download all EPUB artifacts
uses: actions/download-artifact@v4
with:
path: dist
pattern: epub-*
merge-multiple: true

- name: List built artifacts
run: ls -lh dist/ 2>/dev/null || echo "dist/ is empty or does not exist"

- name: Check artifacts exist
id: check_artifacts
run: |
count=$(ls dist/*.epub 2>/dev/null | wc -l)
echo "count=$count" >> $GITHUB_OUTPUT
if [ "$count" -eq 0 ]; then
echo "No EPUB artifacts were built — skipping release."
exit 1
fi

- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: claude-howto-guide.epub
files: dist/*.epub
generate_release_notes: true
fail_on_unmatched_files: true
58 changes: 38 additions & 20 deletions scripts/build_epub.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,29 @@ class CoverGenerationError(EPUBBuildError):
# Configuration and State
# =============================================================================

# Language metadata: maps lang code → (subdir, output filename, title, subtitle).
# "" subdir means use the repo root (English content lives at the top level).
_LANG_METADATA: dict[str, tuple[str, str, str, str]] = {
"en": (
"",
"claude-howto-guide.epub",
"Claude Code How-To Guide",
"Master Claude Code in a Weekend",
),
"vi": (
"vi",
"claude-howto-guide-vi.epub",
"Hướng Dẫn Claude Code",
"Làm chủ Claude Code trong một cuối tuần",
),
"zh": (
"zh",
"claude-howto-guide-zh.epub",
"Claude Code 使用指南",
"一个周末掌握 Claude Code",
),
}


@dataclass
class EPUBConfig:
Expand All @@ -105,15 +128,10 @@ class EPUBConfig:
# EPUB Metadata
identifier: str = "claude-howto-guide"
title: str = "Claude Code How-To Guide"
subtitle: str = "Master Claude Code in a Weekend"
language: str = "en"
author: str = "Claude Code Community"

# Language-specific metadata
vi_title: str = "Hướng Dẫn Claude Code"
vi_subtitle: str = "Làm chủ Claude Code trong một cuối tuần"
en_title: str = "Claude Code How-To Guide"
en_subtitle: str = "Master Claude Code in a Weekend"

# Cover Settings
cover_width: int = 600
cover_height: int = 900
Expand Down Expand Up @@ -906,7 +924,9 @@ def build_epub_async(

# Add cover
logger.info("Generating cover image...")
cover_data = create_cover_image(config, logger)
cover_data = create_cover_image(
config, logger, title=config.title, subtitle=config.subtitle
)
book.set_cover("cover.png", cover_data)

# Add CSS
Expand Down Expand Up @@ -1057,8 +1077,11 @@ def main() -> int:
"--lang",
type=str,
default="en",
choices=["en", "vi"],
help="Language code: 'en' for English, 'vi' for Vietnamese (default: en)",
choices=["en", "vi", "zh"],
help=(
"Language code: 'en' for English, 'vi' for Vietnamese, "
"'zh' for Chinese (default: en)"
),
)
parser.add_argument(
"--puppeteer-config",
Expand All @@ -1073,17 +1096,11 @@ def main() -> int:
repo_root = args.root if args.root else Path(__file__).parent.parent
repo_root = repo_root.resolve()

# Set language-specific paths and metadata
if args.lang == "vi":
root = repo_root / "vi"
output = args.output or (repo_root / "claude-howto-guide-vi.epub")
title = EPUBConfig.vi_title
language = "vi"
else:
root = repo_root
output = args.output or (repo_root / "claude-howto-guide.epub")
title = EPUBConfig.en_title
language = "en"
# Resolve language-specific paths and metadata from the module-level constant.
subdir, default_output_name, title, subtitle = _LANG_METADATA[args.lang]
root = repo_root / subdir if subdir else repo_root
output = args.output or (repo_root / default_output_name)
language = args.lang

root = root.resolve()
output = output.resolve()
Expand All @@ -1094,6 +1111,7 @@ def main() -> int:
output_path=output,
language=language,
title=title,
subtitle=subtitle,
mmdc_path=args.mmdc_path,
puppeteer_config=args.puppeteer_config,
)
Expand Down