Skip to content

Commit

Permalink
Merge dd73918 into 6e8c98c
Browse files Browse the repository at this point in the history
  • Loading branch information
acburdine committed Nov 5, 2019
2 parents 6e8c98c + dd73918 commit e268541
Show file tree
Hide file tree
Showing 14 changed files with 579 additions and 3 deletions.
29 changes: 29 additions & 0 deletions lib/commands/import.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const Command = require('../command');

class ImportCommand extends Command {
async run(argv) {
const importTask = require('../tasks/import');
const StartCommand = require('./start');
const {SystemError} = require('../errors');

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

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

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

await this.runCommand(StartCommand, argv);
}

await importTask(this.ui, instance, argv.file);
}
}

ImportCommand.description = 'Import a Ghost export';
ImportCommand.params = '[file]';

module.exports = ImportCommand;
23 changes: 23 additions & 0 deletions lib/commands/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,25 @@ class InstallCommand extends Command {
this.system.setEnvironment(true, true);
}

if (argv.fromExport) {
const {parseExport} = require('../tasks/import');
const parsed = parseExport(argv.fromExport);

if (version) {
// TODO(acb): fix error message
this.ui.log('Warning: you specified both a specific version and an export file. The version specified in the export file will take precedence', 'yellow');
}

if (parsed.version.startsWith('0.11')) {
// TODO(acb): message
this.ui.log('Detected a pre-1.x export file. Installing latest v1.x version.', 'green');
version = null;
argv.v1 = true;
} else {
version = parsed.version;
}
}

return this.runCommand(DoctorCommand, Object.assign({
categories: ['install'],
skipInstanceCheck: true,
Expand Down Expand Up @@ -138,6 +157,10 @@ InstallCommand.options = {
description: '[--no-setup] Enable/Disable auto-running the setup command',
type: 'boolean',
default: true
},
'from-export': {
description: 'wip',
type: 'string'
}
};
InstallCommand.runPreChecks = true;
Expand Down
5 changes: 5 additions & 0 deletions lib/commands/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,11 @@ class SetupCommand extends Command {
if (shouldStart) {
await this.runCommand(StartCommand, argv);
}

if (argv.fromExport) {
const task = require('../tasks/import');
await task(this.ui, instance, argv.fromExport);
}
}
}

Expand Down
105 changes: 105 additions & 0 deletions lib/tasks/import/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
const fs = require('fs-extra');
const got = require('got');
const get = require('lodash/get');
const semver = require('semver');
const FormData = require('form-data');
const {Cookie} = require('tough-cookie');

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

const bases = {
1: '/ghost/api/v0.1',
2: '/ghost/api/v2/admin',
3: '/ghost/api/v3/admin'
};

function getBaseUrl(version, url) {
const basePath = bases[semver.major(version)];

if (!basePath) {
throw new SystemError(`Unsupported version: ${version}`);
}

return `${url.replace(/\/?$/, '')}${basePath}`;
}

async function isSetup(version, url) {
const baseUrl = getBaseUrl(version, url);
const {body} = await got('/authentication/setup/', {baseUrl, json: true});
return get(body, 'setup[0].status', false);
}

async function setup(version, url, data) {
const baseUrl = getBaseUrl(version, url);
const {name, email, password, blogTitle} = data;
const body = {
setup: [{name, email, password, blogTitle}]
};

await got.post('/authentication/setup/', {baseUrl, body, json: true});
}

async function getAuthOpts(version, url, {username, password}) {
const baseUrl = getBaseUrl(version, url);

if (semver.major(version) === 1) {
const {body: configBody} = await got('/configuration/', {baseUrl, json: true});
const {clientId, clientSecret} = get(configBody, 'configuration[0]', {});
const {body: authBody} = await got.post('/authentication/token/', {
baseUrl,
json: true,
form: true,
body: {
grant_type: 'password',
client_id: clientId,
client_secret: clientSecret,
username,
password
}
});

return {
baseUrl,
headers: {
Authorization: `Bearer ${authBody.access_token}`
}
};
}

const {headers} = await got.post('/session/', {
baseUrl,
headers: {
Origin: url,
'Content-Type': 'application/json'
},
body: JSON.stringify({username, password})
});

const cookies = (headers['set-cookie'] || [])
.map(Cookie.parse)
.filter(Boolean)
.map(c => c.cookieString());

return {
baseUrl,
headers: {
Origin: url,
Cookie: cookies
}
};
}

async function runImport(version, url, auth, exportFile) {
const authOpts = await getAuthOpts(version, url, auth);
const body = new FormData();

body.append('importfile', fs.createReadStream(exportFile));
await got.post('/db/', {...authOpts, body});
}

module.exports = {
getBaseUrl,
isSetup,
setup,
runImport
};
29 changes: 29 additions & 0 deletions lib/tasks/import/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const validator = require('validator');

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

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

const {email: username} = data;
const {password} = await ui.prompt({
type: 'password',
name: 'password',
message: 'Enter the password for your Ghost user account',
validate: val => validator.isLength(`${val}`, {min: 10}) || 'Password must be at least 10 characters long'
});

return ui.listr([{
title: 'Running blog setup',
task: () => setup(instance.version, url, {...data, password}),
skip: () => isSetup(instance.version, url)
}, {
title: 'Running blog import',
task: () => runImport(instance.version, url, {username, password}, exportFile)
}]);
}

module.exports = task;
module.exports.parseExport = parseExport;
49 changes: 49 additions & 0 deletions lib/tasks/import/parse-export.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const fs = require('fs-extra');
const get = require('lodash/get');
const find = require('lodash/find');
const semver = require('semver');

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

const pre1xVersion = /^00[1-9]$/;

/* eslint-disable camelcase */
function parse(content) {
const data = get(content, 'db[0].data', content.data || null);
const {id: role_id} = find(data.roles, {name: 'Owner'}) || {};
const {user_id} = find(data.roles_users, {role_id}) || {};
const {name, email} = find(data.users, {id: user_id}) || {};
const {value: blogTitle} = find(data.settings, {key: 'title'}) || {};

return {name, email, blogTitle};
}
/* eslint-enable camelcase */

module.exports = function parseExport(file) {
let content = {};

try {
content = fs.readJsonSync(file);
} catch (err) {
throw new SystemError({
message: 'Import file not found or is not valid JSON',
err
});
}

const version = get(content, 'db[0].meta.version', get(content, 'meta.version', null));
if (!version) {
throw new SystemError('Unable to determine export version');
}

const validVersion = pre1xVersion.test(version) || semver.valid(version);
if (!validVersion) {
throw new SystemError(`Unrecognized export version: ${version}`);
}

const data = parse(content);
return {
version: pre1xVersion.test(version) ? '0.11.14' : version,
data
};
};
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"execa": "1.0.0",
"find-plugins": "1.1.7",
"fkill": "6.2.0",
"form-data": "2.5.1",
"fs-extra": "8.1.0",
"generate-password": "1.4.2",
"global-modules": "2.0.0",
Expand Down Expand Up @@ -84,6 +85,7 @@
"symlink-or-copy": "1.2.0",
"systeminformation": "4.14.17",
"tail": "2.0.3",
"tough-cookie": "3.0.1",
"validator": "7.2.0",
"yargs": "14.2.0",
"yarn": "1.19.1"
Expand All @@ -95,6 +97,7 @@
"eslint-plugin-ghost": "0.6.0",
"has-ansi": "4.0.0",
"mocha": "6.2.2",
"nock": "11.7.0",
"nyc": "14.1.1",
"proxyquire": "2.1.3",
"sinon": "7.5.0",
Expand Down
Loading

0 comments on commit e268541

Please sign in to comment.