mirror of
				https://github.com/actions/setup-node.git
				synced 2025-10-31 16:14:00 +08:00 
			
		
		
		
	Add node's caching implementation (#2)
* first iteration for implementation of caching * add logs * add debug line * fix build command * fix path * add possible post-if * remove braces * test new action post-if variant * work on built-in caching * remove post-if * pass version * work on yarn support * fix return value * change names and remove logs * worked on resolving comments * check post-if for null * add success() condition * remove primary key field * work on resolving comments * remove logs * resolving comments * resolving comments * resolving comments * resolving comments * fix getpackageManagerVersion * run clean for unstaged changes * fix falling version tests * work on resolving comments * resolving comments * fix comment * resolve comments * Add tests to cover node's caching (#3) * add tests to cover node's caching * work on fixing tests * fix e2e tests * rebuild and fix test * fixing tests * change name of describes, it and fix test * add names for jobs * fix issue
This commit is contained in:
		
							
								
								
									
										57
									
								
								src/cache-restore.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/cache-restore.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| import * as cache from '@actions/cache'; | ||||
| import * as core from '@actions/core'; | ||||
| import * as glob from '@actions/glob'; | ||||
| import path from 'path'; | ||||
| import fs from 'fs'; | ||||
|  | ||||
| import {State, Outputs} from './constants'; | ||||
| import { | ||||
|   getCacheDirectoryPath, | ||||
|   getPackageManagerInfo, | ||||
|   PackageManagerInfo | ||||
| } from './cache-utils'; | ||||
|  | ||||
| export const restoreCache = async (packageManager: string) => { | ||||
|   const packageManagerInfo = await getPackageManagerInfo(packageManager); | ||||
|   if (!packageManagerInfo) { | ||||
|     throw new Error(`Caching for '${packageManager}' is not supported`); | ||||
|   } | ||||
|   const platform = process.env.RUNNER_OS; | ||||
|  | ||||
|   const cachePath = await getCacheDirectoryPath( | ||||
|     packageManagerInfo, | ||||
|     packageManager | ||||
|   ); | ||||
|   const lockFilePath = findLockFile(packageManagerInfo); | ||||
|   const fileHash = await glob.hashFiles(lockFilePath); | ||||
|  | ||||
|   const primaryKey = `${platform}-${packageManager}-${fileHash}`; | ||||
|   core.debug(`primary key is ${primaryKey}`); | ||||
|  | ||||
|   core.saveState(State.CachePrimaryKey, primaryKey); | ||||
|  | ||||
|   const cacheKey = await cache.restoreCache([cachePath], primaryKey); | ||||
|  | ||||
|   if (!cacheKey) { | ||||
|     core.info(`${packageManager} cache is not found`); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   core.saveState(State.CacheMatchedKey, cacheKey); | ||||
|   core.info(`Cache restored from key: ${cacheKey}`); | ||||
| }; | ||||
|  | ||||
| const findLockFile = (packageManager: PackageManagerInfo) => { | ||||
|   let lockFiles = packageManager.lockFilePatterns; | ||||
|   const workspace = process.env.GITHUB_WORKSPACE!; | ||||
|   const rootContent = fs.readdirSync(workspace); | ||||
|  | ||||
|   const lockFile = lockFiles.find(item => rootContent.includes(item)); | ||||
|   if (!lockFile) { | ||||
|     throw new Error( | ||||
|       `Dependencies lock file is not found in ${workspace}. Supported file patterns: ${lockFiles.toString()}` | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   return path.join(workspace, lockFile); | ||||
| }; | ||||
							
								
								
									
										50
									
								
								src/cache-save.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/cache-save.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| import * as core from '@actions/core'; | ||||
| import * as cache from '@actions/cache'; | ||||
| import {State} from './constants'; | ||||
| import {getCacheDirectoryPath, getPackageManagerInfo} from './cache-utils'; | ||||
|  | ||||
| export async function run() { | ||||
|   const cacheLock = core.getInput('cache'); | ||||
|   try { | ||||
|     await cachePackages(cacheLock); | ||||
|   } catch (error) { | ||||
|     core.setFailed(error.message); | ||||
|   } | ||||
| } | ||||
|  | ||||
| const cachePackages = async (packageManager: string) => { | ||||
|   const state = core.getState(State.CacheMatchedKey); | ||||
|   const primaryKey = core.getState(State.CachePrimaryKey); | ||||
|  | ||||
|   const packageManagerInfo = await getPackageManagerInfo(packageManager); | ||||
|   if (!packageManagerInfo) { | ||||
|     core.debug(`Caching for '${packageManager}' is not supported`); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   const cachePath = await getCacheDirectoryPath( | ||||
|     packageManagerInfo, | ||||
|     packageManager | ||||
|   ); | ||||
|   if (primaryKey === state) { | ||||
|     core.info( | ||||
|       `Cache hit occurred on the primary key ${primaryKey}, not saving cache.` | ||||
|     ); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   try { | ||||
|     await cache.saveCache([cachePath], primaryKey); | ||||
|     core.info(`Cache saved with the key: ${primaryKey}`); | ||||
|   } catch (error) { | ||||
|     if (error.name === cache.ValidationError.name) { | ||||
|       throw error; | ||||
|     } else if (error.name === cache.ReserveCacheError.name) { | ||||
|       core.info(error.message); | ||||
|     } else { | ||||
|       core.warning(`${error.message}`); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | ||||
| run(); | ||||
							
								
								
									
										84
									
								
								src/cache-utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/cache-utils.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| import * as core from '@actions/core'; | ||||
| import * as exec from '@actions/exec'; | ||||
|  | ||||
| type SupportedPackageManagers = { | ||||
|   [prop: string]: PackageManagerInfo; | ||||
| }; | ||||
|  | ||||
| export interface PackageManagerInfo { | ||||
|   lockFilePatterns: Array<string>; | ||||
|   getCacheFolderCommand: string; | ||||
| } | ||||
|  | ||||
| export const supportedPackageManagers: SupportedPackageManagers = { | ||||
|   npm: { | ||||
|     lockFilePatterns: ['package-lock.json', 'yarn.lock'], | ||||
|     getCacheFolderCommand: 'npm config get cache' | ||||
|   }, | ||||
|   yarn1: { | ||||
|     lockFilePatterns: ['yarn.lock'], | ||||
|     getCacheFolderCommand: 'yarn cache dir' | ||||
|   }, | ||||
|   yarn2: { | ||||
|     lockFilePatterns: ['yarn.lock'], | ||||
|     getCacheFolderCommand: 'yarn config get cacheFolder' | ||||
|   } | ||||
| }; | ||||
|  | ||||
| export const getCommandOutput = async (toolCommand: string) => { | ||||
|   const {stdout, stderr, exitCode} = await exec.getExecOutput(toolCommand); | ||||
|  | ||||
|   if (stderr) { | ||||
|     throw new Error(stderr); | ||||
|   } | ||||
|  | ||||
|   return stdout; | ||||
| }; | ||||
|  | ||||
| const getPackageManagerVersion = async ( | ||||
|   packageManager: string, | ||||
|   command: string | ||||
| ) => { | ||||
|   const stdOut = await getCommandOutput(`${packageManager} ${command}`); | ||||
|  | ||||
|   if (!stdOut) { | ||||
|     throw new Error(`Could not retrieve version of ${packageManager}`); | ||||
|   } | ||||
|  | ||||
|   return stdOut; | ||||
| }; | ||||
|  | ||||
| export const getPackageManagerInfo = async (packageManager: string) => { | ||||
|   if (packageManager === 'npm') { | ||||
|     return supportedPackageManagers.npm; | ||||
|   } else if (packageManager === 'yarn') { | ||||
|     const yarnVersion = await getPackageManagerVersion('yarn', '--version'); | ||||
|  | ||||
|     core.debug(`Consumed yarn version is ${yarnVersion}`); | ||||
|  | ||||
|     if (yarnVersion.startsWith('1.')) { | ||||
|       return supportedPackageManagers.yarn1; | ||||
|     } else { | ||||
|       return supportedPackageManagers.yarn2; | ||||
|     } | ||||
|   } else { | ||||
|     return null; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| export const getCacheDirectoryPath = async ( | ||||
|   packageManagerInfo: PackageManagerInfo, | ||||
|   packageManager: string | ||||
| ) => { | ||||
|   const stdOut = await getCommandOutput( | ||||
|     packageManagerInfo.getCacheFolderCommand | ||||
|   ); | ||||
|  | ||||
|   if (!stdOut) { | ||||
|     throw new Error(`Could not get cache folder path for ${packageManager}`); | ||||
|   } | ||||
|  | ||||
|   core.debug(`${packageManager} path is ${stdOut}`); | ||||
|  | ||||
|   return stdOut; | ||||
| }; | ||||
							
								
								
									
										13
									
								
								src/constants.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/constants.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| export enum LockType { | ||||
|   Npm = 'npm', | ||||
|   Yarn = 'yarn' | ||||
| } | ||||
|  | ||||
| export enum State { | ||||
|   CachePrimaryKey = 'CACHE_KEY', | ||||
|   CacheMatchedKey = 'CACHE_RESULT' | ||||
| } | ||||
|  | ||||
| export enum Outputs { | ||||
|   CacheHit = 'cache-hit' | ||||
| } | ||||
| @@ -128,7 +128,7 @@ export async function getNode( | ||||
|     let extPath: string; | ||||
|     info = info || ({} as INodeVersionInfo); // satisfy compiler, never null when reaches here | ||||
|     if (osPlat == 'win32') { | ||||
|       let _7zPath = path.join(__dirname, '..', 'externals', '7zr.exe'); | ||||
|       let _7zPath = path.join(__dirname, '../..', 'externals', '7zr.exe'); | ||||
|       extPath = await tc.extract7z(downloadPath, undefined, _7zPath); | ||||
|       // 7z extracts to folder matching file name | ||||
|       let nestedPath = path.join(extPath, path.basename(info.fileName, '.7z')); | ||||
|   | ||||
							
								
								
									
										11
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								src/main.ts
									
									
									
									
									
								
							| @@ -2,6 +2,7 @@ import * as core from '@actions/core'; | ||||
| import * as installer from './installer'; | ||||
| import * as auth from './authutil'; | ||||
| import * as path from 'path'; | ||||
| import {restoreCache} from './cache-restore'; | ||||
| import {URL} from 'url'; | ||||
| import os = require('os'); | ||||
|  | ||||
| @@ -17,6 +18,7 @@ export async function run() { | ||||
|     } | ||||
|  | ||||
|     let arch = core.getInput('architecture'); | ||||
|     const cache = core.getInput('cache'); | ||||
|  | ||||
|     // if architecture supplied but node-version is not | ||||
|     // if we don't throw a warning, the already installed x64 node will be used which is not probably what user meant. | ||||
| @@ -45,7 +47,14 @@ export async function run() { | ||||
|       auth.configAuthentication(registryUrl, alwaysAuth); | ||||
|     } | ||||
|  | ||||
|     const matchersPath = path.join(__dirname, '..', '.github'); | ||||
|     if (cache) { | ||||
|       if (isGhes()) { | ||||
|         throw new Error('Caching is not supported on GHES'); | ||||
|       } | ||||
|       await restoreCache(cache); | ||||
|     } | ||||
|  | ||||
|     const matchersPath = path.join(__dirname, '../..', '.github'); | ||||
|     core.info(`##[add-matcher]${path.join(matchersPath, 'tsc.json')}`); | ||||
|     core.info( | ||||
|       `##[add-matcher]${path.join(matchersPath, 'eslint-stylish.json')}` | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Dmitry Shibanov
					Dmitry Shibanov