Daily GitHub Copilot News Fetcher #456
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: Daily GitHub Copilot News Fetcher | |
| on: | |
| workflow_dispatch: # Manual trigger for demos | |
| push: | |
| branches: [ "main" ] # Run on push to main | |
| schedule: | |
| - cron: '0 0 * * *' # Runs at 00:00 UTC every day | |
| # Ensure only one workflow runs at a time | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }} | |
| jobs: | |
| fetch-news: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 # Prevent runaway jobs | |
| permissions: | |
| contents: write | |
| steps: | |
| # Security check for API key exposure | |
| - name: Check for exposed secrets | |
| run: | | |
| if [ -z "$DEEPSEEK_API_KEY" ]; then | |
| echo "Error: DEEPSEEK_API_KEY secret is not set!" | |
| exit 1 | |
| fi | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 # Shallow clone for faster checkout | |
| ref: main # Explicitly checkout main branch | |
| token: ${{ secrets.GITHUB_TOKEN }} # Explicit token for push access | |
| # Security scan for exposed secrets in the repo | |
| - name: Check for exposed API keys | |
| run: | | |
| if grep -r "DEEPSEEK_API_KEY=sk-" . ; then | |
| echo "WARNING: Found exposed API key in repository!" | |
| echo "Please remove the exposed key and regenerate it immediately!" | |
| exit 1 | |
| fi | |
| # Create requirements.txt if it doesn't exist | |
| - name: Ensure requirements.txt exists | |
| run: | | |
| mkdir -p .github/scripts | |
| if [ ! -f .github/scripts/requirements.txt ]; then | |
| echo "requests>=2.31.0" > .github/scripts/requirements.txt | |
| echo "python-dateutil>=2.8.2" >> .github/scripts/requirements.txt | |
| fi | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| cache: 'pip' # Enable pip caching | |
| cache-dependency-path: '.github/scripts/requirements.txt' | |
| # Cache the news archive to avoid repeated git operations | |
| - name: Cache news archive | |
| uses: actions/cache@v4 | |
| with: | |
| path: news-archive | |
| key: news-archive-${{ github.run_id }} | |
| restore-keys: | | |
| news-archive- | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip --quiet | |
| pip install --quiet -r .github/scripts/requirements.txt | |
| # Ensure script directory and files exist | |
| - name: Verify script setup | |
| run: | | |
| mkdir -p .github/scripts | |
| # Only create fetch_news.py if it doesn't exist | |
| if [ ! -f .github/scripts/fetch_news.py ]; then | |
| echo "Error: fetch_news.py script is missing!" | |
| exit 1 | |
| fi | |
| # Ensure proper permissions | |
| chmod +x .github/scripts/fetch_news.py | |
| - name: Archive previous news | |
| id: archive | |
| continue-on-error: true # Don't fail if no previous news exists | |
| run: | | |
| if [ -f latest-github-news.md ]; then | |
| mkdir -p news-archive | |
| ARCHIVE_NAME="github-news-$(date -d 'yesterday' +'%Y-%m-%d').md" | |
| mv latest-github-news.md "news-archive/$ARCHIVE_NAME" | |
| echo "archived=true" >> $GITHUB_OUTPUT | |
| echo "archive_name=$ARCHIVE_NAME" >> $GITHUB_OUTPUT | |
| else | |
| echo "archived=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Fetch latest news | |
| id: fetch | |
| run: | | |
| # Ensure the script runs with proper error handling | |
| set -e | |
| # Change to repo root before running script | |
| cd $GITHUB_WORKSPACE | |
| python .github/scripts/fetch_news.py | |
| if [ ! -f latest-github-news.md ]; then | |
| echo "Error: News fetching failed to create output file!" | |
| exit 1 | |
| fi | |
| # Upload the news files as artifacts for potential reuse | |
| - name: Upload news artifacts | |
| if: success() # Only if previous steps succeeded | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: copilot-news | |
| path: | | |
| latest-github-news.md | |
| news-archive/*.md | |
| retention-days: 7 # Limit retention to save storage | |
| - name: Configure Git and Handle Changes | |
| if: success() # Only if previous steps succeeded | |
| run: | | |
| git config --local user.email "action@github.com" | |
| git config --local user.name "GitHub Action" | |
| # Ensure we're on the latest main and in repo root | |
| cd $GITHUB_WORKSPACE | |
| git fetch origin main | |
| git reset --hard origin/main | |
| # Re-apply our changes | |
| if [ -f latest-github-news.md ]; then | |
| mkdir -p news-archive | |
| if [ -n "$(ls -A news-archive 2>/dev/null)" ]; then | |
| git add news-archive/ | |
| fi | |
| git add latest-github-news.md | |
| if [[ -n "$(git status --porcelain)" ]]; then | |
| git commit -m "Update GitHub Copilot news for $(date +'%Y-%m-%d')" | |
| # Try pushing multiple times with exponential backoff | |
| max_attempts=3 | |
| attempt=1 | |
| while [ $attempt -le $max_attempts ]; do | |
| if git push origin main; then | |
| break | |
| fi | |
| sleep $((2 ** $attempt)) | |
| attempt=$((attempt + 1)) | |
| if [ $attempt -le $max_attempts ]; then | |
| echo "Push failed, retrying..." | |
| git pull --rebase origin main | |
| fi | |
| done | |
| if [ $attempt -gt $max_attempts ]; then | |
| echo "Failed to push after $max_attempts attempts" | |
| exit 1 | |
| fi | |
| else | |
| echo "No changes to commit" | |
| fi | |
| else | |
| echo "No news file found to commit" | |
| exit 1 | |
| fi | |
| # Clean up old artifacts to save space | |
| - name: Cleanup old artifacts | |
| if: always() # Run even if previous steps failed | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const response = await github.rest.actions.listArtifactsForRepo({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| }); | |
| const artifacts = response.data.artifacts | |
| .filter(artifact => | |
| artifact.name === 'copilot-news' && | |
| new Date(artifact.created_at) < new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) | |
| ); | |
| for (const artifact of artifacts) { | |
| await github.rest.actions.deleteArtifact({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| artifact_id: artifact.id, | |
| }); | |
| } |