docs: add CLI changelog entry for v0.136.0 (#1168) #143
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Sync Japanese Docs | |
| on: | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - 'docs/**/*.mdx' | |
| - 'docs/**/*.md' | |
| - '!docs/jp/**' | |
| workflow_dispatch: | |
| concurrency: | |
| group: sync-jp-docs | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| sync-jp-docs: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| env: | |
| DROID_VERSION: '0.108.0' | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Validate API key | |
| env: | |
| FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| [ -n "${FACTORY_API_KEY:-}" ] || { echo "FACTORY_API_KEY is missing"; exit 1; } | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '22' | |
| - name: Install pinned Droid CLI | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| npm install -g "droid@${DROID_VERSION}" | |
| NPM_GLOBAL_BIN="$(npm config get prefix)/bin" | |
| echo "$NPM_GLOBAL_BIN" >> "$GITHUB_PATH" | |
| "$NPM_GLOBAL_BIN/droid" --version | |
| - name: Sync and translate changed English docs to Japanese | |
| id: sync | |
| env: | |
| FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| BRANCH="docs/auto-sync-jp-docs" | |
| TITLE="docs: sync and translate English doc changes to Japanese" | |
| git fetch origin main | |
| git fetch origin "$BRANCH" || true | |
| BEFORE="${{ github.event.before }}" | |
| if [ "$BEFORE" = "0000000000000000000000000000000000000000" ] || [ -z "$BEFORE" ]; then | |
| BEFORE="$(git rev-parse HEAD~1 2>/dev/null || echo "")" | |
| fi | |
| if [ -z "$BEFORE" ]; then | |
| echo "Cannot determine base commit, skipping." | |
| { | |
| echo "synced=false" | |
| echo "reason=no_base_commit" | |
| } >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if [ "${{ github.actor }}" = "github-actions[bot]" ]; then | |
| echo "Skipping JP sync for github-actions[bot] push." | |
| { | |
| echo "synced=false" | |
| echo "reason=bot_push" | |
| } >> "$GITHUB_OUTPUT" | |
| { | |
| echo "## JP sync" | |
| echo "- Skipped: push actor was github-actions[bot]." | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| exit 0 | |
| fi | |
| changed_entries_file="$(mktemp)" | |
| git diff \ | |
| --name-status \ | |
| --find-renames \ | |
| --diff-filter=ACMR \ | |
| "$BEFORE" \ | |
| "${{ github.sha }}" \ | |
| -- 'docs/**/*.mdx' 'docs/**/*.md' ':!docs/jp/**' \ | |
| > "$changed_entries_file" | |
| if [ ! -s "$changed_entries_file" ]; then | |
| echo "No English doc changes detected." | |
| { | |
| echo "synced=false" | |
| echo "reason=no_english_changes" | |
| } >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| git config --local user.email "github-actions[bot]@users.noreply.github.com" | |
| git config --local user.name "github-actions[bot]" | |
| git checkout -B "$BRANCH" "origin/main" | |
| if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH"; then | |
| mapfile -t existing_jp_files < <(git diff --name-only --diff-filter=ACMR origin/main...origin/"$BRANCH" -- docs/jp) | |
| if [ "${#existing_jp_files[@]}" -gt 0 ]; then | |
| git checkout "origin/$BRANCH" -- "${existing_jp_files[@]}" | |
| fi | |
| fi | |
| manifest_file="$(mktemp)" | |
| prompt_file="$(mktemp)" | |
| snapshot_root="$(mktemp -d)" | |
| cleanup() { | |
| rm -f "$changed_entries_file" "$manifest_file" "$prompt_file" | |
| rm -rf "$snapshot_root" | |
| } | |
| trap cleanup EXIT | |
| jp_targets=0 | |
| while IFS=$'\t' read -r status first second; do | |
| [ -n "$status" ] || continue | |
| source_path="$first" | |
| old_source_path="" | |
| if [[ "$status" == R* ]]; then | |
| old_source_path="$first" | |
| source_path="$second" | |
| fi | |
| jp_file="${source_path/docs\//docs/jp/}" | |
| snapshot_path="$snapshot_root/$source_path" | |
| mkdir -p "$(dirname "$snapshot_path")" | |
| mkdir -p "$(dirname "$jp_file")" | |
| if [ -n "$old_source_path" ]; then | |
| old_jp_file="${old_source_path/docs\//docs/jp/}" | |
| if [ -e "$old_jp_file" ] && [ "$old_jp_file" != "$jp_file" ]; then | |
| if [ -e "$jp_file" ]; then | |
| rm -f "$old_jp_file" | |
| else | |
| mv "$old_jp_file" "$jp_file" | |
| fi | |
| fi | |
| fi | |
| git show "${{ github.sha }}:$source_path" > "$snapshot_path" | |
| jp_targets=$((jp_targets + 1)) | |
| done < "$changed_entries_file" | |
| if [ "$jp_targets" -eq 0 ]; then | |
| echo "No Japanese docs targets were generated." | |
| { | |
| echo "synced=false" | |
| echo "reason=no_jp_targets" | |
| } >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| BEFORE="$BEFORE" GITHUB_SHA="${{ github.sha }}" CHANGED_ENTRIES_FILE="$changed_entries_file" SNAPSHOT_ROOT="$snapshot_root" python3 - <<'PY' > "$manifest_file" | |
| from pathlib import Path | |
| import os | |
| import subprocess | |
| before = os.environ["BEFORE"] | |
| sha = os.environ["GITHUB_SHA"] | |
| changed_entries_file = Path(os.environ["CHANGED_ENTRIES_FILE"]) | |
| snapshot_root = Path(os.environ["SNAPSHOT_ROOT"]) | |
| changed_entries = [ | |
| line.strip().split("\t") | |
| for line in changed_entries_file.read_text().splitlines() | |
| if line.strip() | |
| ] | |
| def run(*args: str) -> str: | |
| return subprocess.run(args, check=True, capture_output=True, text=True).stdout | |
| print("## Translation manifest") | |
| print() | |
| print("Use the English diffs below to scope edits, but read the full English source file and current Japanese target file before you edit.") | |
| print() | |
| for index, entry in enumerate(changed_entries, start=1): | |
| status = entry[0] | |
| old_source = None | |
| if status.startswith("R"): | |
| old_source = entry[1] | |
| source = entry[2] | |
| else: | |
| source = entry[1] | |
| snapshot_path = snapshot_root / source | |
| target = source.replace("docs/", "docs/jp/", 1) | |
| target_exists = Path(target).exists() | |
| diff_paths = [source] if old_source is None else [old_source, source] | |
| diff = run("git", "diff", "--find-renames", "--unified=2", before, sha, "--", *diff_paths).rstrip() | |
| if not diff: | |
| diff = "(No textual diff was produced; inspect the file pair directly before deciding no change is needed.)" | |
| print(f"### {index}. `{source}`") | |
| if old_source is not None: | |
| print(f"- English path changed from `{old_source}`") | |
| print(f"- English source snapshot: `{snapshot_path}`") | |
| print(f"- Japanese target file: `{target}`") | |
| print(f"- Existing Japanese target before this run: {'yes' if target_exists else 'no'}") | |
| print("- Preferred edit scope: only the Japanese sections affected by the English diff below, unless the file is new or the diff forces a broader rewrite.") | |
| print() | |
| print("```diff") | |
| print(diff) | |
| print("```") | |
| print() | |
| PY | |
| { | |
| cat .github/prompts/sync-jp-docs.md | |
| echo | |
| cat "$manifest_file" | |
| } > "$prompt_file" | |
| droid exec \ | |
| --auto medium \ | |
| --model gpt-5.4 \ | |
| --reasoning-effort high \ | |
| --disabled-tools execute-cli \ | |
| --file "$prompt_file" | |
| git add docs/jp/ | |
| if git diff --cached --quiet; then | |
| echo "No Japanese docs changes to commit." | |
| { | |
| echo "synced=false" | |
| echo "reason=no_jp_changes" | |
| } >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| changed_count="$(wc -l < "$changed_entries_file" | tr -d ' ')" | |
| { | |
| echo "## JP sync" | |
| echo "- English docs changed: $changed_count" | |
| echo "- Japanese targets requested: $jp_targets" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| { | |
| echo "branch=$BRANCH" | |
| echo "title=$TITLE" | |
| echo "synced=true" | |
| echo "reason=synced" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Upsert PR with synced docs | |
| id: pr | |
| if: steps.sync.outputs.synced == 'true' | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| BRANCH="${{ steps.sync.outputs.branch }}" | |
| TITLE="${{ steps.sync.outputs.title }}" | |
| BODY="Auto-generated by the sync-jp-docs Droid Exec workflow. This PR is reused for cumulative JP sync updates, so please review each commit for translation accuracy." | |
| REPO_OWNER="${{ github.repository_owner }}" | |
| find_open_pr_number() { | |
| gh pr list \ | |
| --base main \ | |
| --head "$BRANCH" \ | |
| --state open \ | |
| --json number,isCrossRepository,headRepositoryOwner \ | |
| --jq ".[] | select((.isCrossRepository | not) and .headRepositoryOwner.login == \"$REPO_OWNER\") | .number" \ | |
| | head -n 1 | |
| } | |
| find_reopenable_pr_number() { | |
| gh pr list \ | |
| --base main \ | |
| --head "$BRANCH" \ | |
| --state closed \ | |
| --json number,isCrossRepository,headRepositoryOwner,mergedAt \ | |
| --jq ".[] | select((.isCrossRepository | not) and .headRepositoryOwner.login == \"$REPO_OWNER\" and (.mergedAt == null)) | .number" \ | |
| | head -n 1 | |
| } | |
| git commit -m "$TITLE" | |
| git push --force-with-lease origin "$BRANCH" | |
| open_pr="$(find_open_pr_number)" | |
| if [ -n "$open_pr" ] && [ "$open_pr" != "null" ]; then | |
| gh pr edit "$open_pr" --title "$TITLE" --body "$BODY" | |
| { | |
| echo "pr_number=$open_pr" | |
| echo "action=updated" | |
| } >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| closed_pr="$(find_reopenable_pr_number)" | |
| if [ -n "$closed_pr" ] && [ "$closed_pr" != "null" ]; then | |
| gh pr reopen "$closed_pr" | |
| gh pr edit "$closed_pr" --title "$TITLE" --body "$BODY" | |
| { | |
| echo "pr_number=$closed_pr" | |
| echo "action=reopened" | |
| } >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| gh pr create \ | |
| --base main \ | |
| --head "$BRANCH" \ | |
| --title "$TITLE" \ | |
| --body "$BODY" | |
| created_pr="$(find_open_pr_number)" | |
| { | |
| echo "pr_number=$created_pr" | |
| echo "action=created" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Summary | |
| shell: bash | |
| run: | | |
| if [ "${{ steps.sync.outputs.synced }}" != "true" ]; then | |
| reason="${{ steps.sync.outputs.reason }}" | |
| echo "## JP sync" >> "$GITHUB_STEP_SUMMARY" | |
| case "$reason" in | |
| bot_push) | |
| echo "ℹ️ Skipping JP sync for github-actions[bot] push" | |
| echo "- Skipped: push actor was github-actions[bot]." >> "$GITHUB_STEP_SUMMARY" | |
| ;; | |
| no_jp_changes) | |
| echo "ℹ️ English docs changed, but no JP updates were needed" | |
| echo "- English docs changed, but Droid produced no Japanese doc edits." >> "$GITHUB_STEP_SUMMARY" | |
| ;; | |
| no_jp_targets) | |
| echo "ℹ️ No JP targets were generated" | |
| echo "- English docs changed, but no Japanese target files were derived." >> "$GITHUB_STEP_SUMMARY" | |
| ;; | |
| no_base_commit) | |
| echo "ℹ️ Could not determine base commit for JP sync" | |
| echo "- Skipped: could not determine the base commit for diffing English docs." >> "$GITHUB_STEP_SUMMARY" | |
| ;; | |
| *) | |
| echo "ℹ️ No English doc changes to sync" | |
| echo "- No English documentation changes required translation." >> "$GITHUB_STEP_SUMMARY" | |
| ;; | |
| esac | |
| exit 0 | |
| fi | |
| { | |
| echo "- PR action: ${{ steps.pr.outputs.action }}" | |
| echo "- PR number: #${{ steps.pr.outputs.pr_number }}" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| echo "✅ Japanese docs sync PR ${{ steps.pr.outputs.action }} (#${{ steps.pr.outputs.pr_number }})" |