diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cf92f43..518e0f7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,9 +7,14 @@ on: jobs: build: + name: Build EPUB (${{ matrix.lang }}) runs-on: ubuntu-latest - permissions: - contents: write + 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] steps: - uses: actions/checkout@v4 @@ -25,11 +30,51 @@ 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/ - 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 diff --git a/scripts/build_epub.py b/scripts/build_epub.py index e50b3c5..a0c7f35 100755 --- a/scripts/build_epub.py +++ b/scripts/build_epub.py @@ -113,6 +113,8 @@ class EPUBConfig: 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" + zh_title: str = "Claude Code 使用指南" + zh_subtitle: str = "一个周末掌握 Claude Code" # Cover Settings cover_width: int = 600 @@ -1057,8 +1059,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", @@ -1073,17 +1078,16 @@ 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" + # Set language-specific paths and metadata. + # Each entry: (source root, default output filename, title) + lang_map: dict[str, tuple[Path, str, str]] = { + "en": (repo_root, "claude-howto-guide.epub", EPUBConfig.en_title), + "vi": (repo_root / "vi", "claude-howto-guide-vi.epub", EPUBConfig.vi_title), + "zh": (repo_root / "zh", "claude-howto-guide-zh.epub", EPUBConfig.zh_title), + } + root, default_output_name, title = lang_map[args.lang] + output = args.output or (repo_root / default_output_name) + language = args.lang root = root.resolve() output = output.resolve()