From 696fbaaee45df610b81a277096e8d9a3c9eb7815 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Wed, 1 Oct 2014 23:19:46 +0000 Subject: [PATCH] Follow symlinks when resolving theme paths. Closes #4225 - If a theme is symlinked in the themes directory, follow the symlink so that the theme object is populated correctly. - Only do the fallback loading of theme data in the validations module if it doesn't exist in config. --- core/server/data/validation/index.js | 12 +++++++----- core/server/models/index.js | 2 +- core/server/require-tree.js | 20 +++++++++++++++++--- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/core/server/data/validation/index.js b/core/server/data/validation/index.js index b3c239de55..dd18ddc1b1 100644 --- a/core/server/data/validation/index.js +++ b/core/server/data/validation/index.js @@ -98,11 +98,6 @@ validateSettings = function (defaultSettings, model) { return Promise.resolve(); }; -// 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 -// is running and at times the validations are used when it's not (e.g. tests) -availableThemes = requireTree(config.paths.themePath); - validateActiveTheme = function (themeName) { // If Ghost is running and its availableThemes collection exists // give it priority. @@ -110,6 +105,13 @@ validateActiveTheme = function (themeName) { availableThemes = Promise.resolve(config.paths.availableThemes); } + if (!availableThemes) { + // 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 + // is running and at times the validations are used when it's not (e.g. tests) + availableThemes = requireTree(config.paths.themePath); + } + return availableThemes.then(function (themes) { if (!themes.hasOwnProperty(themeName)) { return Promise.reject(new errors.ValidationError(themeName + ' cannot be activated because it is not currently installed.', 'activeTheme')); diff --git a/core/server/models/index.js b/core/server/models/index.js index 757154f809..a2b3744c60 100644 --- a/core/server/models/index.js +++ b/core/server/models/index.js @@ -19,7 +19,7 @@ models = { self.Base = require('./base'); // Require all files in this directory - return requireTree.readAll(__dirname).then(function (modelFiles) { + return requireTree.readAll(__dirname, {followSymlinks: false}).then(function (modelFiles) { // For each found file, excluding those we don't want, // we will require it and cache it here. _.each(modelFiles, function (path, fileName) { diff --git a/core/server/require-tree.js b/core/server/require-tree.js index ede13b25b6..ad341fc2cf 100644 --- a/core/server/require-tree.js +++ b/core/server/require-tree.js @@ -2,8 +2,9 @@ var _ = require('lodash'), fs = require('fs'), path = require('path'), Promise = require('bluebird'), - readdirAsync = Promise.promisify(fs.readdir), - lstatAsync = Promise.promisify(fs.lstat), + readdirAsync = Promise.promisify(fs.readdir), + lstatAsync = Promise.promisify(fs.lstat), + readlinkAsync = Promise.promisify(fs.readlink), parsePackageJson = function (path, messages) { // Default the messages if non were passed @@ -56,7 +57,8 @@ var _ = require('lodash'), }; options = _.extend({ - index: true + index: true, + followSymlinks: true }, options); if (depth > 1) { @@ -72,6 +74,18 @@ var _ = require('lodash'), return lstatAsync(fpath).then(function (result) { if (result.isDirectory()) { return readDir(fpath, options, depth + 1, messages); + } else if (options.followSymlinks && result.isSymbolicLink()) { + return readlinkAsync(fpath).then(function (linkPath) { + linkPath = path.resolve(dir, linkPath); + + return lstatAsync(linkPath).then(function (result) { + if (result.isFile()) { + return linkPath; + } + + return readDir(linkPath, options, depth + 1, messages); + }); + }); } else if (depth === 1 && file === 'package.json') { return parsePackageJson(fpath, messages); } else {