setup-node/src/cache-utils.ts

225 lines
6.5 KiB
TypeScript
Raw Normal View History

import * as core from '@actions/core';
import * as exec from '@actions/exec';
import * as cache from '@actions/cache';
2023-05-11 09:40:44 +02:00
import * as glob from '@actions/glob';
2023-04-10 16:52:10 +02:00
import path from 'path';
2023-05-04 16:05:46 +02:00
import fs from 'fs';
export interface PackageManagerInfo {
2023-05-11 09:40:44 +02:00
name: string;
lockFilePatterns: Array<string>;
2023-05-11 09:40:44 +02:00
getCacheFolderPath: (projectDir?: string) => Promise<string>;
}
interface SupportedPackageManagers {
npm: PackageManagerInfo;
pnpm: PackageManagerInfo;
yarn: PackageManagerInfo;
}
2023-05-11 09:40:44 +02:00
// for testing purposes
export const npmGetCacheFolderCommand = 'npm config get cache';
export const pnpmGetCacheFolderCommand = 'pnpm store path --silent';
export const yarn1GetCacheFolderCommand = 'yarn cache dir';
export const yarn2GetCacheFolderCommand = 'yarn config get cacheFolder';
export const supportedPackageManagers: SupportedPackageManagers = {
npm: {
2023-05-11 09:40:44 +02:00
name: 'npm',
lockFilePatterns: ['package-lock.json', 'npm-shrinkwrap.json', 'yarn.lock'],
2023-05-11 09:40:44 +02:00
getCacheFolderPath: () =>
getCommandOutputGuarded(
npmGetCacheFolderCommand,
'Could not get npm cache folder path'
)
},
2021-06-30 16:44:51 +01:00
pnpm: {
2023-05-11 09:40:44 +02:00
name: 'pnpm',
2021-06-30 16:44:51 +01:00
lockFilePatterns: ['pnpm-lock.yaml'],
2023-05-11 09:40:44 +02:00
getCacheFolderPath: () =>
getCommandOutputGuarded(
pnpmGetCacheFolderCommand,
'Could not get pnpm cache folder path'
)
},
2023-05-11 09:40:44 +02:00
yarn: {
name: 'yarn',
lockFilePatterns: ['yarn.lock'],
2023-05-11 09:40:44 +02:00
getCacheFolderPath: async projectDir => {
const yarnVersion = await getCommandOutputGuarded(
`yarn --version`,
'Could not retrieve version of yarn',
projectDir
);
core.debug(`Consumed yarn version is ${yarnVersion}`);
const stdOut = yarnVersion.startsWith('1.')
? await getCommandOutput(yarn1GetCacheFolderCommand, projectDir)
: await getCommandOutput(yarn2GetCacheFolderCommand, projectDir);
if (!stdOut) {
throw new Error(
`Could not get yarn cache folder path for ${projectDir}`
);
}
return stdOut;
}
}
};
2023-04-10 16:52:10 +02:00
export const getCommandOutput = async (
toolCommand: string,
2023-05-11 09:40:44 +02:00
cwd?: string
): Promise<string> => {
let {stdout, stderr, exitCode} = await exec.getExecOutput(
toolCommand,
undefined,
2023-05-11 09:40:44 +02:00
{ignoreReturnCode: true, ...(cwd && {cwd})}
);
if (exitCode) {
stderr = !stderr.trim()
? `The '${toolCommand}' command failed with exit code: ${exitCode}`
: stderr;
throw new Error(stderr);
}
2021-06-30 16:44:51 +01:00
return stdout.trim();
};
2023-05-11 09:40:44 +02:00
export const getCommandOutputGuarded = async (
toolCommand: string,
error: string,
cwd?: string
): Promise<string> => {
const stdOut = getCommandOutput(toolCommand, cwd);
if (!stdOut) {
2023-05-11 09:40:44 +02:00
throw new Error(error);
}
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;
} else if (packageManager === 'yarn') {
2023-05-11 09:40:44 +02:00
return supportedPackageManagers.yarn;
} else {
return null;
}
};
2023-05-11 09:40:44 +02:00
const globPatternToArray = async (pattern: string): Promise<string[]> => {
const globber = await glob.create(pattern);
return globber.glob();
};
export const expandCacheDependencyPath = async (
cacheDependencyPath: string
): Promise<string[]> => {
const multilinePaths = cacheDependencyPath
.split(/\r?\n/)
.map(path => path.trim())
.filter(path => Boolean(path));
const expandedPathsPromises: Promise<string[]>[] = multilinePaths.map(path =>
path.includes('*') ? globPatternToArray(path) : Promise.resolve([path])
);
const expandedPaths: string[][] = await Promise.all(expandedPathsPromises);
return expandedPaths.length === 0 ? [''] : expandedPaths.flat();
};
const cacheDependencyPathToCacheFolderPath = async (
packageManagerInfo: PackageManagerInfo,
2023-05-11 09:40:44 +02:00
cacheDependencyPath: string
): Promise<string> => {
const cacheDependencyPathDirectory = path.dirname(cacheDependencyPath);
const cacheFolderPath =
fs.existsSync(cacheDependencyPathDirectory) &&
fs.lstatSync(cacheDependencyPathDirectory).isDirectory()
? await packageManagerInfo.getCacheFolderPath(
cacheDependencyPathDirectory
)
: await packageManagerInfo.getCacheFolderPath();
core.debug(
`${packageManagerInfo.name} path is ${cacheFolderPath} (derived from cache-dependency-path: "${cacheDependencyPath}")`
2021-07-15 12:43:19 +01:00
);
2023-05-11 09:40:44 +02:00
return cacheFolderPath;
};
const cacheDependenciesPathsToCacheFoldersPaths = async (
packageManagerInfo: PackageManagerInfo,
cacheDependenciesPaths: string[]
): Promise<string[]> => {
const cacheFoldersPaths = await Promise.all(
cacheDependenciesPaths.map(cacheDependencyPath =>
cacheDependencyPathToCacheFolderPath(
packageManagerInfo,
cacheDependencyPath
)
)
);
return cacheFoldersPaths.filter(
(cachePath, i, result) => result.indexOf(cachePath) === i
);
};
2023-05-11 09:40:44 +02:00
const cacheDependencyPathToCacheFoldersPaths = async (
packageManagerInfo: PackageManagerInfo,
cacheDependencyPath: string
): Promise<string[]> => {
const cacheDependenciesPaths = await expandCacheDependencyPath(
cacheDependencyPath
);
return cacheDependenciesPathsToCacheFoldersPaths(
packageManagerInfo,
cacheDependenciesPaths
);
};
2023-05-11 09:40:44 +02:00
const cacheFoldersPathsForRoot = async (
packageManagerInfo: PackageManagerInfo
): Promise<string[]> => {
const cacheFolderPath = await packageManagerInfo.getCacheFolderPath();
core.debug(`${packageManagerInfo.name} path is ${cacheFolderPath}`);
return [cacheFolderPath];
};
2023-05-11 09:40:44 +02:00
export const getCacheDirectoriesPaths = async (
packageManagerInfo: PackageManagerInfo,
cacheDependencyPath: string
): Promise<string[]> =>
// TODO: multiple directories limited to yarn so far
packageManagerInfo === supportedPackageManagers.yarn
? cacheDependencyPathToCacheFoldersPaths(
packageManagerInfo,
cacheDependencyPath
)
: cacheFoldersPathsForRoot(packageManagerInfo);
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-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-12-09 11:41:54 +01:00
return false;
}