From 0bd929bf6f3301d7e1765ee12c8057f043b65ec1 Mon Sep 17 00:00:00 2001 From: Austin Burdine Date: Sun, 27 Jan 2019 09:08:24 -0500 Subject: [PATCH] fix(install): ignore dotfiles except for .ghost-cli in dir check closes #868 - add dir-is-empty util that checks empty directories correctly --- lib/commands/install.js | 8 +++--- lib/utils/dir-is-empty.js | 14 ++++++++++ test/unit/commands/install-spec.js | 41 ++++++++++++++---------------- test/unit/utils/dir-is-empty.js | 33 ++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 27 deletions(-) create mode 100644 lib/utils/dir-is-empty.js create mode 100644 test/unit/utils/dir-is-empty.js diff --git a/lib/commands/install.js b/lib/commands/install.js index 11d2f9690..e40d4cd31 100644 --- a/lib/commands/install.js +++ b/lib/commands/install.js @@ -13,17 +13,15 @@ class InstallCommand extends Command { } run(argv) { - const fs = require('fs-extra'); - const every = require('lodash/every'); const errors = require('../errors'); const yarnInstall = require('../tasks/yarn-install'); + const dirIsEmpty = require('../utils/dir-is-empty'); const ensureStructure = require('../tasks/ensure-structure'); let version = argv.version; - const filesInDir = fs.readdirSync(process.cwd()); - // Check if there are existing files that *aren't* ghost-cli debug log files - if (filesInDir.length && !every(filesInDir, file => file.match(/^ghost-cli-debug-.*\.log$/i))) { + // Check if the directory is empty + if (!dirIsEmpty(process.cwd())) { return Promise.reject(new errors.SystemError('Current directory is not empty, Ghost cannot be installed here.')); } diff --git a/lib/utils/dir-is-empty.js b/lib/utils/dir-is-empty.js new file mode 100644 index 000000000..c764d0bed --- /dev/null +++ b/lib/utils/dir-is-empty.js @@ -0,0 +1,14 @@ +const fs = require('fs'); + +const debugLogRegex = /^ghost-cli-debug-.*\.log$/i; +const importantDotfiles = ['.ghost-cli']; + +module.exports = function dirIsEmpty(dir) { + const files = fs.readdirSync(dir); + + if (!files.length) { + return true; + } + + return files.every(file => file.match(debugLogRegex) || (file.startsWith('.') && !importantDotfiles.includes(file))); +}; diff --git a/test/unit/commands/install-spec.js b/test/unit/commands/install-spec.js index caf5fb113..8268ef600 100644 --- a/test/unit/commands/install-spec.js +++ b/test/unit/commands/install-spec.js @@ -40,13 +40,10 @@ describe('Unit: Commands > Install', function () { }); it('rejects if directory is not empty', function () { - const readdirStub = sinon.stub().returns([ - '.ghost-cli', - 'README.md' - ]); + const dirEmptyStub = sinon.stub().returns(false); const InstallCommand = proxyquire(modulePath, { - 'fs-extra': {readdirSync: readdirStub} + '../utils/dir-is-empty': dirEmptyStub }); const testInstance = new InstallCommand({}, {}); @@ -55,16 +52,16 @@ describe('Unit: Commands > Install', function () { }).catch((error) => { expect(error).to.be.an.instanceof(errors.SystemError); expect(error.message).to.match(/Current directory is not empty/); - expect(readdirStub.calledOnce).to.be.true; + expect(dirEmptyStub.calledOnce).to.be.true; }); }); it('calls install checks first', function () { - const readdirStub = sinon.stub().returns(['ghost-cli-debug-1234.log']); + const dirEmptyStub = sinon.stub().returns(true); const listrStub = sinon.stub().rejects(); const InstallCommand = proxyquire(modulePath, { - 'fs-extra': {readdirSync: readdirStub}, + '../utils/dir-is-empty': dirEmptyStub, './doctor': {doctorCommand: true} }); const testInstance = new InstallCommand({listr: listrStub}, {}); @@ -73,7 +70,7 @@ describe('Unit: Commands > Install', function () { return testInstance.run({argv: true}).then(() => { expect(false, 'run should have rejected').to.be.true; }).catch(() => { - expect(readdirStub.calledOnce).to.be.true; + expect(dirEmptyStub.calledOnce).to.be.true; expect(runCommandStub.calledOnce).to.be.true; expect(runCommandStub.calledWithExactly( {doctorCommand: true}, @@ -84,14 +81,14 @@ describe('Unit: Commands > Install', function () { }); it('runs local install when command is `ghost install local`', function () { - const readdirStub = sinon.stub().returns([]); + const dirEmptyStub = sinon.stub().returns(true); const listrStub = sinon.stub(); listrStub.onFirstCall().resolves(); listrStub.onSecondCall().rejects(); const setEnvironmentStub = sinon.stub(); const InstallCommand = proxyquire(modulePath, { - 'fs-extra': {readdirSync: readdirStub} + '../utils/dir-is-empty': dirEmptyStub }); const testInstance = new InstallCommand({listr: listrStub}, {cliVersion: '1.0.0', setEnvironment: setEnvironmentStub}); const runCommandStub = sinon.stub(testInstance, 'runCommand').resolves(); @@ -99,7 +96,7 @@ describe('Unit: Commands > Install', function () { return testInstance.run({version: 'local', zip: '', v1: true}).then(() => { expect(false, 'run should have rejected').to.be.true; }).catch(() => { - expect(readdirStub.calledOnce).to.be.true; + expect(dirEmptyStub.calledOnce).to.be.true; expect(runCommandStub.calledOnce).to.be.true; expect(listrStub.calledOnce).to.be.true; expect(listrStub.args[0][1]).to.deep.equal({ @@ -114,14 +111,14 @@ describe('Unit: Commands > Install', function () { }); it('runs local install when command is `ghost install --local`', function () { - const readdirStub = sinon.stub().returns([]); + const dirEmptyStub = sinon.stub().returns(true); const listrStub = sinon.stub(); listrStub.onFirstCall().resolves(); listrStub.onSecondCall().rejects(); const setEnvironmentStub = sinon.stub(); const InstallCommand = proxyquire(modulePath, { - 'fs-extra': {readdirSync: readdirStub} + '../utils/dir-is-empty': dirEmptyStub }); const testInstance = new InstallCommand({listr: listrStub}, {cliVersion: '1.0.0', setEnvironment: setEnvironmentStub}); const runCommandStub = sinon.stub(testInstance, 'runCommand').resolves(); @@ -129,7 +126,7 @@ describe('Unit: Commands > Install', function () { return testInstance.run({version: '1.5.0', local: true, zip: '', v1: false}).then(() => { expect(false, 'run should have rejected').to.be.true; }).catch(() => { - expect(readdirStub.calledOnce).to.be.true; + expect(dirEmptyStub.calledOnce).to.be.true; expect(runCommandStub.calledOnce).to.be.true; expect(listrStub.calledOnce).to.be.true; expect(listrStub.args[0][1]).to.deep.equal({ @@ -144,15 +141,15 @@ describe('Unit: Commands > Install', function () { }); it('calls all tasks and returns after tasks run if --no-setup is passed', function () { - const readdirStub = sinon.stub().returns([]); + const dirEmptyStub = sinon.stub().returns(true); const yarnInstallStub = sinon.stub().resolves(); const ensureStructureStub = sinon.stub().resolves(); const listrStub = sinon.stub().callsFake((tasks, ctx) => Promise.each(tasks, task => task.task(ctx, {}))); const InstallCommand = proxyquire(modulePath, { - 'fs-extra': {readdirSync: readdirStub}, '../tasks/yarn-install': yarnInstallStub, - '../tasks/ensure-structure': ensureStructureStub + '../tasks/ensure-structure': ensureStructureStub, + '../utils/dir-is-empty': dirEmptyStub }); const testInstance = new InstallCommand({listr: listrStub}, {cliVersion: '1.0.0'}); const runCommandStub = sinon.stub(testInstance, 'runCommand').resolves(); @@ -161,7 +158,7 @@ describe('Unit: Commands > Install', function () { const casperStub = sinon.stub(testInstance, 'casper').resolves(); return testInstance.run({version: '1.0.0', setup: false}).then(() => { - expect(readdirStub.calledOnce).to.be.true; + expect(dirEmptyStub.calledOnce).to.be.true; expect(listrStub.calledTwice).to.be.true; expect(yarnInstallStub.calledOnce).to.be.true; expect(ensureStructureStub.calledOnce).to.be.true; @@ -173,19 +170,19 @@ describe('Unit: Commands > Install', function () { }); it('sets local and runs setup command if setup is true', function () { - const readdirStub = sinon.stub().returns([]); + const dirEmptyStub = sinon.stub().returns(true); const listrStub = sinon.stub().resolves(); const setEnvironmentStub = sinon.stub(); const InstallCommand = proxyquire(modulePath, { - 'fs-extra': {readdirSync: readdirStub}, + '../utils/dir-is-empty': dirEmptyStub, './setup': {SetupCommand: true} }); const testInstance = new InstallCommand({listr: listrStub}, {cliVersion: '1.0.0', setEnvironment: setEnvironmentStub}); const runCommandStub = sinon.stub(testInstance, 'runCommand').resolves(); return testInstance.run({version: 'local', setup: true, zip: ''}).then(() => { - expect(readdirStub.calledOnce).to.be.true; + expect(dirEmptyStub.calledOnce).to.be.true; expect(listrStub.calledOnce).to.be.true; expect(setEnvironmentStub.calledOnce).to.be.true; expect(setEnvironmentStub.calledWithExactly(true, true)); diff --git a/test/unit/utils/dir-is-empty.js b/test/unit/utils/dir-is-empty.js new file mode 100644 index 000000000..5d2e137b0 --- /dev/null +++ b/test/unit/utils/dir-is-empty.js @@ -0,0 +1,33 @@ +const proxyquire = require('proxyquire'); +const {expect} = require('chai'); + +const proxy = files => proxyquire('../../../lib/utils/dir-is-empty', { + fs: {readdirSync: () => files} +}); + +describe('Unit: Utils > dirIsEmpty', function () { + it('returns true if directory is empty', function () { + const fn = proxy([]); + expect(fn('dir')).to.be.true; + }); + + it('returns true if directory contains ghost debug log files', function () { + const fn = proxy(['ghost-cli-debug-1234.log']); + expect(fn('dir')).to.be.true; + }); + + it('returns true if directory contains dotfiles other than .ghost-cli', function () { + const fn = proxy(['.npmrc', '.gitignore']); + expect(fn('dir')).to.be.true; + }); + + it('returns false if directory contains .ghost-cli file', function () { + const fn = proxy(['.ghost-cli']); + expect(fn('dir')).to.be.false; + }); + + it('returns false if directory contains other files', function () { + const fn = proxy(['file.txt', 'file2.txt']); + expect(fn('dir')).to.be.false; + }); +});