Skip to content

Commit

Permalink
Merge pull request #278 from jacobwgillespie/cache-pnpm
Browse files Browse the repository at this point in the history
Add pnpm caching support
  • Loading branch information
MaksimZhukov committed Jul 20, 2021
2 parents 38d90ce + 0ae03de commit aa759c6
Show file tree
Hide file tree
Showing 12 changed files with 534 additions and 11 deletions.
35 changes: 34 additions & 1 deletion .github/workflows/e2e-cache.yml
Expand Up @@ -35,6 +35,39 @@ jobs:
run: __tests__/verify-node.sh "${{ matrix.node-version }}"
shell: bash

node-pnpm-depencies-caching:
name: Test pnpm (Node ${{ matrix.node-version}}, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [12, 14, 16]
steps:
- uses: actions/checkout@v2
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 6.10.0
- name: Generate pnpm file
run: pnpm install
- name: Remove dependencies
shell: pwsh
run: Remove-Item node_modules -Force -Recurse
- name: Clean global cache
run: rm -rf ~/.pnpm-store
shell: bash
- name: Setup Node
uses: ./
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Verify node and pnpm
run: __tests__/verify-node.sh "${{ matrix.node-version }}"
shell: bash

node-yarn1-depencies-caching:
name: Test yarn 1 (Node ${{ matrix.node-version}}, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
Expand Down Expand Up @@ -98,4 +131,4 @@ jobs:
run: yarn install
- name: Verify node and yarn
run: __tests__/verify-node.sh "${{ matrix.node-version }}"
shell: bash
shell: bash
28 changes: 25 additions & 3 deletions README.md
Expand Up @@ -7,7 +7,7 @@
This action provides the following functionality for GitHub Actions users:

- Optionally downloading and caching distribution of the requested Node.js version, and adding it to the PATH
- Optionally caching npm/yarn dependencies
- Optionally caching npm/yarn/pnpm dependencies
- Registering problem matchers for error output
- Configuring authentication for GPR or npm

Expand Down Expand Up @@ -41,7 +41,7 @@ nvm lts syntax: `lts/erbium`, `lts/fermium`, `lts/*`

### Caching packages dependencies

The action has a built-in functionality for caching and restoring npm/yarn dependencies. Supported package managers are `npm`, `yarn`. The `cache` input is optional, and caching is turned off by default.
The action has a built-in functionality for caching and restoring npm/yarn dependencies. Supported package managers are `npm`, `yarn`, `pnpm`. The `cache` input is optional, and caching is turned off by default.

**Caching npm dependencies:**
```yaml
Expand All @@ -66,7 +66,29 @@ steps:
- run: yarn install
- run: yarn test
```
Yarn caching handles both yarn versions: 1 or 2.
Yarn caching handles both yarn versions: 1 or 2.

**Caching pnpm (v6.10+) dependencies:**
```yaml
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

# NOTE: pnpm caching support requires pnpm version >= 6.10.0

steps:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@646cdf48217256a3d0b80361c5a50727664284f2
with:
version: 6.10.0
- uses: actions/setup-node@v2
with:
node-version: '14'
cache: 'pnpm'
- run: pnpm install
- run: pnpm test
```

> At the moment, only `lock` files in the project root are supported.
Expand Down
16 changes: 13 additions & 3 deletions __tests__/cache-restore.test.ts
Expand Up @@ -14,14 +14,18 @@ describe('cache-restore', () => {
const platform = process.env.RUNNER_OS;
const commonPath = '/some/random/path';
const npmCachePath = `${commonPath}/npm`;
const pnpmCachePath = `${commonPath}/pnpm`;
const yarn1CachePath = `${commonPath}/yarn1`;
const yarn2CachePath = `${commonPath}/yarn2`;
const yarnFileHash =
'b8a0bae5243251f7c07dd52d1f78ff78281dfefaded700a176261b6b54fa245b';
const npmFileHash =
'abf7c9b306a3149dcfba4673e2362755503bcceaab46f0e4e6fee0ade493e20c';
const pnpmFileHash =
'26309058093e84713f38869c50cf1cee9b08155ede874ec1b44ce3fca8c68c70';
const cachesObject = {
[npmCachePath]: npmFileHash,
[pnpmCachePath]: pnpmFileHash,
[yarn1CachePath]: yarnFileHash,
[yarn2CachePath]: yarnFileHash
};
Expand All @@ -30,6 +34,8 @@ describe('cache-restore', () => {
switch (command) {
case utils.supportedPackageManagers.npm.getCacheFolderCommand:
return npmCachePath;
case utils.supportedPackageManagers.pnpm.getCacheFolderCommand:
return pnpmCachePath;
case utils.supportedPackageManagers.yarn1.getCacheFolderCommand:
return yarn1CachePath;
case utils.supportedPackageManagers.yarn2.getCacheFolderCommand:
Expand Down Expand Up @@ -66,6 +72,8 @@ describe('cache-restore', () => {
hashFilesSpy.mockImplementation((pattern: string) => {
if (pattern.includes('package-lock.json')) {
return npmFileHash;
} else if (pattern.includes('pnpm-lock.yaml')) {
return pnpmFileHash;
} else if (pattern.includes('yarn.lock')) {
return yarnFileHash;
} else {
Expand Down Expand Up @@ -97,7 +105,7 @@ describe('cache-restore', () => {
});

describe('Validate provided package manager', () => {
it.each([['npm7'], ['npm6'], ['yarn1'], ['yarn2'], ['random']])(
it.each([['npm7'], ['npm6'], ['pnpm6'], ['yarn1'], ['yarn2'], ['random']])(
'Throw an error because %s is not supported',
async packageManager => {
await expect(restoreCache(packageManager)).rejects.toThrowError(
Expand All @@ -111,7 +119,8 @@ describe('cache-restore', () => {
it.each([
['yarn', '2.1.2', yarnFileHash],
['yarn', '1.2.3', yarnFileHash],
['npm', '', npmFileHash]
['npm', '', npmFileHash],
['pnpm', '', pnpmFileHash]
])(
'restored dependencies for %s',
async (packageManager, toolVersion, fileHash) => {
Expand Down Expand Up @@ -139,7 +148,8 @@ describe('cache-restore', () => {
it.each([
['yarn', '2.1.2', yarnFileHash],
['yarn', '1.2.3', yarnFileHash],
['npm', '', npmFileHash]
['npm', '', npmFileHash],
['pnpm', '', pnpmFileHash]
])(
'dependencies are changed %s',
async (packageManager, toolVersion, fileHash) => {
Expand Down
55 changes: 55 additions & 0 deletions __tests__/cache-save.test.ts
@@ -1,6 +1,7 @@
import * as core from '@actions/core';
import * as cache from '@actions/cache';
import * as glob from '@actions/glob';
import fs from 'fs';
import path from 'path';

import * as utils from '../src/cache-utils';
Expand All @@ -12,6 +13,8 @@ describe('run', () => {
'b8a0bae5243251f7c07dd52d1f78ff78281dfefaded700a176261b6b54fa245b';
const npmFileHash =
'abf7c9b306a3149dcfba4673e2362755503bcceaab46f0e4e6fee0ade493e20c';
const pnpmFileHash =
'26309058093e84713f38869c50cf1cee9b08155ede874ec1b44ce3fca8c68c70';
const commonPath = '/some/random/path';
process.env['GITHUB_WORKSPACE'] = path.join(__dirname, 'data');

Expand All @@ -26,6 +29,7 @@ describe('run', () => {
let saveCacheSpy: jest.SpyInstance;
let getCommandOutputSpy: jest.SpyInstance;
let hashFilesSpy: jest.SpyInstance;
let existsSpy: jest.SpyInstance;

beforeEach(() => {
getInputSpy = jest.spyOn(core, 'getInput');
Expand Down Expand Up @@ -61,10 +65,17 @@ describe('run', () => {
}
});

existsSpy = jest.spyOn(fs, 'existsSync');
existsSpy.mockImplementation(() => true);

// utils
getCommandOutputSpy = jest.spyOn(utils, 'getCommandOutput');
});

afterEach(() => {
existsSpy.mockRestore();
});

describe('Package manager validation', () => {
it('Package manager is not provided, skip caching', async () => {
inputs['cache'] = '';
Expand Down Expand Up @@ -150,6 +161,23 @@ describe('run', () => {
);
expect(setFailedSpy).not.toHaveBeenCalled();
});

it('should not save cache for pnpm', async () => {
inputs['cache'] = 'pnpm';
getStateSpy.mockImplementation(() => pnpmFileHash);
getCommandOutputSpy.mockImplementationOnce(() => `${commonPath}/pnpm`);

await run();

expect(getInputSpy).toHaveBeenCalled();
expect(getStateSpy).toHaveBeenCalledTimes(2);
expect(getCommandOutputSpy).toHaveBeenCalledTimes(1);
expect(debugSpy).toHaveBeenCalledWith(`pnpm path is ${commonPath}/pnpm`);
expect(infoSpy).toHaveBeenCalledWith(
`Cache hit occurred on the primary key ${pnpmFileHash}, not saving cache.`
);
expect(setFailedSpy).not.toHaveBeenCalled();
});
});

describe('action saves the cache', () => {
Expand Down Expand Up @@ -239,6 +267,33 @@ describe('run', () => {
);
expect(setFailedSpy).not.toHaveBeenCalled();
});

it('saves cache from pnpm', async () => {
inputs['cache'] = 'pnpm';
getStateSpy.mockImplementation((name: string) => {
if (name === State.CacheMatchedKey) {
return pnpmFileHash;
} else {
return npmFileHash;
}
});
getCommandOutputSpy.mockImplementationOnce(() => `${commonPath}/pnpm`);

await run();

expect(getInputSpy).toHaveBeenCalled();
expect(getStateSpy).toHaveBeenCalledTimes(2);
expect(getCommandOutputSpy).toHaveBeenCalledTimes(1);
expect(debugSpy).toHaveBeenCalledWith(`pnpm path is ${commonPath}/pnpm`);
expect(infoSpy).not.toHaveBeenCalledWith(
`Cache hit occurred on the primary key ${pnpmFileHash}, not saving cache.`
);
expect(saveCacheSpy).toHaveBeenCalled();
expect(infoSpy).toHaveBeenLastCalledWith(
`Cache saved with the key: ${npmFileHash}`
);
expect(setFailedSpy).not.toHaveBeenCalled();
});
});

afterEach(() => {
Expand Down
5 changes: 5 additions & 0 deletions __tests__/cache-utils.test.ts
Expand Up @@ -14,6 +14,10 @@ describe('cache-utils', () => {
function getPackagePath(name: string) {
if (name === utils.supportedPackageManagers.npm.getCacheFolderCommand) {
return `${commonPath}/npm`;
} else if (
name === utils.supportedPackageManagers.pnpm.getCacheFolderCommand
) {
return `${commonPath}/pnpm`;
} else {
if (name === utils.supportedPackageManagers.yarn1.getCacheFolderCommand) {
return `${commonPath}/yarn1`;
Expand All @@ -34,6 +38,7 @@ describe('cache-utils', () => {
describe('getPackageManagerInfo', () => {
it.each<[string, PackageManagerInfo | null]>([
['npm', utils.supportedPackageManagers.npm],
['pnpm', utils.supportedPackageManagers.pnpm],
['yarn', utils.supportedPackageManagers.yarn1],
['yarn1', null],
['yarn2', null],
Expand Down

0 comments on commit aa759c6

Please sign in to comment.