@@ -32,8 +32,6 @@ type WorkflowInputs = Partial<Record<KnownInputName, WorkflowInput>>;
3232 * Represents PR check specifications.
3333 */
3434interface Specification extends JobSpecification {
35- /** The display name for the check. */
36- name : string ;
3735 /** Workflow-level input definitions forwarded to `workflow_dispatch`/`workflow_call`. */
3836 inputs ?: Record < string , WorkflowInput > ;
3937 /** CodeQL bundle versions to test against. Defaults to `DEFAULT_TEST_VERSIONS`. */
@@ -50,12 +48,17 @@ interface Specification extends JobSpecification {
5048 /** Service containers for the job. */
5149 services ?: any ;
5250
51+ /** Additional jobs to run after the main PR check job. */
52+ validationJobs ?: Record < string , JobSpecification > ;
53+
5354 /** If set, this check is part of a named collection that gets its own caller workflow. */
5455 collection ?: string ;
5556}
5657
5758/** Represents job specifications. */
5859interface JobSpecification {
60+ /** The display name for the check. */
61+ name : string ;
5962 /** Custom permissions override for the job. */
6063 permissions ?: Record < string , string > ;
6164 /** Extra environment variables for the job. */
@@ -469,6 +472,92 @@ function generateJob(
469472 return { checkJob, workflowInputs } ;
470473}
471474
475+ /** Generates a validation job. */
476+ function generateValidationJob (
477+ specDocument : yaml . Document ,
478+ jobSpecification : JobSpecification ,
479+ checkName : string ,
480+ name : string ,
481+ ) {
482+ // Determine which languages or frameworks have to be installed.
483+ const { inputs, steps } = getSetupSteps ( jobSpecification ) ;
484+
485+ // Extract the sequence of steps from the YAML document to persist as much formatting as possible.
486+ const specSteps = specDocument . getIn ( [
487+ "validationJobs" ,
488+ name ,
489+ "steps" ,
490+ ] ) as yaml . YAMLSeq ;
491+
492+ // Add the generated steps in front of the ones from the specification.
493+ specSteps . items . unshift ( ...steps ) ;
494+
495+ const validationJob : Record < string , any > = {
496+ name : jobSpecification . name ,
497+ if : "github.triggering_actor != 'dependabot[bot]'" ,
498+ needs : [ checkName ] ,
499+ permissions : {
500+ contents : "read" ,
501+ "security-events" : "read" ,
502+ } ,
503+ "timeout-minutes" : 5 ,
504+ "runs-on" : "ubuntu-slim" ,
505+ steps : specSteps ,
506+ } ;
507+
508+ if ( jobSpecification . permissions ) {
509+ validationJob . permissions = jobSpecification . permissions ;
510+ }
511+
512+ for ( const key of [ "env" ] as const ) {
513+ if ( jobSpecification [ key ] !== undefined ) {
514+ validationJob [ key ] = jobSpecification [ key ] ;
515+ }
516+ }
517+
518+ validationJob . env = validationJob . env ?? { } ;
519+ if ( ! ( "CODEQL_ACTION_TEST_MODE" in validationJob . env ) ) {
520+ validationJob . env . CODEQL_ACTION_TEST_MODE = true ;
521+ }
522+
523+ return { validationJob, inputs } ;
524+ }
525+
526+ /** Generates additional jobs that run after the main check job, based on the `validationJobs` property. */
527+ function generateValidationJobs (
528+ specDocument : yaml . Document ,
529+ checkSpecification : Specification ,
530+ checkName : string ,
531+ ) : Record < string , any > {
532+ if ( checkSpecification . validationJobs === undefined ) {
533+ return { } ;
534+ }
535+
536+ const validationJobs : Record < string , any > = { } ;
537+ let workflowInputs : WorkflowInputs = { } ;
538+
539+ for ( const [ jobName , jobSpec ] of Object . entries (
540+ checkSpecification . validationJobs ,
541+ ) ) {
542+ if ( checkName === jobName ) {
543+ throw new Error (
544+ `Validation job '${ jobName } ' cannot have the same name as the main job.` ,
545+ ) ;
546+ }
547+
548+ const { validationJob, inputs } = generateValidationJob (
549+ specDocument ,
550+ jobSpec ,
551+ checkName ,
552+ jobName ,
553+ ) ;
554+ validationJobs [ jobName ] = validationJob ;
555+ workflowInputs = { ...workflowInputs , ...inputs } ;
556+ }
557+
558+ return { validationJobs, workflowInputs } ;
559+ }
560+
472561/**
473562 * Main entry point for the sync script.
474563 */
@@ -505,6 +594,12 @@ function main(): void {
505594 specDocument ,
506595 checkSpecification ,
507596 ) ;
597+ const { validationJobs, validationJobInputs } = generateValidationJobs (
598+ specDocument ,
599+ checkSpecification ,
600+ checkName ,
601+ ) ;
602+ const combinedInputs = { ...workflowInputs , ...validationJobInputs } ;
508603
509604 // If this check belongs to a named collection, record it.
510605 if ( checkSpecification . collection ) {
@@ -515,12 +610,12 @@ function main(): void {
515610 collections [ collectionName ] . push ( {
516611 specification : checkSpecification ,
517612 checkName,
518- inputs : workflowInputs ,
613+ inputs : combinedInputs ,
519614 } ) ;
520615 }
521616
522617 let extraGroupName = "" ;
523- for ( const inputName of Object . keys ( workflowInputs ) ) {
618+ for ( const inputName of Object . keys ( combinedInputs ) ) {
524619 extraGroupName += "-${{inputs." + inputName + "}}" ;
525620 }
526621
@@ -545,10 +640,10 @@ function main(): void {
545640 } ,
546641 schedule : [ { cron } ] ,
547642 workflow_dispatch : {
548- inputs : workflowInputs ,
643+ inputs : combinedInputs ,
549644 } ,
550645 workflow_call : {
551- inputs : workflowInputs ,
646+ inputs : combinedInputs ,
552647 } ,
553648 } ,
554649 defaults : {
@@ -563,6 +658,7 @@ function main(): void {
563658 } ,
564659 jobs : {
565660 [ checkName ] : checkJob ,
661+ ...validationJobs ,
566662 } ,
567663 } ;
568664
0 commit comments