mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-08 12:09:43 +03:00
Merge pull request #5940 from vdemedes/read-themes
Add readThemes() utility to get a list of themes
This commit is contained in:
commit
932f12160a
@ -10,6 +10,7 @@ var path = require('path'),
|
|||||||
knex = require('knex'),
|
knex = require('knex'),
|
||||||
validator = require('validator'),
|
validator = require('validator'),
|
||||||
readDirectory = require('../utils/read-directory'),
|
readDirectory = require('../utils/read-directory'),
|
||||||
|
readThemes = require('../utils/read-themes'),
|
||||||
errors = require('../errors'),
|
errors = require('../errors'),
|
||||||
configUrl = require('./url'),
|
configUrl = require('./url'),
|
||||||
packageInfo = require('../../../package.json'),
|
packageInfo = require('../../../package.json'),
|
||||||
@ -75,7 +76,7 @@ ConfigManager.prototype.init = function (rawConfig) {
|
|||||||
// just the object appropriate for this NODE_ENV
|
// just the object appropriate for this NODE_ENV
|
||||||
self.set(rawConfig);
|
self.set(rawConfig);
|
||||||
|
|
||||||
return Promise.all([readDirectory(self._config.paths.themePath), readDirectory(self._config.paths.appPath)]).then(function (paths) {
|
return Promise.all([readThemes(self._config.paths.themePath), readDirectory(self._config.paths.appPath)]).then(function (paths) {
|
||||||
self._config.paths.availableThemes = paths[0];
|
self._config.paths.availableThemes = paths[0];
|
||||||
self._config.paths.availableApps = paths[1];
|
self._config.paths.availableApps = paths[1];
|
||||||
return self._config;
|
return self._config;
|
||||||
|
@ -4,7 +4,7 @@ var schema = require('../schema').tables,
|
|||||||
Promise = require('bluebird'),
|
Promise = require('bluebird'),
|
||||||
errors = require('../../errors'),
|
errors = require('../../errors'),
|
||||||
config = require('../../config'),
|
config = require('../../config'),
|
||||||
readDirectory = require('../../utils/read-directory'),
|
readThemes = require('../../utils/read-themes'),
|
||||||
|
|
||||||
validateSchema,
|
validateSchema,
|
||||||
validateSettings,
|
validateSettings,
|
||||||
@ -112,7 +112,7 @@ validateActiveTheme = function validateActiveTheme(themeName) {
|
|||||||
// A Promise that will resolve to an object with a property for each installed theme.
|
// A Promise that will resolve to an object with a property for each installed theme.
|
||||||
// This is necessary because certain configuration data is only available while Ghost
|
// This is necessary because certain configuration data is only available while Ghost
|
||||||
// is running and at times the validations are used when it's not (e.g. tests)
|
// is running and at times the validations are used when it's not (e.g. tests)
|
||||||
availableThemes = readDirectory(config.paths.themePath);
|
availableThemes = readThemes(config.paths.themePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return availableThemes.then(function then(themes) {
|
return availableThemes.then(function then(themes) {
|
||||||
|
@ -22,7 +22,7 @@ function readDirectory(dir, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ignore = options.ignore || [];
|
ignore = options.ignore || [];
|
||||||
ignore.push('node_modules', 'bower_components');
|
ignore.push('node_modules', 'bower_components', '.DS_Store');
|
||||||
|
|
||||||
return readDir(dir)
|
return readDir(dir)
|
||||||
.filter(function (filename) {
|
.filter(function (filename) {
|
||||||
|
46
core/server/utils/read-themes.js
Normal file
46
core/server/utils/read-themes.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
|
||||||
|
var readDirectory = require('./read-directory'),
|
||||||
|
Promise = require('bluebird'),
|
||||||
|
join = require('path').join,
|
||||||
|
fs = require('fs'),
|
||||||
|
|
||||||
|
statFile = Promise.promisify(fs.stat);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read themes
|
||||||
|
*/
|
||||||
|
|
||||||
|
function readThemes(dir) {
|
||||||
|
var originalTree;
|
||||||
|
|
||||||
|
return readDirectory(dir)
|
||||||
|
.tap(function (tree) {
|
||||||
|
originalTree = tree;
|
||||||
|
})
|
||||||
|
.then(Object.keys)
|
||||||
|
.filter(function (file) {
|
||||||
|
var path = join(dir, file);
|
||||||
|
|
||||||
|
return statFile(path).then(function (stat) {
|
||||||
|
return stat.isDirectory();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(function (directories) {
|
||||||
|
var themes = {};
|
||||||
|
|
||||||
|
directories.forEach(function (name) {
|
||||||
|
themes[name] = originalTree[name];
|
||||||
|
});
|
||||||
|
|
||||||
|
return themes;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose `read-themes`
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = readThemes;
|
@ -2,7 +2,7 @@
|
|||||||
* Dependencies
|
* Dependencies
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var readDirectory = require('./read-directory'),
|
var readThemes = require('./read-themes'),
|
||||||
Promise = require('bluebird'),
|
Promise = require('bluebird'),
|
||||||
_ = require('lodash');
|
_ = require('lodash');
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ function validateThemes(dir) {
|
|||||||
errors: []
|
errors: []
|
||||||
};
|
};
|
||||||
|
|
||||||
return readDirectory(dir)
|
return readThemes(dir)
|
||||||
.tap(function (themes) {
|
.tap(function (themes) {
|
||||||
_.each(themes, function (theme, name) {
|
_.each(themes, function (theme, name) {
|
||||||
var hasPackageJson, warning;
|
var hasPackageJson, warning;
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
/*globals describe, it*/
|
/*globals describe, it*/
|
||||||
/*jshint expr:true*/
|
/*jshint expr:true*/
|
||||||
var should = require('should'),
|
var should = require('should'),
|
||||||
utils = require('../../server/utils');
|
parsePackageJson = require('../../server/utils/parse-package-json'),
|
||||||
|
validateThemes = require('../../server/utils/validate-themes'),
|
||||||
|
readDirectory = require('../../server/utils/read-directory'),
|
||||||
|
readThemes = require('../../server/utils/read-themes'),
|
||||||
|
tempfile = require('../utils/tempfile'),
|
||||||
|
utils = require('../../server/utils'),
|
||||||
|
join = require('path').join,
|
||||||
|
fs = require('fs');
|
||||||
|
|
||||||
// To stop jshint complaining
|
// To stop jshint complaining
|
||||||
should.equal(true, true);
|
should.equal(true, true);
|
||||||
|
|
||||||
|
describe('Server Utilities', function () {
|
||||||
describe('Safe String', function () {
|
describe('Safe String', function () {
|
||||||
var safeString = utils.safeString,
|
var safeString = utils.safeString,
|
||||||
options = {};
|
options = {};
|
||||||
@ -79,3 +87,266 @@ describe('Safe String', function () {
|
|||||||
result.should.equal('-slug--with--invalid-characters-ni');
|
result.should.equal('-slug--with--invalid-characters-ni');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('parse-package-json', function () {
|
||||||
|
it('should parse valid package.json', function (done) {
|
||||||
|
var pkgJson, tmpPath;
|
||||||
|
|
||||||
|
tmpPath = tempfile();
|
||||||
|
pkgJson = JSON.stringify({
|
||||||
|
name: 'test',
|
||||||
|
version: '0.0.0'
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.writeFileSync(tmpPath, pkgJson);
|
||||||
|
|
||||||
|
parsePackageJson(tmpPath)
|
||||||
|
.then(function (pkg) {
|
||||||
|
pkg.should.eql({
|
||||||
|
name: 'test',
|
||||||
|
version: '0.0.0'
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail when name is missing', function (done) {
|
||||||
|
var pkgJson, tmpPath;
|
||||||
|
|
||||||
|
tmpPath = tempfile();
|
||||||
|
pkgJson = JSON.stringify({
|
||||||
|
version: '0.0.0'
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.writeFileSync(tmpPath, pkgJson);
|
||||||
|
|
||||||
|
parsePackageJson(tmpPath)
|
||||||
|
.then(function () {
|
||||||
|
done(new Error('parsePackageJson succeeded, but should\'ve failed'));
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
err.message.should.equal('"name" or "version" is missing from theme package.json file.');
|
||||||
|
err.context.should.equal(tmpPath);
|
||||||
|
err.help.should.equal('This will be required in future. Please see http://docs.ghost.org/themes/');
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail when version is missing', function (done) {
|
||||||
|
var pkgJson, tmpPath;
|
||||||
|
|
||||||
|
tmpPath = tempfile();
|
||||||
|
pkgJson = JSON.stringify({
|
||||||
|
name: 'test'
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.writeFileSync(tmpPath, pkgJson);
|
||||||
|
|
||||||
|
parsePackageJson(tmpPath)
|
||||||
|
.then(function () {
|
||||||
|
done(new Error('parsePackageJson succeeded, but should\'ve failed'));
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
err.message.should.equal('"name" or "version" is missing from theme package.json file.');
|
||||||
|
err.context.should.equal(tmpPath);
|
||||||
|
err.help.should.equal('This will be required in future. Please see http://docs.ghost.org/themes/');
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail when JSON is invalid', function (done) {
|
||||||
|
var pkgJson, tmpPath;
|
||||||
|
|
||||||
|
tmpPath = tempfile();
|
||||||
|
pkgJson = '{name:"test"}';
|
||||||
|
|
||||||
|
fs.writeFileSync(tmpPath, pkgJson);
|
||||||
|
|
||||||
|
parsePackageJson(tmpPath)
|
||||||
|
.then(function () {
|
||||||
|
done(new Error('parsePackageJson succeeded, but should\'ve failed'));
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
err.message.should.equal('Theme package.json file is malformed');
|
||||||
|
err.context.should.equal(tmpPath);
|
||||||
|
err.help.should.equal('This will be required in future. Please see http://docs.ghost.org/themes/');
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail when file is missing', function (done) {
|
||||||
|
var tmpPath = tempfile();
|
||||||
|
|
||||||
|
parsePackageJson(tmpPath)
|
||||||
|
.then(function () {
|
||||||
|
done(new Error('parsePackageJson succeeded, but should\'ve failed'));
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
err.message.should.equal('Could not read package.json file');
|
||||||
|
err.context.should.equal(tmpPath);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('read-directory', function () {
|
||||||
|
it('should read directory recursively', function (done) {
|
||||||
|
var themePath = tempfile();
|
||||||
|
|
||||||
|
// create example theme
|
||||||
|
fs.mkdirSync(themePath);
|
||||||
|
fs.mkdirSync(join(themePath, 'partials'));
|
||||||
|
fs.writeFileSync(join(themePath, 'index.hbs'));
|
||||||
|
fs.writeFileSync(join(themePath, 'partials', 'navigation.hbs'));
|
||||||
|
|
||||||
|
readDirectory(themePath)
|
||||||
|
.then(function (tree) {
|
||||||
|
tree.should.eql({
|
||||||
|
partials: {
|
||||||
|
'navigation.hbs': join(themePath, 'partials', 'navigation.hbs')
|
||||||
|
},
|
||||||
|
'index.hbs': join(themePath, 'index.hbs')
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should read directory and ignore unneeded items', function (done) {
|
||||||
|
var themePath = tempfile();
|
||||||
|
|
||||||
|
// create example theme
|
||||||
|
fs.mkdirSync(themePath);
|
||||||
|
fs.mkdirSync(join(themePath, 'partials'));
|
||||||
|
fs.writeFileSync(join(themePath, 'index.hbs'));
|
||||||
|
fs.writeFileSync(join(themePath, 'partials', 'navigation.hbs'));
|
||||||
|
|
||||||
|
// create some trash
|
||||||
|
fs.mkdirSync(join(themePath, 'node_modules'));
|
||||||
|
fs.mkdirSync(join(themePath, 'bower_components'));
|
||||||
|
fs.mkdirSync(join(themePath, '.git'));
|
||||||
|
fs.writeFileSync(join(themePath, '.DS_Store'));
|
||||||
|
|
||||||
|
readDirectory(themePath, {ignore: ['.git']})
|
||||||
|
.then(function (tree) {
|
||||||
|
tree.should.eql({
|
||||||
|
partials: {
|
||||||
|
'navigation.hbs': join(themePath, 'partials', 'navigation.hbs')
|
||||||
|
},
|
||||||
|
'index.hbs': join(themePath, 'index.hbs')
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should read directory and parse package.json files', function (done) {
|
||||||
|
var themePath, pkgJson;
|
||||||
|
|
||||||
|
themePath = tempfile();
|
||||||
|
pkgJson = JSON.stringify({
|
||||||
|
name: 'test',
|
||||||
|
version: '0.0.0'
|
||||||
|
});
|
||||||
|
|
||||||
|
// create example theme
|
||||||
|
fs.mkdirSync(themePath);
|
||||||
|
fs.mkdirSync(join(themePath, 'partials'));
|
||||||
|
fs.writeFileSync(join(themePath, 'package.json'), pkgJson);
|
||||||
|
fs.writeFileSync(join(themePath, 'index.hbs'));
|
||||||
|
fs.writeFileSync(join(themePath, 'partials', 'navigation.hbs'));
|
||||||
|
|
||||||
|
readDirectory(themePath)
|
||||||
|
.then(function (tree) {
|
||||||
|
tree.should.eql({
|
||||||
|
partials: {
|
||||||
|
'navigation.hbs': join(themePath, 'partials', 'navigation.hbs')
|
||||||
|
},
|
||||||
|
'index.hbs': join(themePath, 'index.hbs'),
|
||||||
|
'package.json': {
|
||||||
|
name: 'test',
|
||||||
|
version: '0.0.0'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('read-themes', function () {
|
||||||
|
it('should read directory and include only folders', function (done) {
|
||||||
|
var themesPath = tempfile();
|
||||||
|
|
||||||
|
fs.mkdirSync(themesPath);
|
||||||
|
|
||||||
|
// create trash
|
||||||
|
fs.writeFileSync(join(themesPath, 'casper.zip'));
|
||||||
|
fs.writeFileSync(join(themesPath, '.DS_Store'));
|
||||||
|
|
||||||
|
// create actual theme
|
||||||
|
fs.mkdirSync(join(themesPath, 'casper'));
|
||||||
|
fs.mkdirSync(join(themesPath, 'casper', 'partials'));
|
||||||
|
fs.writeFileSync(join(themesPath, 'casper', 'index.hbs'));
|
||||||
|
fs.writeFileSync(join(themesPath, 'casper', 'partials', 'navigation.hbs'));
|
||||||
|
|
||||||
|
readThemes(themesPath)
|
||||||
|
.then(function (tree) {
|
||||||
|
tree.should.eql({
|
||||||
|
casper: {
|
||||||
|
partials: {
|
||||||
|
'navigation.hbs': join(themesPath, 'casper', 'partials', 'navigation.hbs')
|
||||||
|
},
|
||||||
|
'index.hbs': join(themesPath, 'casper', 'index.hbs')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validate-themes', function () {
|
||||||
|
it('should return warnings for themes without package.json', function (done) {
|
||||||
|
var themesPath, pkgJson;
|
||||||
|
|
||||||
|
themesPath = tempfile();
|
||||||
|
pkgJson = JSON.stringify({
|
||||||
|
name: 'casper',
|
||||||
|
version: '1.0.0'
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.mkdirSync(themesPath);
|
||||||
|
|
||||||
|
fs.mkdirSync(join(themesPath, 'casper'));
|
||||||
|
fs.mkdirSync(join(themesPath, 'invalid-casper'));
|
||||||
|
|
||||||
|
fs.writeFileSync(join(themesPath, 'casper', 'package.json'), pkgJson);
|
||||||
|
|
||||||
|
validateThemes(themesPath)
|
||||||
|
.then(function () {
|
||||||
|
done(new Error('validateThemes succeeded, but should\'ve failed'));
|
||||||
|
})
|
||||||
|
.catch(function (result) {
|
||||||
|
result.errors.length.should.equal(0);
|
||||||
|
result.warnings.should.eql([{
|
||||||
|
message: 'Found a theme with no package.json file',
|
||||||
|
context: 'Theme name: invalid-casper',
|
||||||
|
help: 'This will be required in future. Please see http://docs.ghost.org/themes/'
|
||||||
|
}]);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
23
core/test/utils/tempfile.js
Normal file
23
core/test/utils/tempfile.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
|
||||||
|
var join = require('path').join,
|
||||||
|
|
||||||
|
TMP_DIR = require('os').tmpdir();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a temporary file path
|
||||||
|
*/
|
||||||
|
|
||||||
|
function tempfile() {
|
||||||
|
var randomString = Math.random().toString(36).substring(7);
|
||||||
|
|
||||||
|
return join(TMP_DIR, randomString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose `tempfile`
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = tempfile;
|
Loading…
Reference in New Issue
Block a user