@@ -89,13 +89,24 @@ async function main() {
8989 }
9090
9191 let advisorySourceLine : string ;
92+ let advisoryDbFreshnessLine : string | null = null ;
93+ let advisoryDbWarning : string | null = null ;
9294 try {
9395 const advisorySource = createAdvisorySource ( {
9496 osvUrl : options . osvUrl ,
9597 offline : options . offline ,
9698 offlineDb : options . offlineDb ,
9799 } ) ;
98100 advisorySourceLine = advisorySource . sourceLabel ;
101+ if ( advisorySource . offline ) {
102+ const metadata = advisorySource . advisoryDbMetadata ;
103+ advisoryDbFreshnessLine = formatAdvisoryDbFreshness ( metadata ?. lastSyncAt ?? null ) ;
104+ if ( advisorySource . advisoryDbIsStale ) {
105+ advisoryDbWarning = metadata ?. lastSyncAt
106+ ? "The local advisory DB appears stale. Re-run `cve-lite advisories sync` to refresh it."
107+ : "The local advisory DB has no recorded sync timestamp. Re-run `cve-lite advisories sync` to refresh it." ;
108+ }
109+ }
99110 advisorySource . cleanup ( ) ;
100111 } catch ( error ) {
101112 if ( options . offline || options . offlineDb ) {
@@ -108,6 +119,12 @@ async function main() {
108119 console . log ( chalk . gray ( "Offline mode:" ) + " " + chalk . yellow ( "enabled" ) + " " + chalk . gray ( "(no external advisory calls will be made)" ) ) ;
109120 }
110121 console . log ( `${ chalk . gray ( "Advisory source:" ) } ${ formatAdvisorySourceLine ( advisorySourceLine ) } ` ) ;
122+ if ( advisoryDbFreshnessLine ) {
123+ console . log ( `${ chalk . gray ( "Advisory DB freshness:" ) } ${ advisoryDbFreshnessLine } ` ) ;
124+ }
125+ if ( advisoryDbWarning ) {
126+ logWarn ( advisoryDbWarning , options ) ;
127+ }
111128
112129 const scanInput = loadPackages ( projectPath , ! ! options . prodOnly , searchDepth ) ;
113130 const packages = scanInput . packages ;
@@ -195,3 +212,38 @@ function formatAdvisorySourceLine(sourceLabel: string): string {
195212
196213 return `${ match [ 1 ] } (${ chalk . cyan ( match [ 2 ] ) } )` ;
197214}
215+
216+ function formatAdvisoryDbFreshness ( lastSyncAt : string | null ) : string {
217+ if ( ! lastSyncAt ) {
218+ return chalk . yellow ( "unknown" ) ;
219+ }
220+
221+ const timestamp = Date . parse ( lastSyncAt ) ;
222+ if ( Number . isNaN ( timestamp ) ) {
223+ return chalk . yellow ( "unknown" ) ;
224+ }
225+
226+ return `${ relativeAge ( timestamp ) } ${ chalk . gray ( `(${ lastSyncAt } )` ) } ` ;
227+ }
228+
229+ function relativeAge ( timestamp : number ) : string {
230+ const deltaMs = Math . max ( 0 , Date . now ( ) - timestamp ) ;
231+ const minute = 60 * 1000 ;
232+ const hour = 60 * minute ;
233+ const day = 24 * hour ;
234+
235+ if ( deltaMs < minute ) {
236+ return "just synced" ;
237+ }
238+ if ( deltaMs < hour ) {
239+ const minutes = Math . floor ( deltaMs / minute ) ;
240+ return `synced ${ minutes } minute${ minutes === 1 ? "" : "s" } ago` ;
241+ }
242+ if ( deltaMs < day ) {
243+ const hours = Math . floor ( deltaMs / hour ) ;
244+ return `synced ${ hours } hour${ hours === 1 ? "" : "s" } ago` ;
245+ }
246+
247+ const days = Math . floor ( deltaMs / day ) ;
248+ return `synced ${ days } day${ days === 1 ? "" : "s" } ago` ;
249+ }
0 commit comments