withai

GitHub 자동 인덱스 생성 설정하기

문제 상황

해결 방안 비교

1. 로컬 Python 스크립트 방식

새 파일을 추가할 때마다 수동으로 다음 작업을 수행해야 함:

  1. 새 마크다운 파일 생성
  2. python update_indexes.py 실행
  3. git add
  4. git commit
  5. git push

2. GitHub Actions 방식

새 파일을 추가할 때 다음 작업만 수행:

  1. 새 마크다운 파일 생성
  2. git add
  3. git commit
  4. git push

GitHub Actions가 자동으로:

GitHub Actions 설정 방법

1. 워크플로우 디렉토리 생성

mkdir -p .github/workflows

2. 워크플로우 파일 생성

.github/workflows/update-indexes.yml 파일을 다음 내용으로 생성:

name: Update Directory Indexes

# markdown 파일이 변경될 때마다 실행
on:
  push:
    paths:
      - '**/*.md'
    branches:
      - main  # main 브랜치에 push될 때만 실행

jobs:
  update-indexes:
    runs-on: ubuntu-latest
    permissions:
      contents: write  # 저장소에 쓰기 권한 필요
    steps:
      # 저장소 체크아웃
      - uses: actions/checkout@v4

      # Python 설치
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.x'

      # index.md 파일들 업데이트
      - name: Update directory indexes
        run: |
          python - <<EOF
          import os
          import re
          from datetime import datetime
          from pathlib import Path

          def update_directory_index(directory):
              # 디렉토리 내의 날짜형식 마크다운 파일 찾기
              date_files = []
              dir_path = Path(directory)
              
              for file in dir_path.glob('*.md'):
                  if re.match(r'\d{8}\.md$', file.name):
                      date_files.append(file)

              if not date_files:
                  return False

              # 날짜순 정렬 (최신순)
              date_files.sort(reverse=True)

              # 기존 index.md 읽기
              index_path = dir_path / 'index.md'
              if index_path.exists():
                  with open(index_path, 'r', encoding='utf-8') as f:
                      existing_content = f.read()
              else:
                  existing_content = f"# {dir_path.name}\n\n"

              # Last updated 섹션 찾기 또는 추가하기
              last_updated = f"# Last updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
              if '# Last updated:' in existing_content:
                  # 기존 Last updated 섹션 교체
                  existing_content = re.sub(r'# Last updated:.*?\n\n', last_updated, existing_content, flags=re.DOTALL)
              else:
                  # 첫 번째 제목 다음에 Last updated 섹션 추가
                  first_title_end = existing_content.find('\n\n', existing_content.find('#'))
                  if first_title_end != -1:
                      existing_content = existing_content[:first_title_end + 2] + last_updated + existing_content[first_title_end + 2:]
                  else:
                      existing_content += '\n' + last_updated

              # 새로운 파일 링크 추가
              for file in date_files:
                  date = re.match(r'(\d{4})(\d{2})(\d{2})\.md', file.name)
                  if date:
                      formatted_date = f"{date.group(1)}-{date.group(2)}-{date.group(3)}"
                      link = f"- [{formatted_date}]({file.name})\n"
                      if link not in existing_content:
                          # Last updated 섹션 바로 다음에 새 링크 추가
                          insert_pos = existing_content.find('\n\n', existing_content.find('# Last updated:')) + 2
                          existing_content = existing_content[:insert_pos] + link + existing_content[insert_pos:]

              # index.md 파일 쓰기
              with open(index_path, 'w', encoding='utf-8') as f:
                  f.write(existing_content)

              return True

          # 모든 디렉토리 검사
          updated_dirs = []
          for root, dirs, files in os.walk('.'):
              # .git 디렉토리 제외
              if '.git' in root:
                  continue
                  
              # 날짜형식 마크다운 파일이 있는 디렉토리 찾기
              has_date_files = any(re.match(r'\d{8}\.md$', f) for f in files)
              
              if has_date_files:
                  if update_directory_index(root):
                      updated_dirs.append(root)

          # 결과 출력
          if updated_dirs:
              print("Updated indexes in:")
              for dir_path in updated_dirs:
                  print(f"- {dir_path}")
          else:
              print("No directories needed updating")
          EOF

      # 변경사항이 있으면 커밋하고 푸시
      - name: Commit and push if changed
        run: |
          git config --local user.email "github-actions[bot]@users.noreply.github.com"
          git config --local user.name "github-actions[bot]"
          git add "**/index.md"
          git diff --quiet && git diff --staged --quiet || (git commit -m "Update directory indexes [skip ci]" && git push)

3. 워크플로우 파일 커밋

git add .github/workflows/update-indexes.yml
git commit -m "Add index update workflow"
git push

주요 개선사항

  1. 기존 index.md 파일의 형식 유지
  2. Last updated 섹션 자동 관리
    • 없으면 첫 번째 제목 다음에 추가
    • 있으면 시간만 업데이트
  3. 새로운 파일 링크를 Last updated 섹션 바로 다음에 추가
  4. 중복 링크 방지

작동 방식

  1. main 브랜치에 마크다운 파일이 추가/변경/삭제될 때마다 워크플로우가 실행됩니다.
  2. 워크플로우는 저장소의 모든 디렉토리를 검사하여 YYYYMMDD.md 형식의 파일이 있는 디렉토리를 찾습니다.
  3. 해당 디렉토리의 index.md 파일을 업데이트합니다:
    • 기존 형식 유지
    • Last updated 섹션 관리
    • 새 파일 링크 추가
  4. 변경사항이 있는 경우에만 새로운 커밋을 생성하고 푸시합니다.

주의사항

워크플로우 파일에 사용된 언어 설명

워크플로우 파일(.yml)은 세 가지 다른 언어/형식이 결합되어 있습니다:

  1. YAML(YAML Ain’t Markup Language):
    • 전체 워크플로우 구조를 정의하는 데이터 직렬화 형식
    • 주요 특징:
      • 들여쓰기로 계층 구조 표현
      • 키-값 쌍으로 데이터 표현
      • -로 목록 항목 표시
      • | 또는 > 기호로 여러 줄의 문자열 표현 ```yaml name: Update Directory Indexes on: push: paths:
        • */.md’ ```
  2. Shell 스크립트:
    • 시스템 명령어를 실행하는 부분
      run: |
      git config --local user.email "github-actions[bot]@users.noreply.github.com"
      git config --local user.name "github-actions[bot]"
      
  3. Python:
    • 실제 파일 처리 로직을 구현하는 부분
      run: |
      python - <<EOF
      import os
      import re
      # ... Python 코드 ...
      EOF
      

GitHub Actions는 이 YAML 파일을 읽고 정의된 단계들을 순차적으로 실행하게 됩니다. YAML은 GitHub Actions의 워크플로우를 정의할 때 사용되는 표준 형식입니다.