2025-08-12 22:12:10 +02:00
import * as fs from "node:fs" ;
2024-09-05 08:06:45 -04:00
import * as cache from "@actions/cache" ;
import * as core from "@actions/core" ;
import * as exec from "@actions/exec" ;
2025-10-07 09:42:14 +02:00
import * as pep440 from "@renovatebot/pep440" ;
2024-09-05 08:06:45 -04:00
import {
STATE_CACHE_KEY ,
2025-08-12 22:12:10 +02:00
STATE_CACHE_MATCHED_KEY ,
2026-01-06 18:06:29 +01:00
STATE_PYTHON_CACHE_MATCHED_KEY ,
2024-09-05 08:06:45 -04:00
} from "./cache/restore-cache" ;
2025-10-07 09:42:14 +02:00
import { STATE_UV_PATH , STATE_UV_VERSION } from "./utils/constants" ;
2026-03-28 16:23:26 +01:00
import { loadInputs , type SetupInputs } from "./utils/inputs" ;
2026-05-31 21:13:30 +02:00
import * as log from "./utils/logging" ;
2024-08-23 23:58:26 +02:00
2026-05-31 11:25:35 +02:00
function formatUnexpectedFailure ( error : unknown ) : string {
if ( error instanceof Error ) {
return error . stack ?? error . message ;
}
return String ( error );
}
function failUnexpectedly ( event : string , error : unknown ) : never {
core . setFailed ( ` ${ event } : ${ formatUnexpectedFailure ( error ) } ` );
process . exit ( 1 );
}
process . on ( "uncaughtException" , ( error ) => {
failUnexpectedly ( "Uncaught exception" , error );
});
process . on ( "unhandledRejection" , ( reason ) => {
failUnexpectedly ( "Unhandled promise rejection" , reason );
});
2024-08-23 23:58:26 +02:00
export async function run () : Promise < void > {
try {
2026-03-28 16:23:26 +01:00
const inputs = loadInputs ();
if ( inputs . enableCache ) {
if ( inputs . saveCache ) {
await saveCache ( inputs );
2025-09-14 14:55:08 +02:00
} else {
2026-05-31 21:13:30 +02:00
log . info ( "save-cache is false. Skipping save cache step." );
2025-09-14 14:55:08 +02:00
}
2026-05-13 08:28:39 +02:00
// https://github.com/nodejs/node/issues/56645#issuecomment-3924958861
await new Promise (( resolve ) => setTimeout ( resolve , 100 ));
2025-11-21 09:00:37 +01:00
2024-11-23 16:30:54 +01:00
// node will stay alive if any promises are not resolved,
// which is a possibility if HTTP requests are dangling
// due to retries or timeouts. We know that if we got here
// that all promises that we care about have successfully
// resolved, so simply exit with success.
process . exit ( 0 );
2024-08-23 23:58:26 +02:00
}
} catch ( error ) {
2024-09-05 08:06:45 -04:00
const err = error as Error ;
core . setFailed ( err . message );
2024-08-23 23:58:26 +02:00
}
}
2026-03-28 16:23:26 +01:00
async function saveCache ( inputs : SetupInputs ) : Promise < void > {
2024-09-05 08:06:45 -04:00
const cacheKey = core . getState ( STATE_CACHE_KEY );
const matchedKey = core . getState ( STATE_CACHE_MATCHED_KEY );
2024-08-23 23:58:26 +02:00
if ( ! cacheKey ) {
2026-05-31 21:13:30 +02:00
log . warning ( "Error retrieving cache key from state." );
2024-09-05 08:06:45 -04:00
return ;
2024-09-30 02:55:24 -04:00
}
if ( matchedKey === cacheKey ) {
2026-05-31 21:13:30 +02:00
log . info ( `Cache hit occurred on key ${ cacheKey } , not saving cache.` );
2026-01-06 18:06:29 +01:00
} else {
2026-03-28 16:23:26 +01:00
if ( inputs . pruneCache ) {
2026-01-06 18:06:29 +01:00
await pruneCache ();
}
2024-08-24 10:08:12 +02:00
2026-03-28 16:23:26 +01:00
const actualCachePath = getUvCachePath ( inputs );
2026-02-06 16:00:33 +01:00
if ( ! fs . existsSync ( actualCachePath )) {
2026-03-28 16:23:26 +01:00
if ( inputs . ignoreNothingToCache ) {
2026-05-31 21:13:30 +02:00
log . info (
2026-02-06 16:00:33 +01:00
"No cacheable uv cache paths were found. Ignoring because ignore-nothing-to-cache is enabled." ,
);
} else {
throw new Error (
`Cache path ${ actualCachePath } does not exist on disk. This likely indicates that there are no dependencies to cache. Consider disabling the cache input if it is not needed.` ,
);
}
} else {
await saveCacheToKey (
cacheKey ,
actualCachePath ,
STATE_CACHE_MATCHED_KEY ,
"uv cache" ,
);
}
2024-11-23 16:30:54 +01:00
}
2025-10-09 16:47:24 -04:00
2026-03-28 16:23:26 +01:00
if ( inputs . cachePython ) {
if ( ! fs . existsSync ( inputs . pythonDir )) {
2026-05-31 21:13:30 +02:00
log . warning (
2026-03-28 16:23:26 +01:00
`Python cache path ${ inputs . pythonDir } does not exist on disk. Skipping Python cache save because no managed Python installation was found. If you want uv to install managed Python instead of using a system interpreter, set UV_PYTHON_PREFERENCE=only-managed.` ,
2026-02-06 16:00:33 +01:00
);
return ;
}
2026-01-06 18:06:29 +01:00
const pythonCacheKey = ` ${ cacheKey } -python` ;
await saveCacheToKey (
pythonCacheKey ,
2026-03-28 16:23:26 +01:00
inputs . pythonDir ,
2026-01-06 18:06:29 +01:00
STATE_PYTHON_CACHE_MATCHED_KEY ,
"Python cache" ,
);
2024-11-23 16:30:54 +01:00
}
2024-08-23 23:58:26 +02:00
}
2024-08-24 10:08:12 +02:00
async function pruneCache () : Promise < void > {
2025-10-07 09:42:14 +02:00
const forceSupported = pep440 . gte ( core . getState ( STATE_UV_VERSION ), "0.8.24" );
2024-08-24 10:08:12 +02:00
const options : exec.ExecOptions = {
2025-09-30 17:05:27 +02:00
silent : false ,
2024-09-05 08:06:45 -04:00
};
const execArgs = [ "cache" , "prune" , "--ci" ];
2025-10-07 09:42:14 +02:00
if ( forceSupported ) {
execArgs . push ( "--force" );
}
2024-08-24 10:08:12 +02:00
2026-05-31 21:13:30 +02:00
log . info ( "Pruning cache..." );
2025-10-02 17:54:15 +02:00
const uvPath = core . getState ( STATE_UV_PATH );
await exec . exec ( uvPath , execArgs , options );
2024-08-24 10:08:12 +02:00
}
2026-03-28 16:23:26 +01:00
function getUvCachePath ( inputs : SetupInputs ) : string {
if ( inputs . cacheLocalPath === undefined ) {
2026-01-06 18:06:29 +01:00
throw new Error (
"cache-local-path is not set. Cannot save cache without a valid cache path." ,
);
}
if (
process . env . UV_CACHE_DIR &&
2026-03-28 16:23:26 +01:00
process . env . UV_CACHE_DIR !== inputs . cacheLocalPath . path
2026-01-06 18:06:29 +01:00
) {
2026-05-31 21:13:30 +02:00
log . warning (
2026-03-28 16:23:26 +01:00
`The environment variable UV_CACHE_DIR has been changed to " ${ process . env . UV_CACHE_DIR } ", by an action or step running after astral-sh/setup-uv. This can lead to unexpected behavior. If you expected this to happen set the cache-local-path input to " ${ process . env . UV_CACHE_DIR } " instead of " ${ inputs . cacheLocalPath . path } ".` ,
2026-01-06 18:06:29 +01:00
);
return process . env . UV_CACHE_DIR ;
}
2026-03-28 16:23:26 +01:00
return inputs . cacheLocalPath . path ;
2026-01-06 18:06:29 +01:00
}
async function saveCacheToKey (
cacheKey : string ,
cachePath : string ,
stateKey : string ,
cacheName : string ,
) : Promise < void > {
const matchedKey = core . getState ( stateKey );
if ( matchedKey === cacheKey ) {
2026-05-31 21:13:30 +02:00
log . info ( ` ${ cacheName } hit occurred on key ${ cacheKey } , not saving cache.` );
2026-01-06 18:06:29 +01:00
return ;
}
2026-05-31 21:13:30 +02:00
log . info ( `Including ${ cacheName } path: ${ cachePath } ` );
2026-02-06 16:00:33 +01:00
await cache . saveCache ([ cachePath ], cacheKey );
2026-05-31 21:13:30 +02:00
log . info ( ` ${ cacheName } saved with key: ${ cacheKey } ` );
2026-01-06 18:06:29 +01:00
}
2024-09-05 08:06:45 -04:00
run ();