From 51949a865f437da77a0426aa15fb8c307904dcaa Mon Sep 17 00:00:00 2001 From: cornholio <0@mcornholio.ru> Date: Sat, 11 Jan 2020 17:22:15 +0300 Subject: [PATCH] Cleaning node_modules/.cache New flag: clearSharedCache When enabled, it cleans node_modules/.cache before each push --- README.md | 17 ++++-- src/lib/install/index.ts | 4 +- src/lib/install/pushBackends.ts | 24 +++++++-- src/lib/validateConfig.ts | 4 ++ src/test/unit/install/index.test.ts | 17 ++++++ src/test/unit/install/pushBackends.test.ts | 63 +++++++++++++++++++++- src/test/unit/validateConfig.test.ts | 7 ++- src/types.ts | 1 + 8 files changed, 124 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 86006c7..1eaa1b8 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,13 @@ Example: ```js veendorVersion: '>=2.1' ``` +Please notice that `veendorVersion` option is not supported in veendor before 2.0, so if your team might use 1.x, add this to your .veendor.js: + +```js +if (!global.VEENDOR_VERSION) { + throw new Error('veendor version is incompatible, please update'); +} +``` #### dedupe Optional, defaults to `false` @@ -135,11 +142,13 @@ Example: dedupe: true ``` -Please notice that `veendorVersion` option is not supported in veendor before 2.0, so if your team might use 1.x, add this to your .veendor.js: +#### clearSharedCache +Optional, defaults to `false` +Some software (e.g. babel) uses `node_modules/.cache` as a place for caches. In cases when such software runs in postinstall scripts or just in cases of poor luck, these caches may end up in your veendor bundles. +If you add `clearSharedCache: true` to your config, veendor will remove said directory from your `node_modules` before each push. +Example: ```js -if (!global.VEENDOR_VERSION) { - throw new Error('veendor version is incompatible, please update'); -} +clearSharedCache: true ``` ### Built-in backends diff --git a/src/lib/install/index.ts b/src/lib/install/index.ts index 0eab3f0..43cbff7 100644 --- a/src/lib/install/index.ts +++ b/src/lib/install/index.ts @@ -2,7 +2,7 @@ import {getLogger} from '@/lib/util/logger'; import * as helpers from './helpers'; -import pushBackends from './pushBackends'; +import {pushBackends} from './pushBackends'; import * as rsyncWrapper from '@/lib/commandWrappers/rsyncWrapper'; import * as npmWrapper from '@/lib/commandWrappers/npmWrapper'; import * as gitWrapper from '@/lib/commandWrappers/gitWrapper'; @@ -164,7 +164,7 @@ export default async function install( * Pushing bundle */ try { - await pushBackends(backendsToPush, hash, false); + await pushBackends(backendsToPush, hash, false, config.clearSharedCache); } catch (pushError) { if (pushError instanceof errors.RePullNeeded) { // this happens if we failed to push bundle because someone got faster then us diff --git a/src/lib/install/pushBackends.ts b/src/lib/install/pushBackends.ts index cb52754..2fdd040 100644 --- a/src/lib/install/pushBackends.ts +++ b/src/lib/install/pushBackends.ts @@ -1,24 +1,40 @@ +import fsExtra from 'fs-extra'; +import path from 'path'; + +import {BackendCalls, BackendConfig} from '@/types'; + import * as errors from '../errors'; import {getLogger} from '../util/logger'; import * as helpers from './helpers'; import {provideBackendCallTools} from '../util/progress'; -import {BackendCalls, BackendConfig} from '@/types'; -export default async function pushBackends( - backendConfigs: BackendConfig[], hash: string, rePull?: boolean): Promise { +export async function pushBackends( + backendConfigs: BackendConfig[], hash: string, rePull?: boolean, clearCache?: boolean): Promise { const logger = getLogger(); logger.trace(`Pushing '${hash}' to backends`); const pushingBackends = backendConfigs.filter(backend => backend.push); - if (pushingBackends.length === 0 && backendConfigs.length > 0) { logger.info(`No backends with push: true found. Exiting`); + return; } const dirPromises = pushingBackends.map(backend => { return helpers.createCleanCacheDir(backend); }); + const sharedCachePath = path.join(process.cwd(), 'node_modules', '.cache'); + + if (clearCache) { + await fsExtra.pathExists(sharedCachePath).then(() => { + logger.info(`Shared cache directory found at '${sharedCachePath}'. Removing`); + return fsExtra.remove(sharedCachePath); + }, (err) => { + if (err.code !== 'ENOENT') { + throw err; + } + }); + } const cacheDirs = await Promise.all(dirPromises); const pushingPromises = []; diff --git a/src/lib/validateConfig.ts b/src/lib/validateConfig.ts index 6a77595..ae657ea 100644 --- a/src/lib/validateConfig.ts +++ b/src/lib/validateConfig.ts @@ -80,6 +80,10 @@ export default function validateConfig(config: PartialConfig): Promise { config.dedupe = false; } + if (config.clearSharedCache === undefined) { + config.clearSharedCache = false; + } + if (config.veendorVersion !== undefined) { if (!semver.satisfies(global.VEENDOR_VERSION, config.veendorVersion)) { return Promise.reject(new InvalidVeendorVersionError(config.veendorVersion)); diff --git a/src/test/unit/install/index.test.ts b/src/test/unit/install/index.test.ts index 8d0d365..144ba27 100644 --- a/src/test/unit/install/index.test.ts +++ b/src/test/unit/install/index.test.ts @@ -14,6 +14,7 @@ import install, { PkgJsonNotFoundError } from '@/lib/install'; import * as installHelpers from '@/lib/install/helpers'; +import * as pushBackends from '@/lib/install/pushBackends'; import * as pkgJson from '@/lib/pkgjson'; import * as gitWrapper from '@/lib/commandWrappers/gitWrapper'; import * as npmWrapper from '@/lib/commandWrappers/npmWrapper'; @@ -1107,5 +1108,21 @@ describe('install', () => { await install({config}); backendMock0.verify(); }); + + it('should pass clearSharedCache to `pushBackends(1)`', async () => { + config.clearSharedCache = false; + const mock = sandbox.mock(pushBackends); + mock.expects('pushBackends').withArgs(sinon.match.any, sinon.match.any, sinon.match.any, false); + await install({config}); + mock.verify(); + }); + + it('should pass clearSharedCache to `pushBackends(2)`', async () => { + config.clearSharedCache = true; + const mock = sandbox.mock(pushBackends); + mock.expects('pushBackends').withArgs(sinon.match.any, sinon.match.any, sinon.match.any, true); + await install({config}); + mock.verify(); + }); }); }); diff --git a/src/test/unit/install/pushBackends.test.ts b/src/test/unit/install/pushBackends.test.ts index b6d7ee9..0183da0 100644 --- a/src/test/unit/install/pushBackends.test.ts +++ b/src/test/unit/install/pushBackends.test.ts @@ -2,16 +2,20 @@ import {afterEach, beforeEach, describe, it} from 'mocha'; import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; import sinon from 'sinon'; +import mockfs from 'mock-fs'; +import fsExtra from 'fs-extra'; -import pushBackends from '@/lib/install/pushBackends'; +import {pushBackends} from '@/lib/install/pushBackends'; import * as errors from '@/lib/errors'; import * as helpers from '../helpers'; -import {BackendConfig} from '@/types'; +import {BackendConfig, PkgJson} from '@/types'; const assert = chai.assert; chai.use(chaiAsPromised); +let PKGJSON: PkgJson; + describe('pushBackends', function () { let sandbox: sinon.SinonSandbox; let fakeBackends: BackendConfig[]; @@ -22,6 +26,17 @@ describe('pushBackends', function () { fakeBackends = [helpers.fakeBackendConfig('fakeBackends[0]'), helpers.fakeBackendConfig('fakeBackends[1]')]; fakeBackends[0].backend.pull = () => Promise.reject(new errors.BundleNotFoundError); fakeBackends[0].push = true; + + PKGJSON = { + dependencies: { + foo: '2.2.8', + c: '2.2.9' + }, + devDependencies: { + baz: '6.6.6' + } + }; + }); afterEach(function () { @@ -41,4 +56,48 @@ describe('pushBackends', function () { return assert.isRejected(pushBackends(fakeBackends, fakeSha1), helpers.AnError); }); + it('should not clear node_modules/.cache, if `clearSharedCache` is set in config', async () => { + mockfs({ + 'package.json': JSON.stringify(PKGJSON), + 'node_modules': { + 'left-pad': { + 'package.json': '{"a": "b"}', + }, + '.cache': { + 'some': 'garbage', + } + } + }); + + fakeBackends[0].backend.push = () => fsExtra + .stat('node_modules/.cache') + .then( + () => assert(true, 'cache is not cleared before push'), + () => assert(false, 'cache is cleared before push'), + ); + + await pushBackends(fakeBackends, fakeSha1); + }); + it('should not clear node_modules/.cache, if parameter is not passed', async () => { + mockfs({ + 'package.json': JSON.stringify(PKGJSON), + 'node_modules': { + 'left-pad': { + 'package.json': '{"a": "b"}', + }, + '.cache': { + 'some': 'garbage', + } + } + }); + + fakeBackends[0].backend.push = () => fsExtra + .stat('node_modules/.cache') + .then( + () => assert(false, 'cache is not cleared before push'), + () => assert(true, 'cache is cleared before push'), + ); + + await pushBackends(fakeBackends, fakeSha1, false, true); + }); }); diff --git a/src/test/unit/validateConfig.test.ts b/src/test/unit/validateConfig.test.ts index 2dab0cd..fc92f5c 100644 --- a/src/test/unit/validateConfig.test.ts +++ b/src/test/unit/validateConfig.test.ts @@ -38,7 +38,7 @@ describe('validateConfig', function () { sandbox = sinon.sandbox.create(); - const veendorVersion = require('../../package.json').version; + const veendorVersion = require('../../../package.json').version; global.VEENDOR_VERSION = veendorVersion; }); @@ -285,4 +285,9 @@ describe('validateConfig', function () { const res = await validateConfig(config); return assert.equal(res.dedupe, false); }); + + it('should set default `clearSharedCache` value', async () => { + const res = await validateConfig(config); + return assert.equal(res.clearSharedCache, false); + }); }); diff --git a/src/types.ts b/src/types.ts index 0ac33d3..152885c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -37,6 +37,7 @@ export type Config = { veendorVersion?: string, npmVersion?: string, dedupe?: boolean, + clearSharedCache?: boolean, } export type ConfigWithHistory = Config & {