11#!/usr/bin/env npx tsx
22
33import * as fs from "node:fs/promises" ;
4+ import { parseArgs , ParseArgsConfig } from "node:util" ;
45
5- import { BUNDLE_METADATA_FILE } from "./config" ;
6+ import * as exec from "@actions/exec" ;
7+
8+ import {
9+ ApiClient ,
10+ CODEQL_ACTION_REPO ,
11+ getApiClient ,
12+ TOKEN_OPTION_CONFIG ,
13+ } from "./api-client" ;
14+ import { BASELINE_BUNDLE_METADATA_FILE , BUNDLE_METADATA_FILE } from "./config" ;
15+
16+ const optionsConfig = {
17+ ...TOKEN_OPTION_CONFIG ,
18+ branch : {
19+ type : "string" ,
20+ default : "main" ,
21+ } ,
22+ runner : {
23+ type : "string" ,
24+ default : "macos-latest" ,
25+ } ,
26+ "node-version" : {
27+ type : "string" ,
28+ default : "24" ,
29+ } ,
30+ } satisfies ParseArgsConfig [ "options" ] ;
31+
32+ function parseOptions ( ) {
33+ const { values : options } = parseArgs ( {
34+ options : optionsConfig ,
35+ } ) ;
36+
37+ return options ;
38+ }
39+
40+ type Options = ReturnType < typeof parseOptions > ;
641
742interface InputInfo {
843 bytesInOutput : number ;
@@ -23,7 +58,80 @@ function toMB(bytes: number): string {
2358 return `${ ( bytes / ( 1024 * 1024 ) ) . toFixed ( 2 ) } MB` ;
2459}
2560
61+ async function getBaselineFrom ( client : ApiClient , options : Options ) {
62+ const workflowRun = await client . rest . actions . listWorkflowRuns ( {
63+ ...CODEQL_ACTION_REPO ,
64+ branch : options . branch ,
65+ workflow_id : "pr-checks.yml" ,
66+ status : "success" ,
67+ per_page : 1 ,
68+ event : "push" ,
69+ } ) ;
70+
71+ if ( workflowRun . data . total_count === 0 ) {
72+ throw new Error (
73+ `Expected to find a 'pr-checks.yml' run for '${ options . branch } ', but found none.` ,
74+ ) ;
75+ }
76+
77+ const expectedArtifactName = `bundle-metadata-${ options . runner } -${ options [ "node-version" ] } ` ;
78+ const artifacts = await client . rest . actions . listWorkflowRunArtifacts ( {
79+ ...CODEQL_ACTION_REPO ,
80+ run_id : workflowRun . data . workflow_runs [ 0 ] . id ,
81+ name : expectedArtifactName ,
82+ } ) ;
83+
84+ if ( artifacts . data . total_count === 0 ) {
85+ throw new Error (
86+ `Expected to find an artifact named '${ expectedArtifactName } ', but found none.` ,
87+ ) ;
88+ }
89+
90+ const downloadInfo = await client . rest . actions . downloadArtifact ( {
91+ ...CODEQL_ACTION_REPO ,
92+ artifact_id : artifacts . data . artifacts [ 0 ] . id ,
93+ archive_format : "zip" ,
94+ } ) ;
95+
96+ // This works fine for us with our version of Octokit, so we don't need to
97+ // worry about over-complicating this script and handle other possibilities.
98+ if ( downloadInfo . data instanceof ArrayBuffer ) {
99+ const archivePath = `${ expectedArtifactName } .zip` ;
100+ await fs . writeFile ( archivePath , Buffer . from ( downloadInfo . data ) ) ;
101+
102+ console . info ( `Extracting zip file: ${ archivePath } ` ) ;
103+ await exec . exec ( "unzip" , [ "-o" , archivePath , "-d" , "." ] ) ;
104+
105+ // We no longer need the archive after unzipping it.
106+ await fs . rm ( archivePath ) ;
107+
108+ // Check that we have the expected file.
109+ try {
110+ await fs . stat ( BASELINE_BUNDLE_METADATA_FILE ) ;
111+ } catch ( err ) {
112+ throw new Error (
113+ `Expected '${ BASELINE_BUNDLE_METADATA_FILE } ' to have been extracted, but it does not exist: ${ err } ` ,
114+ ) ;
115+ }
116+
117+ const baselineData = await fs . readFile ( BASELINE_BUNDLE_METADATA_FILE ) ;
118+ return JSON . parse ( String ( baselineData ) ) as Metadata ;
119+ } else {
120+ throw new Error ( "Expected to receive artifact data, but didn't." ) ;
121+ }
122+ }
123+
26124async function main ( ) {
125+ const options = parseOptions ( ) ;
126+
127+ if ( options . token === undefined ) {
128+ throw new Error ( "Missing --token" ) ;
129+ }
130+
131+ // Initialise the API client.
132+ const client = getApiClient ( options . token ) ;
133+ const baselineMetadata = await getBaselineFrom ( client , options ) ;
134+
27135 const fileContents = await fs . readFile ( BUNDLE_METADATA_FILE ) ;
28136 const metadata = JSON . parse ( String ( fileContents ) ) as Metadata ;
29137
0 commit comments