Skip to content

Commit

Permalink
wip: export command [ci skip]
Browse files Browse the repository at this point in the history
  • Loading branch information
acburdine committed Nov 6, 2019
1 parent e0d05c5 commit 5297a29
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 91 deletions.
30 changes: 30 additions & 0 deletions lib/commands/export.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const Command = require('../command');

class ExportCommand extends Command {
async run(argv) {
const {exportTask} = require('../tasks/import');
const {SystemError} = require('../errors');

const instance = this.system.getInstance();
const isRunning = await instance.isRunning();

if (!isRunning) {
const shouldStart = await this.ui.confirm('Ghost instance is not currently running. Would you like to start it?', true);

if (!shouldStart) {
throw new SystemError('Ghost instance is not currently running');
}

instance.checkEnvironment();
await this.ui.run(() => instance.start(), 'Starting Ghost');
}

await this.ui.run(() => exportTask(this.ui, instance, argv.file), 'Exporting content');
this.ui.log(`Content exported to ${argv.file}`, 'green');
}
}

ExportCommand.description = 'Export content from a blog';
ExportCommand.params = '[file]';

module.exports = ExportCommand;
4 changes: 2 additions & 2 deletions lib/commands/import.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ const Command = require('../command');
class ImportCommand extends Command {
async run(argv) {
const semver = require('semver');
const {importTask} = require('../tasks/import');
const {importTask, parseExport} = require('../tasks/import');
const {SystemError} = require('../errors');

const instance = this.system.getInstance();
const {version} = importTask.parseExport(argv.file);
const {version} = parseExport(argv.file);

if (semver.major(version) === 0 && semver.major(instance.version) > 1) {
throw new SystemError(`v0.x export files can only be imported by Ghost v1.x versions. You are running Ghost v${instance.version}.`);
Expand Down
3 changes: 2 additions & 1 deletion lib/tasks/import/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const {importTask} = require('./tasks');
const {importTask, exportTask} = require('./tasks');
const parseExport = require('./parse-export');

module.exports = {
importTask,
exportTask,
parseExport
};
46 changes: 31 additions & 15 deletions lib/tasks/import/tasks.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
const validator = require('validator');

const {SystemError} = require('../../errors');
const parseExport = require('./parse-export');
const {isSetup, setup, runImport} = require('./api');
const {isSetup, setup, runImport, downloadExport} = require('./api');

const authPrompts = [{
type: 'string',
name: 'username',
message: 'Enter your Ghost administrator email address',
validate: val => validator.isEmail(`${val}`) || 'You must specify a valid email'
}, {
type: 'password',
name: 'password',
message: 'Enter your Ghost administrator password',
validate: val => validator.isLength(`${val}`, {min: 10}) || 'Password must be at least 10 characters long'
}];

async function importTask(ui, instance, exportFile) {
const {data} = parseExport(exportFile);
const url = instance.config.get('url');

const prompts = [{
type: 'password',
name: 'password',
message: 'Enter your Ghost administrator password',
validate: val => validator.isLength(`${val}`, {min: 10}) || 'Password must be at least 10 characters long'
}];
let prompts = authPrompts;

const blogIsSetup = await isSetup(instance.version, url);
if (blogIsSetup) {
prompts.unshift({
type: 'string',
name: 'username',
message: 'Enter your Ghost administrator email address',
validate: val => validator.isEmail(`${val}`) || 'You must specify a valid email'
});
if (!blogIsSetup) {
prompts = authPrompts.slice(1);
}

const {username, password} = await ui.prompt(prompts);
Expand All @@ -37,6 +40,19 @@ async function importTask(ui, instance, exportFile) {
}], false);
}

async function exportTask(ui, instance, exportFile) {
const url = instance.config.get('url');

const blogIsSetup = await isSetup(instance.version, url);
if (!blogIsSetup) {
throw new SystemError('Cannot export content from a blog that hasn\'t been set up.');
}

const authData = await ui.prompt(authPrompts);
await downloadExport(instance.version, url, authData, exportFile);
}

module.exports = {
importTask
importTask,
exportTask
};
10 changes: 10 additions & 0 deletions test/unit/commands/import-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const {expect} = require('chai');
const sinon = require('sinon');
const proxyquire = require('proxyquire').noCallThru();

const {SystemError} = require('../../../lib/errors');

describe('Unit: Commands > import', function () {
it('throws error if importing a 0.x import into a > 1.x blog', async function () {
});
});
194 changes: 121 additions & 73 deletions test/unit/tasks/import/tasks-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,92 +4,140 @@ const proxyquire = require('proxyquire').noCallThru();
const Promise = require('bluebird');
const createConfigStub = require('../../../utils/config-stub');

const {SystemError} = require('../../../../lib/errors');

const modulePath = '../../../../lib/tasks/import/tasks';

describe('Unit: Tasks > Import > Import task', function () {
it('works with already set up blog', async function () {
const parseExport = sinon.stub().returns({data: {name: 'test', email: '[email protected]', blogTitle: 'test'}});
const isSetup = sinon.stub().resolves(true);
const setup = sinon.stub().resolves();
const runImport = sinon.stub().resolves();
describe('Unit: Tasks > Import > Tasks', function () {
describe('importTask', function () {
it('works with already set up blog', async function () {
const parseExport = sinon.stub().returns({data: {name: 'test', email: '[email protected]', blogTitle: 'test'}});
const isSetup = sinon.stub().resolves(true);
const setup = sinon.stub().resolves();
const runImport = sinon.stub().resolves();

const {importTask} = proxyquire(modulePath, {
'./parse-export': parseExport,
'./api': {isSetup, setup, runImport}
});

const prompt = sinon.stub().resolves({username: '[email protected]', password: '1234567890'});
const listr = sinon.stub().callsFake(tasks => Promise.each(tasks, async (t) => {
if (t.enabled && !t.enabled()) {
return;
}

await t.task();
}));
const config = createConfigStub();
config.get.withArgs('url').returns('http://localhost:2368');

await importTask({prompt, listr}, {config, version: '1.0.0'}, 'test-export.json');

expect(parseExport.calledOnceWithExactly('test-export.json')).to.be.true;
expect(isSetup.calledOnceWithExactly('1.0.0', 'http://localhost:2368')).to.be.true;
expect(prompt.calledOnce).to.be.true;
expect(prompt.args[0][0]).to.have.length(2);

const usernamePrompt = prompt.args[0][0][0];
const passwordPrompt = prompt.args[0][0][1];

expect(usernamePrompt.validate('[email protected]')).to.be.true;
expect(usernamePrompt.validate('not an email')).to.include('valid email');
expect(passwordPrompt.validate('1234567890')).to.be.true;
expect(passwordPrompt.validate('short')).to.include('10 characters long');

expect(listr.calledOnce).to.be.true;
expect(setup.called).to.be.false;
expect(runImport.calledOnceWithExactly('1.0.0', 'http://localhost:2368', {
username: '[email protected]',
password: '1234567890'
}, 'test-export.json')).to.be.true;
});

const {importTask} = proxyquire(modulePath, {
'./parse-export': parseExport,
'./api': {isSetup, setup, runImport}
it('works with not setup blog', async function () {
const parseExport = sinon.stub().returns({data: {name: 'test', email: '[email protected]', blogTitle: 'test'}});
const isSetup = sinon.stub().resolves(false);
const setup = sinon.stub().resolves();
const runImport = sinon.stub().resolves();

const {importTask} = proxyquire(modulePath, {
'./parse-export': parseExport,
'./api': {isSetup, setup, runImport}
});

const prompt = sinon.stub().resolves({password: '1234567890'});
const listr = sinon.stub().callsFake(tasks => Promise.each(tasks, async (t) => {
if (t.enabled && !t.enabled()) {
return;
}

await t.task();
}));
const config = createConfigStub();
config.get.withArgs('url').returns('http://localhost:2368');

await importTask({prompt, listr}, {config, version: '1.0.0'}, 'test-export.json');

expect(parseExport.calledOnceWithExactly('test-export.json')).to.be.true;
expect(isSetup.calledOnceWithExactly('1.0.0', 'http://localhost:2368')).to.be.true;
expect(prompt.calledOnce).to.be.true;
expect(prompt.args[0][0]).to.have.length(1);
expect(listr.calledOnce).to.be.true;
expect(setup.calledOnceWithExactly('1.0.0', 'http://localhost:2368', {
name: 'test',
email: '[email protected]',
blogTitle: 'test',
password: '1234567890'
})).to.be.true;
expect(runImport.calledOnceWithExactly('1.0.0', 'http://localhost:2368', {
username: '[email protected]',
password: '1234567890'
}, 'test-export.json')).to.be.true;
});
});

const prompt = sinon.stub().resolves({username: '[email protected]', password: '1234567890'});
const listr = sinon.stub().callsFake(tasks => Promise.each(tasks, async (t) => {
if (t.enabled && !t.enabled()) {
return;
}
describe('exportTask', function () {
it('throws error for not set up blog', async function () {
const isSetup = sinon.stub().resolves(false);
const config = createConfigStub();

await t.task();
}));
const config = createConfigStub();
config.get.withArgs('url').returns('http://localhost:2368');
config.get.withArgs('url').returns('http://localhost:2368');

await importTask({prompt, listr}, {config, version: '1.0.0'}, 'test-export.json');
const {exportTask} = proxyquire(modulePath, {
'./api': {isSetup}
});

expect(parseExport.calledOnceWithExactly('test-export.json')).to.be.true;
expect(isSetup.calledOnceWithExactly('1.0.0', 'http://localhost:2368')).to.be.true;
expect(prompt.calledOnce).to.be.true;
expect(prompt.args[0][0]).to.have.length(2);
try {
await exportTask({}, {config, version: '1.0.0'}, 'test-export.json');
} catch (error) {
expect(error).to.be.an.instanceof(SystemError);
expect(error.message).to.include('Cannot export content');
expect(isSetup.calledOnceWithExactly('1.0.0', 'http://localhost:2368')).to.be.true;
return;
}

const usernamePrompt = prompt.args[0][0][0];
const passwordPrompt = prompt.args[0][0][1];
expect.fail('exportTask should have errored');
});

expect(usernamePrompt.validate('[email protected]')).to.be.true;
expect(usernamePrompt.validate('not an email')).to.include('valid email');
expect(passwordPrompt.validate('1234567890')).to.be.true;
expect(passwordPrompt.validate('short')).to.include('10 characters long');
it('exports content', async function () {
const isSetup = sinon.stub().resolves(true);
const downloadExport = sinon.stub().resolves();
const config = createConfigStub();
const prompt = sinon.stub().resolves({username: 'username', password: 'password'});

expect(listr.calledOnce).to.be.true;
expect(setup.called).to.be.false;
expect(runImport.calledOnceWithExactly('1.0.0', 'http://localhost:2368', {
username: '[email protected]',
password: '1234567890'
}, 'test-export.json')).to.be.true;
});
config.get.withArgs('url').returns('http://localhost:2368');

it('works with not setup blog', async function () {
const parseExport = sinon.stub().returns({data: {name: 'test', email: '[email protected]', blogTitle: 'test'}});
const isSetup = sinon.stub().resolves(false);
const setup = sinon.stub().resolves();
const runImport = sinon.stub().resolves();
const {exportTask} = proxyquire(modulePath, {
'./api': {isSetup, downloadExport}
});

const {importTask} = proxyquire(modulePath, {
'./parse-export': parseExport,
'./api': {isSetup, setup, runImport}
await exportTask({prompt}, {config, version: '1.0.0'}, 'test-export.json');
expect(isSetup.calledOnceWithExactly('1.0.0', 'http://localhost:2368')).to.be.true;
expect(prompt.calledOnce).to.be.true;
expect(downloadExport.calledOnceWithExactly('1.0.0', 'http://localhost:2368', {
username: 'username', password: 'password'
}, 'test-export.json'));
});

const prompt = sinon.stub().resolves({password: '1234567890'});
const listr = sinon.stub().callsFake(tasks => Promise.each(tasks, async (t) => {
if (t.enabled && !t.enabled()) {
return;
}

await t.task();
}));
const config = createConfigStub();
config.get.withArgs('url').returns('http://localhost:2368');

await importTask({prompt, listr}, {config, version: '1.0.0'}, 'test-export.json');

expect(parseExport.calledOnceWithExactly('test-export.json')).to.be.true;
expect(isSetup.calledOnceWithExactly('1.0.0', 'http://localhost:2368')).to.be.true;
expect(prompt.calledOnce).to.be.true;
expect(prompt.args[0][0]).to.have.length(1);
expect(listr.calledOnce).to.be.true;
expect(setup.calledOnceWithExactly('1.0.0', 'http://localhost:2368', {
name: 'test',
email: '[email protected]',
blogTitle: 'test',
password: '1234567890'
})).to.be.true;
expect(runImport.calledOnceWithExactly('1.0.0', 'http://localhost:2368', {
username: '[email protected]',
password: '1234567890'
}, 'test-export.json')).to.be.true;
});
});

0 comments on commit 5297a29

Please sign in to comment.