@@ -1314,6 +1314,175 @@ export class RegistryClient {
1314
1314
return null
1315
1315
}
1316
1316
1317
+ /**
1318
+ * Get the release date of a specific package version from registry
1319
+ */
1320
+ async getPackageVersionReleaseDate ( packageName : string , version : string ) : Promise < Date | null > {
1321
+ try {
1322
+ // First try using bun info to get package metadata
1323
+ const result = await this . runCommand ( 'bun' , [ 'info' , `${ packageName } @${ version } ` , '--json' ] )
1324
+ const data = JSON . parse ( result )
1325
+
1326
+ // Check if the package data has time information
1327
+ if ( data . time && typeof data . time === 'string' ) {
1328
+ return new Date ( data . time )
1329
+ }
1330
+
1331
+ // If bun doesn't provide time info, fall back to npm registry API
1332
+ if ( ! data . time ) {
1333
+ try {
1334
+ const npmResult = await this . runCommand ( 'npm' , [ 'view' , `${ packageName } @${ version } ` , 'time' , '--json' ] )
1335
+ const timeData = JSON . parse ( npmResult )
1336
+
1337
+ if ( timeData && typeof timeData === 'string' ) {
1338
+ return new Date ( timeData )
1339
+ }
1340
+ }
1341
+ catch ( npmError ) {
1342
+ this . logger . debug ( `npm fallback failed for ${ packageName } @${ version } :` , npmError )
1343
+ }
1344
+ }
1345
+
1346
+ return null
1347
+ }
1348
+ catch ( error ) {
1349
+ this . logger . debug ( `Failed to get release date for ${ packageName } @${ version } :` , error )
1350
+ return null
1351
+ }
1352
+ }
1353
+
1354
+ /**
1355
+ * Get the release date for GitHub Actions
1356
+ */
1357
+ async getGitHubActionReleaseDate ( actionName : string , version : string ) : Promise < Date | null > {
1358
+ try {
1359
+ // GitHub Actions use GitHub releases API
1360
+ // Format: owner/repo@version
1361
+ const [ owner , repo ] = actionName . split ( '/' )
1362
+ if ( ! owner || ! repo ) {
1363
+ return null
1364
+ }
1365
+
1366
+ // Use GitHub API to get release information
1367
+ const apiUrl = `https://api.github.com/repos/${ owner } /${ repo } /releases/tags/${ version } `
1368
+
1369
+ // Use curl to fetch the release data
1370
+ const result = await this . runCommand ( 'curl' , [ '-s' , '-H' , 'Accept: application/vnd.github.v3+json' , apiUrl ] )
1371
+ const releaseData = JSON . parse ( result )
1372
+
1373
+ if ( releaseData . published_at ) {
1374
+ return new Date ( releaseData . published_at )
1375
+ }
1376
+
1377
+ return null
1378
+ }
1379
+ catch ( error ) {
1380
+ this . logger . debug ( `Failed to get GitHub Action release date for ${ actionName } @${ version } :` , error )
1381
+ return null
1382
+ }
1383
+ }
1384
+
1385
+ /**
1386
+ * Get the release date for Composer packages
1387
+ */
1388
+ async getComposerPackageReleaseDate ( packageName : string , version : string ) : Promise < Date | null > {
1389
+ try {
1390
+ // Use Packagist API for Composer packages
1391
+ const apiUrl = `https://packagist.org/packages/${ packageName } .json`
1392
+
1393
+ const result = await this . runCommand ( 'curl' , [ '-s' , apiUrl ] )
1394
+ const packageData = JSON . parse ( result )
1395
+
1396
+ if ( packageData . package && packageData . package . versions && packageData . package . versions [ version ] ) {
1397
+ const versionData = packageData . package . versions [ version ]
1398
+ if ( versionData . time ) {
1399
+ return new Date ( versionData . time )
1400
+ }
1401
+ }
1402
+
1403
+ return null
1404
+ }
1405
+ catch ( error ) {
1406
+ this . logger . debug ( `Failed to get Composer package release date for ${ packageName } @${ version } :` , error )
1407
+ return null
1408
+ }
1409
+ }
1410
+
1411
+ /**
1412
+ * Get the release date for Docker images
1413
+ */
1414
+ async getDockerImageReleaseDate ( imageName : string , version : string ) : Promise < Date | null > {
1415
+ try {
1416
+ // For Docker Hub images, we can use the Docker Hub API
1417
+ // This is more complex as it requires authentication for some images
1418
+ // For now, we'll be conservative and allow Docker image updates
1419
+ this . logger . debug ( `Docker image release date checking not fully implemented for ${ imageName } :${ version } ` )
1420
+ return null
1421
+ }
1422
+ catch ( error ) {
1423
+ this . logger . debug ( `Failed to get Docker image release date for ${ imageName } :${ version } :` , error )
1424
+ return null
1425
+ }
1426
+ }
1427
+
1428
+ /**
1429
+ * Check if a package version meets the minimum release age requirement
1430
+ */
1431
+ async meetsMinimumReleaseAge ( packageName : string , version : string , dependencyType ?: string ) : Promise < boolean > {
1432
+ const minimumReleaseAge = this . config ?. packages ?. minimumReleaseAge ?? 0
1433
+ const excludeList = this . config ?. packages ?. minimumReleaseAgeExclude ?? [ ]
1434
+
1435
+ // If no minimum age is set, allow all packages
1436
+ if ( minimumReleaseAge === 0 ) {
1437
+ return true
1438
+ }
1439
+
1440
+ // If package is in exclude list, allow it immediately
1441
+ if ( excludeList . includes ( packageName ) ) {
1442
+ this . logger . debug ( `Package ${ packageName } is excluded from minimum release age requirement` )
1443
+ return true
1444
+ }
1445
+
1446
+ // Get the release date based on dependency type
1447
+ let releaseDate : Date | null = null
1448
+
1449
+ switch ( dependencyType ) {
1450
+ case 'github-actions' :
1451
+ releaseDate = await this . getGitHubActionReleaseDate ( packageName , version )
1452
+ break
1453
+ case 'require' :
1454
+ case 'require-dev' :
1455
+ releaseDate = await this . getComposerPackageReleaseDate ( packageName , version )
1456
+ break
1457
+ case 'docker-image' :
1458
+ releaseDate = await this . getDockerImageReleaseDate ( packageName , version )
1459
+ break
1460
+ default :
1461
+ // For npm/bun packages (dependencies, devDependencies, etc.)
1462
+ releaseDate = await this . getPackageVersionReleaseDate ( packageName , version )
1463
+ break
1464
+ }
1465
+
1466
+ if ( ! releaseDate ) {
1467
+ // If we can't get the release date, be conservative and allow the update
1468
+ // This prevents blocking updates when registry data is unavailable
1469
+ this . logger . warn ( `Could not determine release date for ${ packageName } @${ version } (${ dependencyType || 'unknown type' } ), allowing update` )
1470
+ return true
1471
+ }
1472
+
1473
+ // Calculate age in minutes
1474
+ const now = new Date ( )
1475
+ const ageInMinutes = ( now . getTime ( ) - releaseDate . getTime ( ) ) / ( 1000 * 60 )
1476
+
1477
+ const meetsRequirement = ageInMinutes >= minimumReleaseAge
1478
+
1479
+ if ( ! meetsRequirement ) {
1480
+ this . logger . info ( `Package ${ packageName } @${ version } (${ dependencyType || 'unknown type' } ) is too new (${ Math . round ( ageInMinutes ) } minutes old, minimum: ${ minimumReleaseAge } minutes)` )
1481
+ }
1482
+
1483
+ return meetsRequirement
1484
+ }
1485
+
1317
1486
/**
1318
1487
* Run bun outdated for a specific workspace
1319
1488
*/
0 commit comments