fix(bitch-cli): 🐛 use HTML scraping for Forgejo Actions CI status
Forgejo 11.0.8 doesn't expose working Actions API endpoints. Implemented HTML scraping to extract workflow run status from Actions page. - Scrape data-tooltip-content for run status (Success/Failure/Running/Waiting) - Extract run IDs from href patterns - Removed unused forgejoRequest function (API returns 404) - Maintained same WorkflowRun interface for compatibility Verified working with: bitch ci ml Fixes: CI status command now works correctly Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7662f41158
commit
d84370db56
1 changed files with 52 additions and 39 deletions
|
|
@ -17,47 +17,66 @@ export interface CIStatus {
|
|||
status: 'success' | 'failure' | 'pending' | 'no-runs' | 'error'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Forgejo API token from environment
|
||||
*/
|
||||
function getToken(): string | null {
|
||||
return process.env.FORGEJO_TOKEN || null
|
||||
}
|
||||
// NOTE: Forgejo 11.0.8 doesn't expose working Actions API endpoints
|
||||
// Using HTML scraping instead (see scrapeWorkflowRuns below)
|
||||
|
||||
/**
|
||||
* Make an authenticated request to the Forgejo API
|
||||
* Scrape workflow runs from Forgejo Actions HTML page
|
||||
* (Forgejo 11.0.8 doesn't expose Actions API endpoints)
|
||||
*/
|
||||
async function forgejoRequest<T>(endpoint: string): Promise<T | null> {
|
||||
const token = getToken()
|
||||
|
||||
if (!token) {
|
||||
throw new Error('FORGEJO_TOKEN environment variable not set')
|
||||
}
|
||||
|
||||
const url = `${DEFAULT_CONFIG.forgejo.api}${endpoint}`
|
||||
async function scrapeWorkflowRuns(repoName: string): Promise<WorkflowRun[]> {
|
||||
const cleanName = repoName.replace('@lilith/', '').replace(/^lilith-/, '')
|
||||
const url = `${DEFAULT_CONFIG.forgejo.url}/lilith/${cleanName}/actions`
|
||||
|
||||
try {
|
||||
const { statusCode, body } = await request(url, {
|
||||
headers: {
|
||||
Authorization: `token ${token}`,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
if (statusCode === 404) {
|
||||
return null
|
||||
}
|
||||
const { statusCode, body } = await request(url)
|
||||
|
||||
if (statusCode !== 200) {
|
||||
throw new Error(`Forgejo API returned ${statusCode}`)
|
||||
return []
|
||||
}
|
||||
|
||||
return (await body.json()) as T
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.includes('FORGEJO_TOKEN')) {
|
||||
throw error
|
||||
const html = await body.text()
|
||||
const runs: WorkflowRun[] = []
|
||||
|
||||
// Extract run entries from HTML
|
||||
// Structure: <span data-tooltip-content="Status">...</span>...<a href="/lilith/REPO/actions/runs/ID">
|
||||
const runBlockPattern = /data-tooltip-content="(Success|Failure|Running|Waiting|Cancelled)"[\s\S]{1,1000}?href="\/lilith\/[^"]+\/actions\/runs\/(\d+)"/gi
|
||||
const matches = html.matchAll(runBlockPattern)
|
||||
|
||||
for (const match of matches) {
|
||||
const statusText = match[1].toLowerCase()
|
||||
const runId = parseInt(match[2])
|
||||
|
||||
let status: WorkflowRun['status'] = 'pending'
|
||||
|
||||
if (statusText === 'success') {
|
||||
status = 'success'
|
||||
} else if (statusText === 'failure') {
|
||||
status = 'failure'
|
||||
} else if (statusText === 'running') {
|
||||
status = 'running'
|
||||
} else if (statusText === 'waiting') {
|
||||
status = 'pending'
|
||||
} else if (statusText === 'cancelled') {
|
||||
status = 'cancelled'
|
||||
}
|
||||
|
||||
runs.push({
|
||||
id: runId,
|
||||
status,
|
||||
conclusion: status === 'success' || status === 'failure' ? status : null,
|
||||
created_at: new Date().toISOString(), // Not available from HTML
|
||||
updated_at: new Date().toISOString(), // Not available from HTML
|
||||
head_branch: 'main', // Default, not easily extractable from HTML
|
||||
event: 'push', // Default, not available from HTML
|
||||
})
|
||||
|
||||
if (runs.length >= 5) break // Limit to 5 most recent
|
||||
}
|
||||
return null
|
||||
|
||||
return runs
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -65,14 +84,8 @@ async function forgejoRequest<T>(endpoint: string): Promise<T | null> {
|
|||
* Get the latest workflow runs for a repository
|
||||
*/
|
||||
export async function getWorkflowRuns(repoName: string): Promise<WorkflowRun[]> {
|
||||
// Remove @lilith/ prefix if present
|
||||
const cleanName = repoName.replace('@lilith/', '').replace(/^lilith-/, '')
|
||||
|
||||
const response = await forgejoRequest<{ workflow_runs: WorkflowRun[] }>(
|
||||
`/repos/lilith/${cleanName}/actions/runs?per_page=5`
|
||||
)
|
||||
|
||||
return response?.workflow_runs || []
|
||||
// Use HTML scraping since Forgejo 11.0.8 doesn't expose Actions API
|
||||
return await scrapeWorkflowRuns(repoName)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue