ReviewOS

also looking at this

craft-native/craft

fix(ci): restore dependency bot scheduled job selection

#309
Merged glennmichael123 wants to merge codex/buddy-schedule-selection into main
3 files +72 -10
.github/workflows/buddy-bot.ymlmodified+17-10
Changes to .github/workflows/buddy-bot.yml
@@ -87,14 +87,22 @@ jobs:
8787 steps:
8888 - name: Determine which jobs to run
8989 id: determine
90 env:
91 EVENT_NAME: ${{ github.event_name }}
92 EVENT_SCHEDULE: ${{ github.event.schedule }}
93 REQUESTED_JOB: ${{ github.event.inputs.job }}
9094 run: |
9195 # Default to not running any jobs
9296 echo "run_check=false" >> $GITHUB_OUTPUT
9397 echo "run_update=false" >> $GITHUB_OUTPUT
9498 echo "run_dashboard=false" >> $GITHUB_OUTPUT
9599
96 if [ "$" = "workflow_dispatch" ]; then
97 JOB="${{ github.event.inputs.job || 'all' }}"
100 if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
101 JOB="${REQUESTED_JOB:-all}"
102 case "$JOB" in
103 all|check|update|dashboard) ;;
104 *) echo "::error::Unknown Buddy Bot job: $JOB"; exit 1 ;;
105 esac
98106 if [ "$JOB" = "all" ] || [ "$JOB" = "check" ]; then
99107 echo "run_check=true" >> $GITHUB_OUTPUT
100108 fi
@@ -104,14 +112,17 @@ jobs:
104112 if [ "$JOB" = "all" ] || [ "$JOB" = "dashboard" ]; then
105113 echo "run_dashboard=true" >> $GITHUB_OUTPUT
106114 fi
107 elif [ "$" = "schedule" ]; then
115 elif [ "$EVENT_NAME" = "schedule" ]; then
108116 # Determine based on cron schedule
109 if [ "$" = "*/1 * * * *" ]; then
117 if [ "$EVENT_SCHEDULE" = "0 * * * *" ]; then
110118 echo "run_check=true" >> $GITHUB_OUTPUT
111 elif [ "$" = "0 */2 * * *" ]; then
119 elif [ "$EVENT_SCHEDULE" = "0 3,15 * * *" ]; then
112120 echo "run_update=true" >> $GITHUB_OUTPUT
113 elif [ "$" = "15 */2 * * *" ]; then
121 elif [ "$EVENT_SCHEDULE" = "15 3,15 * * *" ]; then
114122 echo "run_dashboard=true" >> $GITHUB_OUTPUT
123 else
124 echo "::error::Unknown Buddy Bot schedule: $EVENT_SCHEDULE"
125 exit 1
115126 fi
116127 fi
117128
@@ -362,7 +373,6 @@ jobs:
362373 - name: Display dashboard configuration
363374 run: |
364375 echo "📊 **Buddy Bot Dashboard Management**"
365 echo "Pin Dashboard: $"
366376 echo "Custom Title: $"
367377 echo "Issue Number: $"
368378 echo "Verbose: $"
@@ -398,14 +408,12 @@ jobs:
398408
399409 - name: Update Dependency Dashboard
400410 run: |
401 PIN="$"
402411 TITLE="$"
403412 ISSUE_NUMBER="$"
404413 VERBOSE="$"
405414 DRY_RUN="$"
406415
407416 echo "📊 Updating dependency dashboard..."
408 echo "Pin: $PIN"
409417 echo "Title: ${TITLE:-default}"
410418 echo "Issue Number: ${ISSUE_NUMBER:-auto-detect}"
411419 echo "Verbose: $VERBOSE"
@@ -485,7 +493,6 @@ jobs:
485493 run: |
486494 echo "## 📊 Dependency Dashboard Summary" >> $GITHUB_STEP_SUMMARY
487495 echo "" >> $GITHUB_STEP_SUMMARY
488 echo "- **Pin Dashboard**: $" >> $GITHUB_STEP_SUMMARY
489496 echo "- **Custom Title**: $" >> $GITHUB_STEP_SUMMARY
490497 echo "- **Issue Number**: $" >> $GITHUB_STEP_SUMMARY
491498 echo "- **Triggered by**: $" >> $GITHUB_STEP_SUMMARY
.github/workflows/ci.ymlmodified+3-0
Changes to .github/workflows/ci.yml
@@ -286,6 +286,9 @@ jobs:
286286 - name: Verify API documentation links
287287 run: bun run verify:docs
288288
289 - name: Test dependency-bot job selection
290 run: bun test scripts/buddy-schedule.test.ts
291
289292 - name: Test merged SBOM inventory
290293 run: bun test scripts/merge-sbom.test.ts
291294
scripts/buddy-schedule.test.tsadded+52-0
Changes to scripts/buddy-schedule.test.ts
@@ -0,0 +1,52 @@
1import { expect, test } from 'bun:test'
2import { mkdtempSync, readFileSync, rmSync } from 'node:fs'
3import { tmpdir } from 'node:os'
4import { join } from 'node:path'
5
6const workflow = Bun.YAML.parse(readFileSync(join(import.meta.dir, '../.github/workflows/buddy-bot.yml'), 'utf8')) as any
7const selector = workflow.jobs['determine-jobs'].steps.find((step: any) => step.id === 'determine')
8
9function select(event: string, schedule = '', job = '') {
10 const directory = mkdtempSync(join(tmpdir(), 'craft-buddy-schedule-'))
11 try {
12 const output = join(directory, 'output')
13 // Substitute the old workflow's expressions too, so this executes the
14 // actual selector before and after its move to environment variables.
15 const script = selector.run
16 .replaceAll('$', event)
17 .replaceAll('$', schedule)
18 .replaceAll("$", job || 'all')
19 const result = Bun.spawnSync(['/bin/bash', '-eo', 'pipefail', '-c', script], {
20 env: { ...process.env, GITHUB_OUTPUT: output, EVENT_NAME: event, EVENT_SCHEDULE: schedule, REQUESTED_JOB: job },
21 stdout: 'pipe', stderr: 'pipe',
22 })
23 const values = Object.fromEntries(readFileSync(output, 'utf8').trim().split('\n').map(line => line.split('=')))
24 return { exitCode: result.exitCode, values }
25 }
26 finally { rmSync(directory, { recursive: true, force: true }) }
27}
28
29test('every declared schedule selects exactly its intended job', () => {
30 const expected = { '0 * * * *': 'check', '0 3,15 * * *': 'update', '15 3,15 * * *': 'dashboard' }
31 expect(workflow.on.schedule.map((item: any) => item.cron).sort()).toEqual(Object.keys(expected).sort())
32 for (const [cron, job] of Object.entries(expected)) {
33 const result = select('schedule', cron)
34 expect(result.exitCode).toBe(0)
35 expect(result.values).toEqual({ run_check: String(job === 'check'), run_update: String(job === 'update'), run_dashboard: String(job === 'dashboard') })
36 }
37})
38
39test('manual dispatch preserves individual jobs and the all/default choice', () => {
40 expect(selector.env).toEqual({ EVENT_NAME: '$', EVENT_SCHEDULE: '$', REQUESTED_JOB: '$' })
41 expect(readFileSync(join(import.meta.dir, '../.github/workflows/buddy-bot.yml'), 'utf8')).not.toContain('github.event.inputs.pin')
42 for (const job of ['', 'all', 'check', 'update', 'dashboard']) {
43 const result = select('workflow_dispatch', '', job)
44 expect(result.exitCode).toBe(0)
45 expect(result.values).toEqual(Object.fromEntries(['check', 'update', 'dashboard'].map(name => [`run_${name}`, String(!job || job === 'all' || name === job)])))
46 }
47})
48
49test('unknown schedules and jobs fail visibly instead of silently doing nothing', () => {
50 expect(select('schedule', '*/1 * * * *').exitCode).not.toBe(0)
51 expect(select('workflow_dispatch', '', 'typo').exitCode).not.toBe(0)
52})