11import path from 'node:path' ;
2- import { promises as fs } from 'node:fs' ;
2+ import { promises as fs , existsSync , readFileSync } from 'node:fs' ;
33
44import semver from 'semver' ;
55import { replaceInFile } from 'replace-in-file' ;
@@ -21,7 +21,11 @@ const isWindows = process.platform === 'win32';
2121export default class ReleasePreparation extends Session {
2222 constructor ( argv , cli , dir ) {
2323 super ( cli , dir ) ;
24- this . isSecurityRelease = argv . security ;
24+ // argv.security can be either:
25+ // - true (boolean) if --security was used without parameter
26+ // - string if --security=path was used
27+ this . isSecurityRelease = ! ! argv . security ;
28+ this . securityReleaseRepo = typeof argv . security === 'string' ? argv . security : null ;
2529 this . isLTS = false ;
2630 this . isLTSTransition = argv . startLTS ;
2731 this . runBranchDiff = ! argv . skipBranchDiff ;
@@ -63,17 +67,63 @@ export default class ReleasePreparation extends Session {
6367 return false ;
6468 }
6569
70+ // Read vulnerabilities.json and create PR URL -> CVE-IDs mapping
71+ let vulnCveMap = new Map ( ) ;
72+ if ( this . isSecurityRelease && this . securityReleaseRepo ) {
73+ // Construct path to vulnerabilities.json within the security-release repo
74+ const vulnPath = path . join (
75+ this . securityReleaseRepo ,
76+ 'security-release' ,
77+ 'next-security-release' ,
78+ 'vulnerabilities.json'
79+ ) ;
80+
81+ if ( ! existsSync ( vulnPath ) ) {
82+ cli . error ( `vulnerabilities.json not found at ${ vulnPath } . ` +
83+ 'Skipping CVE auto-population.' ) ;
84+ cli . warn ( 'PRs will require manual CVE-ID entry.' ) ;
85+ } else {
86+ try {
87+ cli . startSpinner ( `Reading vulnerabilities.json from ${ vulnPath } ..` ) ;
88+ const vulnData = JSON . parse ( readFileSync ( vulnPath , 'utf-8' ) ) ;
89+ cli . stopSpinner ( `Done reading vulnerabilities.json from ${ vulnPath } ` ) ;
90+
91+ if ( vulnData . reports && Array . isArray ( vulnData . reports ) ) {
92+ vulnData . reports . forEach ( report => {
93+ if ( report . prURL && report . cveIds && report . cveIds . length > 0 ) {
94+ vulnCveMap . set ( report . prURL , report . cveIds ) ;
95+ }
96+ } ) ;
97+ }
98+ cli . ok ( `Loaded ${ vulnCveMap . size } CVE mappings from vulnerabilities.json` ) ;
99+ } catch ( err ) {
100+ cli . error ( `Failed to read vulnerabilities.json: ${ err . message } ` ) ;
101+ cli . warn ( 'Continuing without CVE auto-population.' ) ;
102+ }
103+ }
104+ }
105+
66106 for ( const pr of prs ) {
67107 if ( pr . mergeable !== 'MERGEABLE' ) {
68108 this . warnForNonMergeablePR ( pr ) ;
69109 }
110+
111+ // Look up CVE-IDs from vulnerabilities.json
112+ const prUrl = `https://github.com/${ this . owner } /${ this . repo } /pull/${ pr . number } ` ;
113+ const cveIds = vulnCveMap . get ( prUrl ) ;
114+
115+ if ( ! cveIds || cveIds . length === 0 ) {
116+ cli . warn ( `No CVE-IDs found in vulnerabilities.json for ${ prUrl } ` ) ;
117+ }
118+
70119 const cp = new CherryPick ( pr . number , this . dir , cli , {
71120 owner : this . owner ,
72121 repo : this . repo ,
73122 gpgSign : this . gpgSign ,
74123 upstream : this . isSecurityRelease ? `https://${ this . username } :${ this . config . token } @github.com/${ this . owner } /${ this . repo } .git` : this . upstream ,
75124 lint : false ,
76- includeCVE : true
125+ includeCVE : true ,
126+ cveIds : cveIds || null
77127 } ) ;
78128 const success = await cp . start ( ) ;
79129 if ( ! success ) {
0 commit comments