2021-06-16 09:52:44 +03:00
import * as core from '@actions/core' ;
import * as exec from '@actions/exec' ;
2022-03-31 21:10:37 +02:00
import * as cache from '@actions/cache' ;
2023-06-21 17:52:17 +02:00
import * as glob from '@actions/glob' ;
import path from 'path' ;
import fs from 'fs' ;
import { unique } from './util' ;
2021-06-16 09:52:44 +03:00
export interface PackageManagerInfo {
2023-06-21 17:52:17 +02:00
name : string ;
2021-06-16 09:52:44 +03:00
lockFilePatterns : Array < string >;
2023-06-21 17:52:17 +02:00
getCacheFolderPath : ( projectDir? : string ) => Promise < string >;
2021-06-16 09:52:44 +03:00
}
2023-06-21 17:52:17 +02:00
interface SupportedPackageManagers {
npm : PackageManagerInfo ;
pnpm : PackageManagerInfo ;
yarn : PackageManagerInfo ;
}
2021-06-16 09:52:44 +03:00
export const supportedPackageManagers : SupportedPackageManagers = {
npm : {
2023-06-21 17:52:17 +02:00
name : 'npm' ,
2022-07-04 17:29:56 -04:00
lockFilePatterns : [ 'package-lock.json' , 'npm-shrinkwrap.json' , 'yarn.lock' ],
2023-06-21 17:52:17 +02:00
getCacheFolderPath : () =>
getCommandOutputNotEmpty (
'npm config get cache' ,
'Could not get npm cache folder path'
)
2021-06-16 09:52:44 +03:00
},
2021-06-30 16:44:51 +01:00
pnpm : {
2023-06-21 17:52:17 +02:00
name : 'pnpm' ,
2021-06-30 16:44:51 +01:00
lockFilePatterns : [ 'pnpm-lock.yaml' ],
2023-06-21 17:52:17 +02:00
getCacheFolderPath : () =>
getCommandOutputNotEmpty (
'pnpm store path --silent' ,
'Could not get pnpm cache folder path'
)
2021-06-30 16:44:51 +01:00
},
2023-06-21 17:52:17 +02:00
yarn : {
name : 'yarn' ,
2021-06-16 09:52:44 +03:00
lockFilePatterns : [ 'yarn.lock' ],
2023-06-21 17:52:17 +02:00
getCacheFolderPath : async projectDir => {
const yarnVersion = await getCommandOutputNotEmpty (
`yarn --version` ,
'Could not retrieve version of yarn' ,
projectDir
);
core . debug (
`Consumed yarn version is ${ yarnVersion } (working dir: " ${
projectDir || ''
} ")`
);
const stdOut = yarnVersion . startsWith ( '1.' )
? await getCommandOutput ( 'yarn cache dir' , projectDir )
: await getCommandOutput ( 'yarn config get cacheFolder' , projectDir );
if ( ! stdOut ) {
throw new Error (
`Could not get yarn cache folder path for ${ projectDir } `
);
}
return stdOut ;
}
2021-06-16 09:52:44 +03:00
}
};
2023-06-21 17:52:17 +02:00
export const getCommandOutput = async (
toolCommand : string ,
cwd? : string
) : Promise < string > => {
2021-12-27 12:34:06 +03:00
let { stdout , stderr , exitCode } = await exec . getExecOutput (
toolCommand ,
undefined ,
2023-06-21 17:52:17 +02:00
{ ignoreReturnCode : true , ...( cwd && { cwd })}
2021-12-27 12:34:06 +03:00
);
if ( exitCode ) {
stderr = ! stderr . trim ()
? `The ' ${ toolCommand } ' command failed with exit code: ${ exitCode } `
: stderr ;
2021-06-16 09:52:44 +03:00
throw new Error ( stderr );
}
2021-06-30 16:44:51 +01:00
return stdout . trim ();
2021-06-16 09:52:44 +03:00
};
2023-06-21 17:52:17 +02:00
export const getCommandOutputNotEmpty = async (
toolCommand : string ,
error : string ,
cwd? : string
) : Promise < string > => {
const stdOut = getCommandOutput ( toolCommand , cwd );
2021-06-16 09:52:44 +03:00
if ( ! stdOut ) {
2023-06-21 17:52:17 +02:00
throw new Error ( error );
2021-06-16 09:52:44 +03:00
}
return stdOut ;
};
export const getPackageManagerInfo = async ( packageManager : string ) => {
if ( packageManager === 'npm' ) {
return supportedPackageManagers . npm ;
2021-06-30 16:44:51 +01:00
} else if ( packageManager === 'pnpm' ) {
return supportedPackageManagers . pnpm ;
2021-06-16 09:52:44 +03:00
} else if ( packageManager === 'yarn' ) {
2023-06-21 17:52:17 +02:00
return supportedPackageManagers . yarn ;
2021-06-16 09:52:44 +03:00
} else {
return null ;
}
};
2023-06-21 17:52:17 +02:00
/**
* Expands (converts) the string input `cache-dependency-path` to list of directories that
* may be project roots
* @param cacheDependencyPath - either a single string or multiline string with possible glob patterns
* expected to be the result of `core.getInput('cache-dependency-path')`
* @return list of directories and possible
*/
const getProjectDirectoriesFromCacheDependencyPath = async (
cacheDependencyPath : string
) : Promise < string [] > => {
const globber = await glob . create ( cacheDependencyPath );
const cacheDependenciesPaths = await globber . glob ();
const existingDirectories : string [] = cacheDependenciesPaths
. map ( path . dirname )
. filter ( unique ())
. filter ( directory => fs . lstatSync ( directory ). isDirectory ());
if ( ! existingDirectories . length )
core . warning (
`No existing directories found containing cache-dependency-path=" ${ cacheDependencyPath } "`
);
return existingDirectories ;
};
/**
* Finds the cache directories configured for the repo if cache-dependency-path is not empty
* @param packageManagerInfo - an object having getCacheFolderPath method specific to given PM
* @param cacheDependencyPath - either a single string or multiline string with possible glob patterns
* expected to be the result of `core.getInput('cache-dependency-path')`
* @return list of files on which the cache depends
*/
const getCacheDirectoriesFromCacheDependencyPath = async (
2021-06-16 09:52:44 +03:00
packageManagerInfo : PackageManagerInfo ,
2023-06-21 17:52:17 +02:00
cacheDependencyPath : string
) : Promise < string [] > => {
const projectDirectories = await getProjectDirectoriesFromCacheDependencyPath (
cacheDependencyPath
2021-07-15 12:43:19 +01:00
);
2023-06-21 17:52:17 +02:00
const cacheFoldersPaths = await Promise . all (
projectDirectories . map ( async projectDirectory => {
const cacheFolderPath =
packageManagerInfo . getCacheFolderPath ( projectDirectory );
core . debug (
` ${ packageManagerInfo . name } 's cache folder " ${ cacheFolderPath } " configured for the directory " ${ projectDirectory } "`
);
return cacheFolderPath ;
})
);
// uniq in order to do not cache the same directories twice
return cacheFoldersPaths . filter ( unique ());
};
2021-06-16 09:52:44 +03:00
2023-06-21 17:52:17 +02:00
/**
* Finds the cache directories configured for the repo ignoring cache-dependency-path
* @param packageManagerInfo - an object having getCacheFolderPath method specific to given PM
* @return list of files on which the cache depends
*/
const getCacheDirectoriesForRootProject = async (
packageManagerInfo : PackageManagerInfo
) : Promise < string [] > => {
const cacheFolderPath = await packageManagerInfo . getCacheFolderPath ();
core . debug (
` ${ packageManagerInfo . name } 's cache folder " ${ cacheFolderPath } " configured for the root directory`
);
return [ cacheFolderPath ];
};
2021-06-16 09:52:44 +03:00
2023-06-21 17:52:17 +02:00
/**
* A function to find the cache directories configured for the repo
* currently it handles only the case of PM=yarn && cacheDependencyPath is not empty
* @param packageManagerInfo - an object having getCacheFolderPath method specific to given PM
* @param cacheDependencyPath - either a single string or multiline string with possible glob patterns
* expected to be the result of `core.getInput('cache-dependency-path')`
* @return list of files on which the cache depends
*/
export const getCacheDirectories = async (
packageManagerInfo : PackageManagerInfo ,
cacheDependencyPath : string
) : Promise < string [] > => {
// For yarn, if cacheDependencyPath is set, ask information about cache folders in each project
// folder satisfied by cacheDependencyPath https://github.com/actions/setup-node/issues/488
if ( packageManagerInfo . name === 'yarn' && cacheDependencyPath ) {
return getCacheDirectoriesFromCacheDependencyPath (
packageManagerInfo ,
cacheDependencyPath
);
}
return getCacheDirectoriesForRootProject ( packageManagerInfo );
2021-06-16 09:52:44 +03:00
};
2022-03-31 21:10:37 +02:00
export function isGhes () : boolean {
const ghUrl = new URL (
process . env [ 'GITHUB_SERVER_URL' ] || 'https://github.com'
);
return ghUrl . hostname . toUpperCase () !== 'GITHUB.COM' ;
}
export function isCacheFeatureAvailable () : boolean {
2022-12-09 11:41:54 +01:00
if ( cache . isFeatureAvailable ()) return true ;
2022-03-31 21:10:37 +02:00
2022-12-09 12:05:59 +01:00
if ( isGhes ()) {
core . warning (
2022-12-09 11:41:54 +01:00
'Cache action is only supported on GHES version >= 3.5. If you are on version >=3.5 Please check with GHES admin if Actions cache service is enabled or not.'
);
2022-12-09 12:05:59 +01:00
return false ;
}
2022-12-09 11:41:54 +01:00
core . warning (
'The runner was not able to contact the cache service. Caching will be skipped'
);
2022-03-31 21:10:37 +02:00
2022-12-09 11:41:54 +01:00
return false ;
2022-03-31 21:10:37 +02:00
}