Skip to content

Commit 0fc5d7f

Browse files
committed
wip: export command [ci skip]
1 parent b2629e5 commit 0fc5d7f

File tree

5 files changed

+194
-89
lines changed

5 files changed

+194
-89
lines changed

lib/commands/export.js

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const Command = require('../command');
2+
3+
class ExportCommand extends Command {
4+
async run(argv) {
5+
const {exportTask} = require('../tasks/import');
6+
const {SystemError} = require('../errors');
7+
8+
const instance = this.system.getInstance();
9+
const isRunning = await instance.isRunning();
10+
11+
if (!isRunning) {
12+
const shouldStart = await this.ui.confirm('Ghost instance is not currently running. Would you like to start it?', true);
13+
14+
if (!shouldStart) {
15+
throw new SystemError('Ghost instance is not currently running');
16+
}
17+
18+
instance.checkEnvironment();
19+
await this.ui.run(() => instance.start(), 'Starting Ghost');
20+
}
21+
22+
await this.ui.run(() => exportTask(this.ui, instance, argv.file), 'Exporting content');
23+
this.ui.log(`Content exported to ${argv.file}`, 'green');
24+
}
25+
}
26+
27+
ExportCommand.description = 'Export content from a blog';
28+
ExportCommand.params = '[file]';
29+
30+
module.exports = ExportCommand;

lib/tasks/import/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
const {importTask} = require('./tasks');
1+
const {importTask, exportTask} = require('./tasks');
22
const parseExport = require('./parse-export');
33

44
module.exports = {
55
importTask,
6+
exportTask,
67
parseExport
78
};

lib/tasks/import/tasks.js

+31-15
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,30 @@
11
const validator = require('validator');
22

3+
const {SystemError} = require('../../errors');
34
const parseExport = require('./parse-export');
4-
const {isSetup, setup, runImport} = require('./api');
5+
const {isSetup, setup, runImport, downloadExport} = require('./api');
6+
7+
const authPrompts = [{
8+
type: 'string',
9+
name: 'username',
10+
message: 'Enter your Ghost administrator email address',
11+
validate: val => validator.isEmail(`${val}`) || 'You must specify a valid email'
12+
}, {
13+
type: 'password',
14+
name: 'password',
15+
message: 'Enter your Ghost administrator password',
16+
validate: val => validator.isLength(`${val}`, {min: 10}) || 'Password must be at least 10 characters long'
17+
}];
518

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

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

1725
const blogIsSetup = await isSetup(instance.version, url);
18-
if (blogIsSetup) {
19-
prompts.unshift({
20-
type: 'string',
21-
name: 'username',
22-
message: 'Enter your Ghost administrator email address',
23-
validate: val => validator.isEmail(`${val}`) || 'You must specify a valid email'
24-
});
26+
if (!blogIsSetup) {
27+
prompts = authPrompts.slice(1);
2528
}
2629

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

43+
async function exportTask(ui, instance, exportFile) {
44+
const url = instance.config.get('url');
45+
46+
const blogIsSetup = await isSetup(instance.version, url);
47+
if (!blogIsSetup) {
48+
throw new SystemError('Cannot export content from a blog that hasn\'t been set up.');
49+
}
50+
51+
const authData = await ui.prompt(authPrompts);
52+
await downloadExport(instance.version, url, authData, exportFile);
53+
}
54+
4055
module.exports = {
41-
importTask
56+
importTask,
57+
exportTask
4258
};

test/unit/commands/import-spec.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const {expect} = require('chai');
2+
const sinon = require('sinon');
3+
const proxyquire = require('proxyquire').noCallThru();
4+
5+
const {SystemError} = require('../../../lib/errors');
6+
7+
describe('Unit: Commands > import', function () {
8+
it('throws error if importing a 0.x import into a > 1.x blog', async function () {
9+
});
10+
});

test/unit/tasks/import/tasks-spec.js

+121-73
Original file line numberDiff line numberDiff line change
@@ -4,92 +4,140 @@ const proxyquire = require('proxyquire').noCallThru();
44
const Promise = require('bluebird');
55
const createConfigStub = require('../../../utils/config-stub');
66

7+
const {SystemError} = require('../../../../lib/errors');
8+
79
const modulePath = '../../../../lib/tasks/import/tasks';
810

9-
describe('Unit: Tasks > Import > Import task', function () {
10-
it('works with already set up blog', async function () {
11-
const parseExport = sinon.stub().returns({data: {name: 'test', email: '[email protected]', blogTitle: 'test'}});
12-
const isSetup = sinon.stub().resolves(true);
13-
const setup = sinon.stub().resolves();
14-
const runImport = sinon.stub().resolves();
11+
describe('Unit: Tasks > Import > Tasks', function () {
12+
describe('importTask', function () {
13+
it('works with already set up blog', async function () {
14+
const parseExport = sinon.stub().returns({data: {name: 'test', email: '[email protected]', blogTitle: 'test'}});
15+
const isSetup = sinon.stub().resolves(true);
16+
const setup = sinon.stub().resolves();
17+
const runImport = sinon.stub().resolves();
18+
19+
const {importTask} = proxyquire(modulePath, {
20+
'./parse-export': parseExport,
21+
'./api': {isSetup, setup, runImport}
22+
});
23+
24+
const prompt = sinon.stub().resolves({username: '[email protected]', password: '1234567890'});
25+
const listr = sinon.stub().callsFake(tasks => Promise.each(tasks, async (t) => {
26+
if (t.enabled && !t.enabled()) {
27+
return;
28+
}
29+
30+
await t.task();
31+
}));
32+
const config = createConfigStub();
33+
config.get.withArgs('url').returns('http://localhost:2368');
34+
35+
await importTask({prompt, listr}, {config, version: '1.0.0'}, 'test-export.json');
36+
37+
expect(parseExport.calledOnceWithExactly('test-export.json')).to.be.true;
38+
expect(isSetup.calledOnceWithExactly('1.0.0', 'http://localhost:2368')).to.be.true;
39+
expect(prompt.calledOnce).to.be.true;
40+
expect(prompt.args[0][0]).to.have.length(2);
41+
42+
const usernamePrompt = prompt.args[0][0][0];
43+
const passwordPrompt = prompt.args[0][0][1];
44+
45+
expect(usernamePrompt.validate('[email protected]')).to.be.true;
46+
expect(usernamePrompt.validate('not an email')).to.include('valid email');
47+
expect(passwordPrompt.validate('1234567890')).to.be.true;
48+
expect(passwordPrompt.validate('short')).to.include('10 characters long');
49+
50+
expect(listr.calledOnce).to.be.true;
51+
expect(setup.called).to.be.false;
52+
expect(runImport.calledOnceWithExactly('1.0.0', 'http://localhost:2368', {
53+
username: '[email protected]',
54+
password: '1234567890'
55+
}, 'test-export.json')).to.be.true;
56+
});
1557

16-
const {importTask} = proxyquire(modulePath, {
17-
'./parse-export': parseExport,
18-
'./api': {isSetup, setup, runImport}
58+
it('works with not setup blog', async function () {
59+
const parseExport = sinon.stub().returns({data: {name: 'test', email: '[email protected]', blogTitle: 'test'}});
60+
const isSetup = sinon.stub().resolves(false);
61+
const setup = sinon.stub().resolves();
62+
const runImport = sinon.stub().resolves();
63+
64+
const {importTask} = proxyquire(modulePath, {
65+
'./parse-export': parseExport,
66+
'./api': {isSetup, setup, runImport}
67+
});
68+
69+
const prompt = sinon.stub().resolves({password: '1234567890'});
70+
const listr = sinon.stub().callsFake(tasks => Promise.each(tasks, async (t) => {
71+
if (t.enabled && !t.enabled()) {
72+
return;
73+
}
74+
75+
await t.task();
76+
}));
77+
const config = createConfigStub();
78+
config.get.withArgs('url').returns('http://localhost:2368');
79+
80+
await importTask({prompt, listr}, {config, version: '1.0.0'}, 'test-export.json');
81+
82+
expect(parseExport.calledOnceWithExactly('test-export.json')).to.be.true;
83+
expect(isSetup.calledOnceWithExactly('1.0.0', 'http://localhost:2368')).to.be.true;
84+
expect(prompt.calledOnce).to.be.true;
85+
expect(prompt.args[0][0]).to.have.length(1);
86+
expect(listr.calledOnce).to.be.true;
87+
expect(setup.calledOnceWithExactly('1.0.0', 'http://localhost:2368', {
88+
name: 'test',
89+
90+
blogTitle: 'test',
91+
password: '1234567890'
92+
})).to.be.true;
93+
expect(runImport.calledOnceWithExactly('1.0.0', 'http://localhost:2368', {
94+
username: '[email protected]',
95+
password: '1234567890'
96+
}, 'test-export.json')).to.be.true;
1997
});
98+
});
2099

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

27-
await t.task();
28-
}));
29-
const config = createConfigStub();
30-
config.get.withArgs('url').returns('http://localhost:2368');
105+
config.get.withArgs('url').returns('http://localhost:2368');
31106

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

34-
expect(parseExport.calledOnceWithExactly('test-export.json')).to.be.true;
35-
expect(isSetup.calledOnceWithExactly('1.0.0', 'http://localhost:2368')).to.be.true;
36-
expect(prompt.calledOnce).to.be.true;
37-
expect(prompt.args[0][0]).to.have.length(2);
111+
try {
112+
await exportTask({}, {config, version: '1.0.0'}, 'test-export.json');
113+
} catch (error) {
114+
expect(error).to.be.an.instanceof(SystemError);
115+
expect(error.message).to.include('Cannot export content');
116+
expect(isSetup.calledOnceWithExactly('1.0.0', 'http://localhost:2368')).to.be.true;
117+
return;
118+
}
38119

39-
const usernamePrompt = prompt.args[0][0][0];
40-
const passwordPrompt = prompt.args[0][0][1];
120+
expect.fail('exportTask should have errored');
121+
});
41122

42-
expect(usernamePrompt.validate('[email protected]')).to.be.true;
43-
expect(usernamePrompt.validate('not an email')).to.include('valid email');
44-
expect(passwordPrompt.validate('1234567890')).to.be.true;
45-
expect(passwordPrompt.validate('short')).to.include('10 characters long');
123+
it('exports content', async function () {
124+
const isSetup = sinon.stub().resolves(true);
125+
const downloadExport = sinon.stub().resolves();
126+
const config = createConfigStub();
127+
const prompt = sinon.stub().resolves({username: 'username', password: 'password'});
46128

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

55-
it('works with not setup blog', async function () {
56-
const parseExport = sinon.stub().returns({data: {name: 'test', email: '[email protected]', blogTitle: 'test'}});
57-
const isSetup = sinon.stub().resolves(false);
58-
const setup = sinon.stub().resolves();
59-
const runImport = sinon.stub().resolves();
131+
const {exportTask} = proxyquire(modulePath, {
132+
'./api': {isSetup, downloadExport}
133+
});
60134

61-
const {importTask} = proxyquire(modulePath, {
62-
'./parse-export': parseExport,
63-
'./api': {isSetup, setup, runImport}
135+
await exportTask({prompt}, {config, version: '1.0.0'}, 'test-export.json');
136+
expect(isSetup.calledOnceWithExactly('1.0.0', 'http://localhost:2368')).to.be.true;
137+
expect(prompt.calledOnce).to.be.true;
138+
expect(downloadExport.calledOnceWithExactly('1.0.0', 'http://localhost:2368', {
139+
username: 'username', password: 'password'
140+
}, 'test-export.json'));
64141
});
65-
66-
const prompt = sinon.stub().resolves({password: '1234567890'});
67-
const listr = sinon.stub().callsFake(tasks => Promise.each(tasks, async (t) => {
68-
if (t.enabled && !t.enabled()) {
69-
return;
70-
}
71-
72-
await t.task();
73-
}));
74-
const config = createConfigStub();
75-
config.get.withArgs('url').returns('http://localhost:2368');
76-
77-
await importTask({prompt, listr}, {config, version: '1.0.0'}, 'test-export.json');
78-
79-
expect(parseExport.calledOnceWithExactly('test-export.json')).to.be.true;
80-
expect(isSetup.calledOnceWithExactly('1.0.0', 'http://localhost:2368')).to.be.true;
81-
expect(prompt.calledOnce).to.be.true;
82-
expect(prompt.args[0][0]).to.have.length(1);
83-
expect(listr.calledOnce).to.be.true;
84-
expect(setup.calledOnceWithExactly('1.0.0', 'http://localhost:2368', {
85-
name: 'test',
86-
87-
blogTitle: 'test',
88-
password: '1234567890'
89-
})).to.be.true;
90-
expect(runImport.calledOnceWithExactly('1.0.0', 'http://localhost:2368', {
91-
username: '[email protected]',
92-
password: '1234567890'
93-
}, 'test-export.json')).to.be.true;
94142
});
95143
});

0 commit comments

Comments
 (0)