diff --git a/README.md b/README.md index 3ad549c..c365db1 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,30 @@ You can use your own install command See [example-install-command.yml](./.github/workflows/example-install-command.yml) +### Add cache prefix + +If you are installing different individual tools, you might want to have different caches. You can insert custom cache prefix strings into the cache keys. For example, let's install two different tools, each cache will be separate. + +```yml +- name: Install tool A + uses: bahmutov/npm-install@v1 + with: + # use just package.json checksum + useLockFile: false + install-command: 'npm install tool-a' + cache-key-prefix: tool-a + +- name: Install tool B + uses: bahmutov/npm-install@v1 + with: + # use just package.json checksum + useLockFile: false + install-command: 'npm install tool-b' + cache-key-prefix: tool-b +``` + +The first cache will have key `npm-tool-a-...` and the second cache will have key `npm-tool-b-...` + ### Node version If you need to use a specific Node version, use the [actions/setup-node](https://github.com/actions/setup-node) before installing the dependencies. diff --git a/action.yml b/action.yml index cb227ad..3fef892 100644 --- a/action.yml +++ b/action.yml @@ -21,3 +21,6 @@ inputs: install-command: description: 'Custom install command to use' required: false + cache-key-prefix: + description: 'Prefix the cache name with this string' + required: false diff --git a/dist/index.js b/dist/index.js index 90bced5..0fab339 100644 --- a/dist/index.js +++ b/dist/index.js @@ -183,11 +183,17 @@ const getCacheParams = ({ useRollingCache, homeDirectory, npmCacheFolder, - lockHash + lockHash, + cachePrefix }) => { const platformAndArch = api.utils.getPlatformAndArch() core.debug(`platform and arch ${platformAndArch}`) const primaryKeySegments = [platformAndArch] + + if (cachePrefix) { + primaryKeySegments.unshift(cachePrefix) + } + let inputPaths, restoreKeys if (useYarn) { @@ -221,7 +227,8 @@ const installInOneFolder = ({ usePackageLock, workingDirectory, useRollingCache, - installCommand + installCommand, + cachePrefix }) => { core.debug(`usePackageLock? ${usePackageLock}`) core.debug(`working directory ${workingDirectory}`) @@ -253,7 +260,8 @@ const installInOneFolder = ({ homeDirectory, useRollingCache, npmCacheFolder: NPM_CACHE_FOLDER, - lockHash + lockHash, + cachePrefix }) const opts = { @@ -278,8 +286,10 @@ const installInOneFolder = ({ const npmInstallAction = async () => { const usePackageLock = getInputBool('useLockFile', true) const useRollingCache = getInputBool('useRollingCache', false) + const cachePrefix = core.getInput('cache-key-prefix') || '' core.debug(`usePackageLock? ${usePackageLock}`) core.debug(`useRollingCache? ${useRollingCache}`) + core.debug(`cache prefix "${cachePrefix}"`) // Note: working directory for "actions/exec" should be absolute @@ -299,7 +309,8 @@ const npmInstallAction = async () => { usePackageLock, useRollingCache, workingDirectory, - installCommand + installCommand, + cachePrefix }) } } diff --git a/index.js b/index.js index d897091..a69b6f9 100644 --- a/index.js +++ b/index.js @@ -176,11 +176,17 @@ const getCacheParams = ({ useRollingCache, homeDirectory, npmCacheFolder, - lockHash + lockHash, + cachePrefix }) => { const platformAndArch = api.utils.getPlatformAndArch() core.debug(`platform and arch ${platformAndArch}`) const primaryKeySegments = [platformAndArch] + + if (cachePrefix) { + primaryKeySegments.unshift(cachePrefix) + } + let inputPaths, restoreKeys if (useYarn) { @@ -214,7 +220,8 @@ const installInOneFolder = ({ usePackageLock, workingDirectory, useRollingCache, - installCommand + installCommand, + cachePrefix }) => { core.debug(`usePackageLock? ${usePackageLock}`) core.debug(`working directory ${workingDirectory}`) @@ -246,7 +253,8 @@ const installInOneFolder = ({ homeDirectory, useRollingCache, npmCacheFolder: NPM_CACHE_FOLDER, - lockHash + lockHash, + cachePrefix }) const opts = { @@ -271,8 +279,10 @@ const installInOneFolder = ({ const npmInstallAction = async () => { const usePackageLock = getInputBool('useLockFile', true) const useRollingCache = getInputBool('useRollingCache', false) + const cachePrefix = core.getInput('cache-key-prefix') || '' core.debug(`usePackageLock? ${usePackageLock}`) core.debug(`useRollingCache? ${useRollingCache}`) + core.debug(`cache prefix "${cachePrefix}"`) // Note: working directory for "actions/exec" should be absolute @@ -292,7 +302,8 @@ const npmInstallAction = async () => { usePackageLock, useRollingCache, workingDirectory, - installCommand + installCommand, + cachePrefix }) } } diff --git a/test/action-spec.js b/test/action-spec.js index 371e7ad..27c4759 100644 --- a/test/action-spec.js +++ b/test/action-spec.js @@ -23,7 +23,7 @@ describe('action', () => { sandbox.stub(os, 'homedir').returns(homedir) sandbox.stub(process, 'cwd').returns(cwd) sandbox.stub(utils, 'getPlatformAndArch').returns('platform-arch') - sandbox.stub(utils, 'getNow').returns(new Date(2020, 01, 01)) + sandbox.stub(utils, 'getNow').returns(new Date(2020, 0o1, 0o1)) // always stub "core.exportVariable" to avoid polluting actual workflow sandbox.stub(core, 'exportVariable').returns() }) @@ -80,7 +80,7 @@ describe('action', () => { }) }) - context('does not find Yarn', function() { + context('does not find Yarn and uses NPM', function() { const yarnFilename = path.join(cwd, 'yarn.lock') const npmShrinkwrapFilename = path.join(cwd, 'npm-shrinkwrap.json') const packageLockFilename = path.join(cwd, 'package-lock.json') @@ -268,18 +268,21 @@ describe('action', () => { await action.npmInstallAction() expect(installInOneFolder).to.be.calledThrice expect(installInOneFolder).to.be.calledWithExactly({ + cachePrefix: '', installCommand: undefined, usePackageLock: true, useRollingCache: false, workingDirectory: 'subfolder/foo' }) expect(installInOneFolder).to.be.calledWithExactly({ + cachePrefix: '', installCommand: undefined, usePackageLock: true, useRollingCache: false, workingDirectory: 'subfolder/bar' }) expect(installInOneFolder).to.be.calledWithExactly({ + cachePrefix: '', installCommand: undefined, usePackageLock: true, useRollingCache: false, @@ -418,4 +421,59 @@ describe('action', () => { ) }) }) + + context('with cachePrefix', function() { + const pathToYarn = '/path/to/yarn' + const yarnFilename = path.join(cwd, 'yarn.lock') + const yarnCachePaths = [path.join(homedir, '.cache', 'yarn')] + const cacheKey = + 'yarn-my-cache-prefix-platform-arch-2020-1-hash-from-yarn-lock-file' + + beforeEach(function() { + const stub = sandbox.stub(core, 'getInput') + stub.withArgs('cache-key-prefix').returns('my-cache-prefix') + stub.withArgs('useRollingCache').returns('1') + stub.withArgs('useLockFile').returns() + sandbox + .stub(fs, 'existsSync') + .withArgs(yarnFilename) + .returns(true) + + sandbox + .stub(io, 'which') + .withArgs('yarn') + .resolves(pathToYarn) + + sandbox + .stub(hasha, 'fromFileSync') + .withArgs(yarnFilename) + .returns('hash-from-yarn-lock-file') + + const cacheHit = false + this.restoreCache = sandbox.stub(cache, 'restoreCache').resolves(cacheHit) + this.saveCache = sandbox.stub(cache, 'saveCache').resolves() + }) + + it('finds yarn and uses lock file', async function() { + await action.npmInstallAction() + + expect(this.restoreCache).to.be.calledOnceWithExactly( + yarnCachePaths, + cacheKey, + [ + 'yarn-my-cache-prefix-platform-arch-2020-1-hash-from-yarn-lock-file', + 'yarn-my-cache-prefix-platform-arch-2020-1' + ] + ) + expect(this.exec).to.be.calledOnceWithExactly( + quote(pathToYarn), + ['--frozen-lockfile'], + { cwd } + ) + expect(this.saveCache).to.be.calledOnceWithExactly( + yarnCachePaths, + cacheKey + ) + }) + }) })