Skip to content

Commit 65bd3fb

Browse files
Merge pull request #50 from Andre-Diamond:main
Main
2 parents 2d8bbb3 + f28fd67 commit 65bd3fb

File tree

6 files changed

+1046
-0
lines changed

6 files changed

+1046
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Release GitHub Stats Action
2+
3+
on:
4+
push:
5+
tags:
6+
- 'github-stats-action@*'
7+
paths:
8+
- 'apps/shared-backend/github-actions/github-stats-action/**'
9+
10+
jobs:
11+
release:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v4
16+
17+
- name: Create Release
18+
id: create_release
19+
uses: softprops/action-gh-release@v1
20+
env:
21+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22+
with:
23+
tag_name: ${{ github.ref_name }}
24+
name: GitHub Stats Action ${{ github.ref_name }}
25+
body: |
26+
Release of GitHub Stats Action
27+
28+
Changes in this release:
29+
- See commit history for details
30+
draft: false
31+
prerelease: false
32+
files: |
33+
apps/shared-backend/github-actions/github-stats-action/action.yml
34+
apps/shared-backend/github-actions/github-stats-action/getStats.js
35+
apps/shared-backend/github-actions/github-stats-action/package.json
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# GitHub Stats Action
2+
3+
This GitHub Action triggers the GitHub stats collection background function to fetch and store repository statistics in the Supabase database.
4+
5+
## Usage
6+
7+
### Basic Usage
8+
9+
```yaml
10+
- name: Collect GitHub Stats
11+
uses: ./.github/actions/github-stats-action
12+
with:
13+
org: 'your-organization'
14+
repo: 'your-repository'
15+
github-token: ${{ secrets.GITHUB_TOKEN }}
16+
```
17+
18+
### With Custom GitHub Token
19+
20+
```yaml
21+
- name: Collect GitHub Stats
22+
uses: ./.github/actions/github-stats-action
23+
with:
24+
org: 'your-organization'
25+
repo: 'your-repository'
26+
github-token: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
27+
```
28+
29+
## Inputs
30+
31+
| Input | Description | Required | Default |
32+
|-------|-------------|----------|---------|
33+
| `org` | GitHub organization name | Yes | - |
34+
| `repo` | GitHub repository name | Yes | - |
35+
| `github-token` | GitHub token for API access | Yes | - |
36+
37+
## What it does
38+
39+
This action:
40+
41+
1. Validates the provided inputs (org, repo, github-token)
42+
2. Triggers the Netlify background function `github-stats-background`
43+
3. The background function fetches and stores:
44+
- Repository information
45+
- Contributors
46+
- Commits with detailed statistics
47+
- Pull requests with reviews, assignees, labels, and commits
48+
- Issues with assignees and labels
49+
50+
## Requirements
51+
52+
- Node.js 18 or higher
53+
- Valid GitHub token with appropriate permissions
54+
- Access to the Netlify function endpoint
55+
56+
## Error Handling
57+
58+
The action includes comprehensive error handling:
59+
- Input validation with detailed error messages
60+
- HTTP request error handling
61+
- Graceful handling of various response types
62+
- Proper exit codes for CI/CD integration
63+
64+
## Security
65+
66+
- GitHub tokens are redacted in logs
67+
- Input validation prevents injection attacks
68+
- Uses HTTPS for all external requests
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# action.yml
2+
name: "GitHub Stats"
3+
description: "Trigger GitHub stats collection via Netlify background function"
4+
5+
inputs:
6+
org:
7+
description: "GitHub organization name"
8+
required: true
9+
repo:
10+
description: "GitHub repository name"
11+
required: true
12+
github-token:
13+
description: "GitHub token for API access"
14+
required: true
15+
16+
runs:
17+
using: composite
18+
steps:
19+
- name: Set up Node.js
20+
uses: actions/setup-node@v3
21+
with:
22+
node-version: "18"
23+
24+
- name: Install dependencies
25+
working-directory: ${{ github.action_path }}
26+
shell: bash
27+
run: npm install
28+
29+
- name: Run stats script
30+
shell: bash
31+
run: node ${{ github.action_path }}/getStats.js
32+
env:
33+
ORG: ${{ inputs.org }}
34+
REPO: ${{ inputs.repo }}
35+
GITHUB_TOKEN: ${{ inputs.github-token }}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#!/usr/bin/env node
2+
3+
import fetch from 'node-fetch'
4+
5+
// —— Config from env / GitHub Action inputs ——
6+
const ORG = process.env.ORG
7+
const REPO = process.env.REPO
8+
const GITHUB_TOKEN = process.env.GITHUB_TOKEN
9+
10+
// Debug logging
11+
console.log('🔍 Environment variables:')
12+
console.log(` ORG: ${process.env.ORG}`)
13+
console.log(` REPO: ${process.env.REPO}`)
14+
console.log(` GITHUB_TOKEN: ${GITHUB_TOKEN ? '[REDACTED]' : '(not provided)'}`)
15+
16+
// Input validation function
17+
function validateInputs() {
18+
const errors = []
19+
20+
// Validate ORG
21+
if (!ORG) {
22+
errors.push('ORG is required but not provided')
23+
} else if (typeof ORG !== 'string' || ORG.trim() === '') {
24+
errors.push('ORG must be a non-empty string')
25+
} else if (!/^[a-zA-Z0-9_-]+$/.test(ORG.trim())) {
26+
errors.push('ORG must be a valid GitHub organization name (alphanumeric, hyphens, underscores only)')
27+
}
28+
29+
// Validate REPO
30+
if (!REPO) {
31+
errors.push('REPO is required but not provided')
32+
} else if (typeof REPO !== 'string' || REPO.trim() === '') {
33+
errors.push('REPO must be a non-empty string')
34+
} else if (!/^[a-zA-Z0-9._-]+$/.test(REPO.trim())) {
35+
errors.push('REPO must be a valid GitHub repository name (alphanumeric, dots, hyphens, underscores only)')
36+
}
37+
38+
// Validate GITHUB_TOKEN
39+
if (!GITHUB_TOKEN) {
40+
errors.push('GITHUB_TOKEN is required but not provided')
41+
} else if (typeof GITHUB_TOKEN !== 'string' || GITHUB_TOKEN.trim() === '') {
42+
errors.push('GITHUB_TOKEN must be a non-empty string')
43+
}
44+
45+
return errors
46+
}
47+
48+
// Parse and validate inputs
49+
const validationErrors = validateInputs()
50+
51+
if (validationErrors.length > 0) {
52+
console.error('❌ Input validation failed:')
53+
validationErrors.forEach(error => console.error(` - ${error}`))
54+
process.exit(1)
55+
}
56+
57+
// Parse validated inputs
58+
const parsedOrg = ORG.trim()
59+
const parsedRepo = REPO.trim()
60+
const parsedGithubToken = GITHUB_TOKEN.trim()
61+
62+
console.log('📊 Resolved values:')
63+
console.log(` ORG: ${parsedOrg}`)
64+
console.log(` REPO: ${parsedRepo}`)
65+
66+
// Netlify function configuration - hardcoded URL
67+
const NETLIFY_FUNCTION_URL = 'https://glittering-chebakia-09bd42.netlify.app/.netlify/functions/github-stats-background'
68+
69+
// Helper function to make HTTP requests
70+
async function makeRequest(url, options = {}) {
71+
try {
72+
const response = await fetch(url, {
73+
headers: {
74+
'Content-Type': 'application/json',
75+
...options.headers
76+
},
77+
...options
78+
})
79+
80+
if (!response.ok) {
81+
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
82+
}
83+
84+
// Check if response has content
85+
const contentType = response.headers.get('content-type')
86+
const text = await response.text()
87+
88+
if (!text || text.trim() === '') {
89+
console.log('⚠️ Empty response received')
90+
return { success: true, message: 'Empty response' }
91+
}
92+
93+
// Try to parse as JSON, but handle non-JSON responses gracefully
94+
try {
95+
return JSON.parse(text)
96+
} catch (parseError) {
97+
console.log(`⚠️ Non-JSON response received: ${text.substring(0, 200)}...`)
98+
return { success: true, message: 'Non-JSON response', raw: text }
99+
}
100+
} catch (error) {
101+
console.error(`❌ Request failed: ${error.message}`)
102+
throw error
103+
}
104+
}
105+
106+
// Main function
107+
async function main() {
108+
console.log(`🚀 Starting GitHub stats collection for repository: ${parsedOrg}/${parsedRepo}`)
109+
110+
// Trigger the Netlify background function
111+
console.log('📡 Triggering Netlify background function...')
112+
113+
const functionUrl = new URL(NETLIFY_FUNCTION_URL)
114+
functionUrl.searchParams.set('org', parsedOrg)
115+
functionUrl.searchParams.set('repo', parsedRepo)
116+
functionUrl.searchParams.set('githubToken', parsedGithubToken)
117+
118+
console.log(`🌐 Calling URL: ${functionUrl.toString().replace(parsedGithubToken, '[REDACTED]')}`)
119+
120+
try {
121+
const functionResponse = await makeRequest(functionUrl.toString(), {
122+
method: 'GET'
123+
})
124+
125+
console.log('✅ Background function triggered successfully')
126+
console.log(`📊 Response: ${JSON.stringify(functionResponse, null, 2)}`)
127+
128+
// If the response indicates success (even if it's not JSON), we're done
129+
if (functionResponse.success !== false) {
130+
console.log('✅ Background function appears to have been triggered successfully')
131+
console.log('🎉 GitHub stats collection initiated - the background function will handle the rest')
132+
process.exit(0)
133+
} else {
134+
console.error('❌ Background function returned an error')
135+
process.exit(1)
136+
}
137+
} catch (error) {
138+
console.error('❌ Failed to trigger background function:', error.message)
139+
process.exit(1)
140+
}
141+
}
142+
143+
// Run the main function
144+
main().catch(error => {
145+
console.error('❌ Fatal error:', error.message)
146+
process.exit(1)
147+
})
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "github-stats-action",
3+
"version": "1.0.0",
4+
"description": "GitHub Action to trigger GitHub stats collection",
5+
"main": "getStats.js",
6+
"type": "module",
7+
"scripts": {
8+
"test": "echo \"Error: no test specified\" && exit 1"
9+
},
10+
"keywords": [
11+
"github-action",
12+
"github-stats",
13+
"netlify"
14+
],
15+
"author": "",
16+
"license": "MIT",
17+
"dependencies": {
18+
"node-fetch": "^3.3.2"
19+
},
20+
"engines": {
21+
"node": ">=18.0.0"
22+
}
23+
}

0 commit comments

Comments
 (0)