refactor(scripts): split specs and checking into separate files
parent
b6bd53a1a7
commit
80c235822d
|
@ -0,0 +1,84 @@
|
||||||
|
const yaml = require('js-yaml');
|
||||||
|
const Type = require('js-yaml/lib/js-yaml/type');
|
||||||
|
const Schema = require('js-yaml/lib/js-yaml/schema');
|
||||||
|
|
||||||
|
const { is, descriptors } = require('./utils');
|
||||||
|
const { doc, type, requires, defaultValue } = descriptors;
|
||||||
|
|
||||||
|
const UNDEFINED = Symbol('undefined');
|
||||||
|
// output null as empty in yaml
|
||||||
|
const YAML_SCHEMA = new Schema({
|
||||||
|
include: [
|
||||||
|
require('js-yaml/lib/js-yaml/schema/default_full')
|
||||||
|
],
|
||||||
|
implicit: [
|
||||||
|
new Type('tag:yaml.org,2002:null', {
|
||||||
|
kind: 'scalar',
|
||||||
|
resolve(data) {
|
||||||
|
if (data === null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const max = data.length;
|
||||||
|
return (max === 1 && data === '~') ||
|
||||||
|
(max === 4 && (data === 'null' || data === 'Null' || data === 'NULL'));
|
||||||
|
},
|
||||||
|
construct: () => null,
|
||||||
|
predicate: object => object === null,
|
||||||
|
represent: {
|
||||||
|
empty: function () { return ''; }
|
||||||
|
},
|
||||||
|
defaultStyle: 'empty'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
function generate(spec, parentConfig = null) {
|
||||||
|
if (!is.spec(spec)) {
|
||||||
|
return UNDEFINED;
|
||||||
|
}
|
||||||
|
if (spec.hasOwnProperty(requires) && !spec[requires](parentConfig)) {
|
||||||
|
return UNDEFINED;
|
||||||
|
}
|
||||||
|
if (spec.hasOwnProperty(defaultValue)) {
|
||||||
|
return spec[defaultValue];
|
||||||
|
}
|
||||||
|
const types = is.array(spec[type]) ? spec[type] : [spec[type]];
|
||||||
|
if (types.includes('object')) {
|
||||||
|
let defaults = UNDEFINED;
|
||||||
|
for (let key in spec) {
|
||||||
|
if (key === '*') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const value = generate(spec[key], defaults);
|
||||||
|
if (value !== UNDEFINED) {
|
||||||
|
if (defaults === UNDEFINED) {
|
||||||
|
defaults = {};
|
||||||
|
}
|
||||||
|
if (spec[key].hasOwnProperty(doc)) {
|
||||||
|
defaults['#' + key] = spec[key][doc];
|
||||||
|
}
|
||||||
|
defaults[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaults;
|
||||||
|
} else if (types.includes('array') && spec.hasOwnProperty('*')) {
|
||||||
|
return [generate(spec['*'], {})];
|
||||||
|
}
|
||||||
|
return UNDEFINED;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConfigGenerator {
|
||||||
|
constructor(spec) {
|
||||||
|
this.spec = spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
generate() {
|
||||||
|
return yaml.safeDump(generate(this.spec), {
|
||||||
|
indent: 4,
|
||||||
|
lineWidth: 1024,
|
||||||
|
schema: YAML_SCHEMA
|
||||||
|
}).replace(/^(\s*)\'#.*?\':\s*\'*(.*?)\'*$/mg, '$1# $2'); // make comment lines
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ConfigGenerator;
|
|
@ -0,0 +1,132 @@
|
||||||
|
const { is, descriptors } = require('./utils');
|
||||||
|
const { type, required, requires, format, defaultValue } = descriptors;
|
||||||
|
const {
|
||||||
|
InvalidSpecError,
|
||||||
|
MissingRequiredError,
|
||||||
|
TypeMismatchError,
|
||||||
|
FormatMismatchError,
|
||||||
|
VersionMalformedError,
|
||||||
|
VersionNotFoundError,
|
||||||
|
VersionMismatchError } = require('./utils').errors;
|
||||||
|
|
||||||
|
function isRequiresSatisfied(spec, config) {
|
||||||
|
try {
|
||||||
|
if (!spec.hasOwnProperty(requires) || spec[requires](config) === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) { }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConfigType(spec, config) {
|
||||||
|
const specTypes = is.array(spec[type]) ? spec[type] : [spec[type]];
|
||||||
|
for (let specType of specTypes) {
|
||||||
|
if (is[specType](config)) {
|
||||||
|
return specType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasFormat(spec, config) {
|
||||||
|
if (!spec.hasOwnProperty(format)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return spec[format].test(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate(spec, config, parentConfig, path) {
|
||||||
|
if (!is.spec(spec)) {
|
||||||
|
throw new InvalidSpecError(spec, path);
|
||||||
|
}
|
||||||
|
if (!isRequiresSatisfied(spec, parentConfig)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (is.undefined(config) || is.null(config)) {
|
||||||
|
if (spec[required] === true) {
|
||||||
|
throw new MissingRequiredError(spec, path);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const type = getConfigType(spec, config);
|
||||||
|
if (type === null) {
|
||||||
|
throw new TypeMismatchError(spec, path, config);
|
||||||
|
}
|
||||||
|
if (type === 'string') {
|
||||||
|
if (!hasFormat(spec, config)) {
|
||||||
|
throw new FormatMismatchError(spec, path, config);
|
||||||
|
}
|
||||||
|
} else if (type === 'array' && spec.hasOwnProperty('*')) {
|
||||||
|
config.forEach((child, i) => validate(spec['*'], child, config, path.concat(`[${i}]`)));
|
||||||
|
} else if (type === 'object') {
|
||||||
|
for (let key in spec) {
|
||||||
|
if (key === '*') {
|
||||||
|
Object.keys(config).forEach(k => validate(spec['*'], config[k], config, path.concat(k)));
|
||||||
|
} else {
|
||||||
|
validate(spec[key], config[key], config, path.concat(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatVersion(ver) {
|
||||||
|
const m = /^(\d)+\.(\d)+\.(\d)+(?:-([0-9A-Za-z-]+))*$/.exec(ver);
|
||||||
|
if (m === null) {
|
||||||
|
throw new VersionMalformedError(ver);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
major: m[1],
|
||||||
|
minor: m[2],
|
||||||
|
patch: m[3],
|
||||||
|
identifier: m.length > 4 ? m[4] : null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareVersion(ver1, ver2) {
|
||||||
|
for (let section of ['major', 'minor', 'patch']) {
|
||||||
|
if (ver1[section] !== ver2[section]) {
|
||||||
|
return Math.sign(ver1[section] - ver2[section]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const id1 = ver1.hasOwnProperty('identifier') ? ver1.identifier : null;
|
||||||
|
const id2 = ver2.hasOwnProperty('identifier') ? ver2.identifier : null;
|
||||||
|
if (id1 === id2) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (id1 === null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (id2 === null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return id1.localeCompare(id2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBreakingChange(base, ver) {
|
||||||
|
return base.major !== ver.major;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function checkVersion(spec, config) {
|
||||||
|
if (!config.hasOwnProperty('version')) {
|
||||||
|
throw new VersionNotFoundError();
|
||||||
|
}
|
||||||
|
const configVersion = formatVersion(config.version);
|
||||||
|
const specVersion = formatVersion(spec.version[defaultValue]);
|
||||||
|
if (isBreakingChange(specVersion, configVersion)) {
|
||||||
|
throw new VersionMismatchError(spec.version[defaultValue], config.version, compareVersion(specVersion, configVersion) > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConfigValidator {
|
||||||
|
constructor(spec) {
|
||||||
|
this.spec = spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
validate(config) {
|
||||||
|
checkVersion(this.spec, config);
|
||||||
|
validate(this.spec, config, null, []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ConfigValidator;
|
|
@ -0,0 +1,148 @@
|
||||||
|
const doc = Symbol('@doc');
|
||||||
|
const type = Symbol('@type');
|
||||||
|
const format = Symbol('@format');
|
||||||
|
const required = Symbol('@required');
|
||||||
|
const requires = Symbol('@requires');
|
||||||
|
const defaultValue = Symbol('@default');
|
||||||
|
|
||||||
|
const descriptors = {
|
||||||
|
doc,
|
||||||
|
type,
|
||||||
|
format,
|
||||||
|
requires,
|
||||||
|
required,
|
||||||
|
defaultValue
|
||||||
|
};
|
||||||
|
|
||||||
|
const is = (() => ({
|
||||||
|
string(value) {
|
||||||
|
return typeof (value) === 'string';
|
||||||
|
},
|
||||||
|
array(value) {
|
||||||
|
return Array.isArray(value);
|
||||||
|
},
|
||||||
|
boolean(value) {
|
||||||
|
return typeof (value) === 'boolean';
|
||||||
|
},
|
||||||
|
object(value) {
|
||||||
|
return typeof (value) === 'object' && value.constructor == Object;
|
||||||
|
},
|
||||||
|
function(value) {
|
||||||
|
return typeof (value) === 'function';
|
||||||
|
},
|
||||||
|
regexp(value) {
|
||||||
|
return value instanceof RegExp;
|
||||||
|
},
|
||||||
|
undefined(value) {
|
||||||
|
return typeof (value) === 'undefined';
|
||||||
|
},
|
||||||
|
null(value) {
|
||||||
|
return value === null;
|
||||||
|
},
|
||||||
|
spec(value) {
|
||||||
|
if (!value.hasOwnProperty(type)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!is.string(value[type]) && !is.array(value[type])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value.hasOwnProperty(doc) && !is.string(value[doc])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value.hasOwnProperty(required) && !is.boolean(value[required])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value.hasOwnProperty(requires) && !is.function(value[requires])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value.hasOwnProperty(format) && !is.regexp(value[format])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}))();
|
||||||
|
|
||||||
|
class ConfigError extends Error {
|
||||||
|
constructor(spec, path) {
|
||||||
|
super();
|
||||||
|
this.spec = spec;
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InvalidSpecError extends ConfigError {
|
||||||
|
constructor(spec, path) {
|
||||||
|
super(spec, path);
|
||||||
|
this.message = `The specification '${path.join('.')}' is invalid.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MissingRequiredError extends ConfigError {
|
||||||
|
constructor(spec, path) {
|
||||||
|
super(spec, path);
|
||||||
|
this.message = `Configuration file do not have the required '${path.join('.')}' field.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TypeMismatchError extends ConfigError {
|
||||||
|
constructor(spec, path, config) {
|
||||||
|
super(spec, path);
|
||||||
|
this.config = config;
|
||||||
|
this.message = `Configuration '${path.join('.')}' is not one of the '${spec[type]}' type.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FormatMismatchError extends ConfigError {
|
||||||
|
constructor(spec, path, config) {
|
||||||
|
super(spec, path);
|
||||||
|
this.config = config;
|
||||||
|
this.message = `Configuration '${path.join('.')}' do not match the format '${spec[format]}'.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VersionError extends Error {
|
||||||
|
}
|
||||||
|
|
||||||
|
class VersionNotFoundError extends VersionError {
|
||||||
|
constructor() {
|
||||||
|
super(`Version number is not found in the configuration file.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VersionMalformedError extends VersionError {
|
||||||
|
constructor(version) {
|
||||||
|
super(`Version number ${version} is malformed.`);
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VersionMismatchError extends VersionError {
|
||||||
|
constructor(specVersion, configVersion, isConfigVersionSmaller) {
|
||||||
|
super();
|
||||||
|
this.specVersion = specVersion;
|
||||||
|
this.configVersion = configVersion;
|
||||||
|
if (isConfigVersionSmaller) {
|
||||||
|
this.message = `The configuration version ${configVersion} is far behind the specification version ${specVersion}.`;
|
||||||
|
} else {
|
||||||
|
this.message = `The configuration version ${configVersion} is way ahead of the specification version ${specVersion}.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const errors = {
|
||||||
|
ConfigError,
|
||||||
|
InvalidSpecError,
|
||||||
|
MissingRequiredError,
|
||||||
|
TypeMismatchError,
|
||||||
|
FormatMismatchError,
|
||||||
|
VersionError,
|
||||||
|
VersionMalformedError,
|
||||||
|
VersionNotFoundError,
|
||||||
|
VersionMismatchError
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
is,
|
||||||
|
descriptors,
|
||||||
|
errors
|
||||||
|
};
|
|
@ -7,8 +7,8 @@
|
||||||
* <%- has_config(config_name, exclude_page) %>
|
* <%- has_config(config_name, exclude_page) %>
|
||||||
* <%- get_config(config_name, default_value, exclude_page) %>
|
* <%- get_config(config_name, default_value, exclude_page) %>
|
||||||
*/
|
*/
|
||||||
const specs = require('../specs/_config.yml');
|
const specs = require('../specs/config.spec');
|
||||||
const descriptors = require('../specs/common').descriptor;
|
const descriptors = require('../common/utils').descriptors;
|
||||||
|
|
||||||
module.exports = function (hexo) {
|
module.exports = function (hexo) {
|
||||||
function readProperty(object, path) {
|
function readProperty(object, path) {
|
||||||
|
|
|
@ -1,473 +0,0 @@
|
||||||
const { version } = require('../../package.json');
|
|
||||||
const { type, required, defaultValue, description, condition } = require('./common').descriptor;
|
|
||||||
const desc = description;
|
|
||||||
|
|
||||||
const IconLink = {
|
|
||||||
[type]: 'object',
|
|
||||||
[desc]: 'Link icon settings',
|
|
||||||
'*': {
|
|
||||||
[type]: ['string', 'object'],
|
|
||||||
[desc]: 'Path or URL to the menu item, and/or link icon class names',
|
|
||||||
icon: {
|
|
||||||
[required]: true,
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Link icon class names'
|
|
||||||
},
|
|
||||||
url: {
|
|
||||||
[required]: true,
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Path or URL to the menu item'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEFAULT_WIDGETS = [
|
|
||||||
{
|
|
||||||
type: 'profile',
|
|
||||||
position: 'left',
|
|
||||||
author: 'Your name',
|
|
||||||
author_title: 'Your title',
|
|
||||||
location: 'Your location',
|
|
||||||
avatar: null,
|
|
||||||
gravatar: null,
|
|
||||||
follow_link: 'http://github.com/ppoffice',
|
|
||||||
social_links: {
|
|
||||||
Github: {
|
|
||||||
icon: 'fab fa-github',
|
|
||||||
url: 'http://github.com/ppoffice'
|
|
||||||
},
|
|
||||||
Facebook: {
|
|
||||||
icon: 'fab fa-facebook',
|
|
||||||
url: 'http://facebook.com'
|
|
||||||
},
|
|
||||||
Twitter: {
|
|
||||||
icon: 'fab fa-twitter',
|
|
||||||
url: 'http://twitter.com'
|
|
||||||
},
|
|
||||||
Dribbble: {
|
|
||||||
icon: 'fab fa-dribbble',
|
|
||||||
url: 'http://dribbble.com'
|
|
||||||
},
|
|
||||||
RSS: {
|
|
||||||
icon: 'fas fa-rss',
|
|
||||||
url: '/'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'toc',
|
|
||||||
position: 'left'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'links',
|
|
||||||
position: 'left',
|
|
||||||
links: {
|
|
||||||
Hexo: 'https://hexo.io',
|
|
||||||
Github: 'https://github.com/ppoffice'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'category',
|
|
||||||
position: 'left'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'tagcloud',
|
|
||||||
position: 'left'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'recent_posts',
|
|
||||||
position: 'right'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'archive',
|
|
||||||
position: 'right'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'tag',
|
|
||||||
position: 'right'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
[type]: 'object',
|
|
||||||
[desc]: 'Root of the configuration file',
|
|
||||||
[required]: true,
|
|
||||||
version: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Version of the Icarus theme that is currently used',
|
|
||||||
[required]: true,
|
|
||||||
[defaultValue]: version
|
|
||||||
},
|
|
||||||
favicon: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Path or URL to the website\'s icon',
|
|
||||||
[defaultValue]: null
|
|
||||||
},
|
|
||||||
open_graph: {
|
|
||||||
[type]: 'object',
|
|
||||||
[desc]: 'Open Graph metadata (https://hexo.io/docs/helpers.html#open-graph)',
|
|
||||||
fb_app_id: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Facebook App ID',
|
|
||||||
[defaultValue]: null
|
|
||||||
},
|
|
||||||
fb_admins: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Facebook Admin ID',
|
|
||||||
[defaultValue]: null
|
|
||||||
},
|
|
||||||
twitter_id: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Twitter ID',
|
|
||||||
[defaultValue]: null
|
|
||||||
},
|
|
||||||
twitter_site: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Twitter site',
|
|
||||||
[defaultValue]: null
|
|
||||||
},
|
|
||||||
google_plus: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Google+ profile link',
|
|
||||||
[defaultValue]: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rss: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Path or URL to RSS atom.xml',
|
|
||||||
[defaultValue]: null
|
|
||||||
},
|
|
||||||
logo: {
|
|
||||||
[type]: ['string', 'object'],
|
|
||||||
[defaultValue]: '/images/logo.svg',
|
|
||||||
[desc]: 'Path or URL to the website\'s logo to be shown on the left of the navigation bar or footer',
|
|
||||||
text: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Text to be shown in place of the logo image'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
navbar: {
|
|
||||||
[type]: 'object',
|
|
||||||
[desc]: 'Navigation bar link settings',
|
|
||||||
menu: {
|
|
||||||
[type]: 'object',
|
|
||||||
[desc]: 'Navigation bar menu links',
|
|
||||||
[defaultValue]: {
|
|
||||||
Home: '/',
|
|
||||||
Archives: '/archives',
|
|
||||||
Categories: '/categories',
|
|
||||||
Tags: '/tags',
|
|
||||||
About: '/about'
|
|
||||||
},
|
|
||||||
'*': {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Path or URL to the menu item'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
links: {
|
|
||||||
...IconLink,
|
|
||||||
[desc]: 'Navigation bar links to be shown on the right',
|
|
||||||
[defaultValue]: {
|
|
||||||
'Download on GitHub': {
|
|
||||||
icon: 'fab fa-github',
|
|
||||||
url: 'http://github.com/ppoffice/hexo-theme-icarus'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
[type]: 'object',
|
|
||||||
[description]: 'Footer section link settings',
|
|
||||||
links: {
|
|
||||||
...IconLink,
|
|
||||||
[desc]: 'Links to be shown on the right of the footer section',
|
|
||||||
[defaultValue]: {
|
|
||||||
'Creative Commons': {
|
|
||||||
icon: 'fab fa-creative-commons',
|
|
||||||
url: 'https://creativecommons.org/'
|
|
||||||
},
|
|
||||||
'Attribution 4.0 International': {
|
|
||||||
icon: 'fab fa-creative-commons-by',
|
|
||||||
url: 'https://creativecommons.org/licenses/by/4.0/'
|
|
||||||
},
|
|
||||||
'Download on GitHub': {
|
|
||||||
icon: 'fab fa-github',
|
|
||||||
url: 'http://github.com/ppoffice/hexo-theme-icarus'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
article: {
|
|
||||||
[type]: 'object',
|
|
||||||
[desc]: 'Article display settings',
|
|
||||||
highlight: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Code highlight theme (https://github.com/highlightjs/highlight.js/tree/master/src/styles)',
|
|
||||||
[defaultValue]: 'atom-one-light'
|
|
||||||
},
|
|
||||||
thumbnail: {
|
|
||||||
[type]: 'boolean',
|
|
||||||
[desc]: 'Whether to show article thumbnail images',
|
|
||||||
[defaultValue]: true
|
|
||||||
},
|
|
||||||
readtime: {
|
|
||||||
[type]: 'boolean',
|
|
||||||
[desc]: 'Whether to show estimate article reading time',
|
|
||||||
[defaultValue]: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
search: {
|
|
||||||
[type]: 'object',
|
|
||||||
[desc]: 'Search plugin settings (http://ppoffice.github.io/hexo-theme-icarus/categories/Configuration/Search-Plugins)',
|
|
||||||
type: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Name of the search plugin',
|
|
||||||
[defaultValue]: 'insight'
|
|
||||||
},
|
|
||||||
cx: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Google CSE cx value',
|
|
||||||
[required]: true,
|
|
||||||
[condition]: parent => parent.type === 'google-cse'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
comment: {
|
|
||||||
[type]: 'object',
|
|
||||||
[desc]: 'Comment plugin settings (http://ppoffice.github.io/hexo-theme-icarus/categories/Configuration/Comment-Plugins)',
|
|
||||||
type: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Name of the comment plugin',
|
|
||||||
[defaultValue]: null
|
|
||||||
},
|
|
||||||
appid: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Changyan comment app ID',
|
|
||||||
[required]: true,
|
|
||||||
[condition]: parent => parent.type === 'changyan'
|
|
||||||
},
|
|
||||||
conf: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Changyan comment configuration ID',
|
|
||||||
[required]: true,
|
|
||||||
[condition]: parent => parent.type === 'changyan'
|
|
||||||
},
|
|
||||||
shortname: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Disqus shortname',
|
|
||||||
[required]: true,
|
|
||||||
[condition]: parent => parent.type === 'disqus'
|
|
||||||
},
|
|
||||||
owner: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Your GitHub ID',
|
|
||||||
[required]: true,
|
|
||||||
[condition]: parent => parent.type === 'gitment'
|
|
||||||
},
|
|
||||||
repo: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'The repo to store comments',
|
|
||||||
[required]: true,
|
|
||||||
[condition]: parent => parent.type === 'gitment'
|
|
||||||
},
|
|
||||||
client_id: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Your client ID',
|
|
||||||
[required]: true,
|
|
||||||
[condition]: parent => parent.type === 'gitment'
|
|
||||||
},
|
|
||||||
client_secret: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Your client secret',
|
|
||||||
[required]: true,
|
|
||||||
[condition]: parent => parent.type === 'gitment'
|
|
||||||
},
|
|
||||||
url: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'URL to your Isso comment service',
|
|
||||||
[required]: true,
|
|
||||||
[condition]: parent => parent.type === 'isso'
|
|
||||||
},
|
|
||||||
uid: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'LiveRe comment service UID',
|
|
||||||
[required]: true,
|
|
||||||
[condition]: parent => parent.type === 'livere'
|
|
||||||
},
|
|
||||||
app_id: {
|
|
||||||
[type]: 'boolean',
|
|
||||||
[desc]: 'LeanCloud APP ID',
|
|
||||||
[required]: true,
|
|
||||||
[condition]: parent => parent.type === 'valine'
|
|
||||||
},
|
|
||||||
app_key: {
|
|
||||||
[type]: 'boolean',
|
|
||||||
[desc]: 'LeanCloud APP key',
|
|
||||||
[required]: true,
|
|
||||||
[condition]: parent => parent.type === 'valine'
|
|
||||||
},
|
|
||||||
notify: {
|
|
||||||
[type]: 'boolean',
|
|
||||||
[desc]: 'Enable email notification when someone comments',
|
|
||||||
[defaultValue]: false,
|
|
||||||
[condition]: parent => parent.type === 'valine'
|
|
||||||
},
|
|
||||||
verify: {
|
|
||||||
[type]: 'boolean',
|
|
||||||
[desc]: 'Enable verification code service',
|
|
||||||
[defaultValue]: false,
|
|
||||||
[condition]: parent => parent.type === 'valine'
|
|
||||||
},
|
|
||||||
placeholder: {
|
|
||||||
[type]: 'boolean',
|
|
||||||
[desc]: 'Placeholder text in the comment box',
|
|
||||||
[defaultValue]: 'Say something...',
|
|
||||||
[condition]: parent => parent.type === 'valine'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
share: {
|
|
||||||
[type]: 'object',
|
|
||||||
[desc]: 'Share plugin settings (http://ppoffice.github.io/hexo-theme-icarus/categories/Configuration/Share-Plugins)',
|
|
||||||
type: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Share plugin name',
|
|
||||||
[defaultValue]: null
|
|
||||||
},
|
|
||||||
install_url: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'URL to the share plugin script provided by share plugin service provider',
|
|
||||||
[required]: true,
|
|
||||||
[condition]: parent => parent.type === 'sharethis' || parent.type === 'addthis'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
widgets: {
|
|
||||||
[type]: 'array',
|
|
||||||
[desc]: 'Sidebar widget settings',
|
|
||||||
[defaultValue]: DEFAULT_WIDGETS,
|
|
||||||
'*': {
|
|
||||||
[type]: 'object',
|
|
||||||
[desc]: 'Single widget settings',
|
|
||||||
type: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Widget name',
|
|
||||||
[required]: true,
|
|
||||||
[defaultValue]: 'profile'
|
|
||||||
},
|
|
||||||
position: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Where should the widget be placed, left or right',
|
|
||||||
[required]: true,
|
|
||||||
[defaultValue]: 'left'
|
|
||||||
},
|
|
||||||
author: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Author name to be shown in the profile widget',
|
|
||||||
[condition]: parent => parent.type === 'profile',
|
|
||||||
[defaultValue]: 'Your name'
|
|
||||||
},
|
|
||||||
author_title: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Title of the author to be shown in the profile widget',
|
|
||||||
[condition]: parent => parent.type === 'profile',
|
|
||||||
[defaultValue]: 'Your title'
|
|
||||||
},
|
|
||||||
location: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Author\'s current location to be shown in the profile widget',
|
|
||||||
[condition]: parent => parent.type === 'profile',
|
|
||||||
[defaultValue]: 'Your location'
|
|
||||||
},
|
|
||||||
avatar: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Path or URL to the avatar to be shown in the profile widget',
|
|
||||||
[condition]: parent => parent.type === 'profile',
|
|
||||||
[defaultValue]: '/images/avatar.png'
|
|
||||||
},
|
|
||||||
gravatar: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Email address for the Gravatar to be shown in the profile widget',
|
|
||||||
[condition]: parent => parent.type === 'profile'
|
|
||||||
},
|
|
||||||
follow_link: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Path or URL for the follow button',
|
|
||||||
[condition]: parent => parent.type === 'profile'
|
|
||||||
},
|
|
||||||
social_links: {
|
|
||||||
...IconLink,
|
|
||||||
[desc]: 'Links to be shown on the bottom of the profile widget',
|
|
||||||
[condition]: parent => parent.type === 'profile'
|
|
||||||
},
|
|
||||||
links: {
|
|
||||||
[type]: 'object',
|
|
||||||
[desc]: 'Links to be shown in the links widget',
|
|
||||||
[condition]: parent => parent.type === 'links',
|
|
||||||
'*': {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Path or URL to the link',
|
|
||||||
[required]: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
[type]: 'object',
|
|
||||||
[desc]: 'Other plugin settings',
|
|
||||||
gallery: {
|
|
||||||
[type]: 'boolean',
|
|
||||||
[desc]: 'Enable the lightGallery and Justified Gallery plugins',
|
|
||||||
[defaultValue]: true
|
|
||||||
},
|
|
||||||
'outdated-browser': {
|
|
||||||
[type]: 'boolean',
|
|
||||||
[desc]: 'Enable the Outdated Browser plugin',
|
|
||||||
[defaultValue]: true
|
|
||||||
},
|
|
||||||
animejs: {
|
|
||||||
[type]: 'boolean',
|
|
||||||
[desc]: 'Enable page animations',
|
|
||||||
[defaultValue]: true
|
|
||||||
},
|
|
||||||
mathjax: {
|
|
||||||
[type]: 'boolean',
|
|
||||||
[desc]: 'Enable the MathJax plugin',
|
|
||||||
[defaultValue]: true
|
|
||||||
},
|
|
||||||
'google-analytics': {
|
|
||||||
[type]: ['boolean', 'object'],
|
|
||||||
[desc]: 'Google Analytics plugin settings (http://ppoffice.github.io/hexo-theme-icarus/2018/01/01/plugin/Analytics/#Google-Analytics)',
|
|
||||||
tracking_id: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Google Analytics tracking id',
|
|
||||||
[defaultValue]: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'baidu-analytics': {
|
|
||||||
[type]: ['boolean', 'object'],
|
|
||||||
[desc]: 'Baidu Analytics plugin settings (http://ppoffice.github.io/hexo-theme-icarus/2018/01/01/plugin/Analytics/#Baidu-Analytics)',
|
|
||||||
tracking_id: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Baidu Analytics tracking id',
|
|
||||||
[defaultValue]: null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
providers: {
|
|
||||||
[type]: 'object',
|
|
||||||
[desc]: 'CDN provider settings',
|
|
||||||
cdn: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Name or URL of the JavaScript and/or stylesheet CDN provider',
|
|
||||||
[defaultValue]: 'cdnjs'
|
|
||||||
},
|
|
||||||
fontcdn: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Name or URL of the webfont CDN provider',
|
|
||||||
[defaultValue]: 'google'
|
|
||||||
},
|
|
||||||
iconcdn: {
|
|
||||||
[type]: 'string',
|
|
||||||
[desc]: 'Name or URL of the webfont Icon CDN provider',
|
|
||||||
[defaultValue]: 'fontawesome'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
const { doc, type, defaultValue } = require('../common/utils').descriptors;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
[type]: 'object',
|
||||||
|
[doc]: 'Article display settings',
|
||||||
|
highlight: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Code highlight theme (https://github.com/highlightjs/highlight.js/tree/master/src/styles)',
|
||||||
|
[defaultValue]: 'atom-one-light'
|
||||||
|
},
|
||||||
|
thumbnail: {
|
||||||
|
[type]: 'boolean',
|
||||||
|
[doc]: 'Whether to show article thumbnail images',
|
||||||
|
[defaultValue]: true
|
||||||
|
},
|
||||||
|
readtime: {
|
||||||
|
[type]: 'boolean',
|
||||||
|
[doc]: 'Whether to show estimate article reading time',
|
||||||
|
[defaultValue]: true
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,119 @@
|
||||||
|
const { doc, type, defaultValue, required, requires } = require('../common/utils').descriptors;
|
||||||
|
|
||||||
|
const ChangYanSpec = {
|
||||||
|
appid: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Changyan comment app ID',
|
||||||
|
[required]: true,
|
||||||
|
[requires]: comment => comment.type === 'changyan'
|
||||||
|
},
|
||||||
|
conf: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Changyan comment configuration ID',
|
||||||
|
[required]: true,
|
||||||
|
[requires]: comment => comment.type === 'changyan'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const DisqusSpec = {
|
||||||
|
shortname: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Disqus shortname',
|
||||||
|
[required]: true,
|
||||||
|
[requires]: comment => comment.type === 'disqus'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const GitmentSpec = {
|
||||||
|
owner: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Your GitHub ID',
|
||||||
|
[required]: true,
|
||||||
|
[requires]: comment => comment.type === 'gitment'
|
||||||
|
},
|
||||||
|
repo: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'The repo to store comments',
|
||||||
|
[required]: true,
|
||||||
|
[requires]: comment => comment.type === 'gitment'
|
||||||
|
},
|
||||||
|
client_id: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Your client ID',
|
||||||
|
[required]: true,
|
||||||
|
[requires]: comment => comment.type === 'gitment'
|
||||||
|
},
|
||||||
|
client_secret: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Your client secret',
|
||||||
|
[required]: true,
|
||||||
|
[requires]: comment => comment.type === 'gitment'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const IssoSpec = {
|
||||||
|
url: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'URL to your Isso comment service',
|
||||||
|
[required]: true,
|
||||||
|
[requires]: comment => comment.type === 'isso'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const LiveReSpec = {
|
||||||
|
uid: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'LiveRe comment service UID',
|
||||||
|
[required]: true,
|
||||||
|
[requires]: comment => comment.type === 'livere'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ValineSpec = {
|
||||||
|
app_id: {
|
||||||
|
[type]: 'boolean',
|
||||||
|
[doc]: 'LeanCloud APP ID',
|
||||||
|
[required]: true,
|
||||||
|
[requires]: comment => comment.type === 'valine'
|
||||||
|
},
|
||||||
|
app_key: {
|
||||||
|
[type]: 'boolean',
|
||||||
|
[doc]: 'LeanCloud APP key',
|
||||||
|
[required]: true,
|
||||||
|
[requires]: comment => comment.type === 'valine'
|
||||||
|
},
|
||||||
|
notify: {
|
||||||
|
[type]: 'boolean',
|
||||||
|
[doc]: 'Enable email notification when someone comments',
|
||||||
|
[defaultValue]: false,
|
||||||
|
[requires]: comment => comment.type === 'valine'
|
||||||
|
},
|
||||||
|
verify: {
|
||||||
|
[type]: 'boolean',
|
||||||
|
[doc]: 'Enable verification code service',
|
||||||
|
[defaultValue]: false,
|
||||||
|
[requires]: comment => comment.type === 'valine'
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
[type]: 'boolean',
|
||||||
|
[doc]: 'Placeholder text in the comment box',
|
||||||
|
[defaultValue]: 'Say something...',
|
||||||
|
[requires]: comment => comment.type === 'valine'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
[type]: 'object',
|
||||||
|
[doc]: 'Comment plugin settings (http://ppoffice.github.io/hexo-theme-icarus/categories/Configuration/Comment-Plugins)',
|
||||||
|
type: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Name of the comment plugin',
|
||||||
|
[defaultValue]: null
|
||||||
|
},
|
||||||
|
...ChangYanSpec,
|
||||||
|
...DisqusSpec,
|
||||||
|
...GitmentSpec,
|
||||||
|
...IssoSpec,
|
||||||
|
...LiveReSpec,
|
||||||
|
...ValineSpec
|
||||||
|
}
|
|
@ -1,24 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
projectName: 'Icarus',
|
|
||||||
is: {
|
|
||||||
string(value) {
|
|
||||||
return typeof(value) === 'string';
|
|
||||||
},
|
|
||||||
array(value) {
|
|
||||||
return Array.isArray(value);
|
|
||||||
},
|
|
||||||
boolean(value) {
|
|
||||||
return typeof(value) === 'boolean';
|
|
||||||
},
|
|
||||||
object(value) {
|
|
||||||
return typeof(value) === 'object' && value.constructor == Object;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
descriptor: {
|
|
||||||
type: Symbol('@type'),
|
|
||||||
required: Symbol('@required'),
|
|
||||||
description: Symbol('@description'),
|
|
||||||
defaultValue: Symbol('@defaultValue'),
|
|
||||||
condition: Symbol('@condition'),
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
const { version } = require('../../package.json');
|
||||||
|
const { type, required, defaultValue, doc } = require('../common/utils').descriptors;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
[type]: 'object',
|
||||||
|
[doc]: 'Root of the configuration file',
|
||||||
|
[required]: true,
|
||||||
|
version: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Version of the Icarus theme that is currently used',
|
||||||
|
[required]: true,
|
||||||
|
[defaultValue]: version
|
||||||
|
},
|
||||||
|
...require('./meta.spec'),
|
||||||
|
navbar: require('./navbar.spec'),
|
||||||
|
footer: require('./footer.spec'),
|
||||||
|
article: require('./article.spec'),
|
||||||
|
search: require('./search.spec'),
|
||||||
|
comment: require('./comment.spec'),
|
||||||
|
share: require('./share.spec'),
|
||||||
|
widgets: require('./widgets.spec'),
|
||||||
|
plugins: require('./plugins.spec'),
|
||||||
|
providers: require('./providers.spec')
|
||||||
|
};
|
|
@ -0,0 +1,24 @@
|
||||||
|
const { doc, type, defaultValue } = require('../common/utils').descriptors;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
[type]: 'object',
|
||||||
|
[doc]: 'Footer section link settings',
|
||||||
|
links: {
|
||||||
|
...require('./icon_link.spec'),
|
||||||
|
[doc]: 'Links to be shown on the right of the footer section',
|
||||||
|
[defaultValue]: {
|
||||||
|
'Creative Commons': {
|
||||||
|
icon: 'fab fa-creative-commons',
|
||||||
|
url: 'https://creativecommons.org/'
|
||||||
|
},
|
||||||
|
'Attribution 4.0 International': {
|
||||||
|
icon: 'fab fa-creative-commons-by',
|
||||||
|
url: 'https://creativecommons.org/licenses/by/4.0/'
|
||||||
|
},
|
||||||
|
'Download on GitHub': {
|
||||||
|
icon: 'fab fa-github',
|
||||||
|
url: 'http://github.com/ppoffice/hexo-theme-icarus'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,20 @@
|
||||||
|
const { doc, type, required } = require('../common/utils').descriptors;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
[type]: 'object',
|
||||||
|
[doc]: 'Link icon settings',
|
||||||
|
'*': {
|
||||||
|
[type]: ['string', 'object'],
|
||||||
|
[doc]: 'Path or URL to the menu item, and/or link icon class names',
|
||||||
|
icon: {
|
||||||
|
[required]: true,
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Link icon class names'
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
[required]: true,
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Path or URL to the menu item'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,52 @@
|
||||||
|
const { doc, type, defaultValue } = require('../common/utils').descriptors;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
favicon: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Path or URL to the website\'s icon',
|
||||||
|
[defaultValue]: null
|
||||||
|
},
|
||||||
|
rss: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Path or URL to RSS atom.xml',
|
||||||
|
[defaultValue]: null
|
||||||
|
},
|
||||||
|
logo: {
|
||||||
|
[type]: ['string', 'object'],
|
||||||
|
[defaultValue]: '/images/logo.svg',
|
||||||
|
[doc]: 'Path or URL to the website\'s logo to be shown on the left of the navigation bar or footer',
|
||||||
|
text: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Text to be shown in place of the logo image'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
open_graph: {
|
||||||
|
[type]: 'object',
|
||||||
|
[doc]: 'Open Graph metadata (https://hexo.io/docs/helpers.html#open-graph)',
|
||||||
|
fb_app_id: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Facebook App ID',
|
||||||
|
[defaultValue]: null
|
||||||
|
},
|
||||||
|
fb_admins: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Facebook Admin ID',
|
||||||
|
[defaultValue]: null
|
||||||
|
},
|
||||||
|
twitter_id: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Twitter ID',
|
||||||
|
[defaultValue]: null
|
||||||
|
},
|
||||||
|
twitter_site: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Twitter site',
|
||||||
|
[defaultValue]: null
|
||||||
|
},
|
||||||
|
google_plus: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Google+ profile link',
|
||||||
|
[defaultValue]: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,31 @@
|
||||||
|
const { doc, type, defaultValue } = require('../common/utils').descriptors;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
[type]: 'object',
|
||||||
|
[doc]: 'Navigation bar link settings',
|
||||||
|
menu: {
|
||||||
|
[type]: 'object',
|
||||||
|
[doc]: 'Navigation bar menu links',
|
||||||
|
[defaultValue]: {
|
||||||
|
Home: '/',
|
||||||
|
Archives: '/archives',
|
||||||
|
Categories: '/categories',
|
||||||
|
Tags: '/tags',
|
||||||
|
About: '/about'
|
||||||
|
},
|
||||||
|
'*': {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Path or URL to the menu item'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
links: {
|
||||||
|
...require('./icon_link.spec'),
|
||||||
|
[doc]: 'Navigation bar links to be shown on the right',
|
||||||
|
[defaultValue]: {
|
||||||
|
'Download on GitHub': {
|
||||||
|
icon: 'fab fa-github',
|
||||||
|
url: 'http://github.com/ppoffice/hexo-theme-icarus'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,44 @@
|
||||||
|
const { doc, type, defaultValue, required, requires } = require('../common/utils').descriptors;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
[type]: 'object',
|
||||||
|
[doc]: 'Other plugin settings',
|
||||||
|
gallery: {
|
||||||
|
[type]: 'boolean',
|
||||||
|
[doc]: 'Enable the lightGallery and Justified Gallery plugins',
|
||||||
|
[defaultValue]: true
|
||||||
|
},
|
||||||
|
'outdated-browser': {
|
||||||
|
[type]: 'boolean',
|
||||||
|
[doc]: 'Enable the Outdated Browser plugin',
|
||||||
|
[defaultValue]: true
|
||||||
|
},
|
||||||
|
animejs: {
|
||||||
|
[type]: 'boolean',
|
||||||
|
[doc]: 'Enable page animations',
|
||||||
|
[defaultValue]: true
|
||||||
|
},
|
||||||
|
mathjax: {
|
||||||
|
[type]: 'boolean',
|
||||||
|
[doc]: 'Enable the MathJax plugin',
|
||||||
|
[defaultValue]: true
|
||||||
|
},
|
||||||
|
'google-analytics': {
|
||||||
|
[type]: ['boolean', 'object'],
|
||||||
|
[doc]: 'Google Analytics plugin settings (http://ppoffice.github.io/hexo-theme-icarus/2018/01/01/plugin/Analytics/#Google-Analytics)',
|
||||||
|
tracking_id: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Google Analytics tracking id',
|
||||||
|
[defaultValue]: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'baidu-analytics': {
|
||||||
|
[type]: ['boolean', 'object'],
|
||||||
|
[doc]: 'Baidu Analytics plugin settings (http://ppoffice.github.io/hexo-theme-icarus/2018/01/01/plugin/Analytics/#Baidu-Analytics)',
|
||||||
|
tracking_id: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Baidu Analytics tracking id',
|
||||||
|
[defaultValue]: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,21 @@
|
||||||
|
const { doc, type, defaultValue } = require('../common/utils').descriptors;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
[type]: 'object',
|
||||||
|
[doc]: 'CDN provider settings',
|
||||||
|
cdn: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Name or URL of the JavaScript and/or stylesheet CDN provider',
|
||||||
|
[defaultValue]: 'cdnjs'
|
||||||
|
},
|
||||||
|
fontcdn: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Name or URL of the webfont CDN provider',
|
||||||
|
[defaultValue]: 'google'
|
||||||
|
},
|
||||||
|
iconcdn: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Name or URL of the webfont Icon CDN provider',
|
||||||
|
[defaultValue]: 'fontawesome'
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,17 @@
|
||||||
|
const { doc, type, defaultValue, required, requires } = require('../common/utils').descriptors;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
[type]: 'object',
|
||||||
|
[doc]: 'Search plugin settings (http://ppoffice.github.io/hexo-theme-icarus/categories/Configuration/Search-Plugins)',
|
||||||
|
type: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Name of the search plugin',
|
||||||
|
[defaultValue]: 'insight'
|
||||||
|
},
|
||||||
|
cx: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Google CSE cx value',
|
||||||
|
[required]: true,
|
||||||
|
[requires]: search => search.type === 'google-cse'
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,17 @@
|
||||||
|
const { doc, type, defaultValue, required, requires } = require('../common/utils').descriptors;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
[type]: 'object',
|
||||||
|
[doc]: 'Share plugin settings (http://ppoffice.github.io/hexo-theme-icarus/categories/Configuration/Share-Plugins)',
|
||||||
|
type: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Share plugin name',
|
||||||
|
[defaultValue]: null
|
||||||
|
},
|
||||||
|
install_url: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'URL to the share plugin script provided by share plugin service provider',
|
||||||
|
[required]: true,
|
||||||
|
[requires]: share => share.type === 'sharethis' || share.type === 'addthis'
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
const { doc, type, defaultValue, required, requires, format } = require('../common/utils').descriptors;
|
||||||
|
|
||||||
|
const DEFAULT_WIDGETS = [
|
||||||
|
{
|
||||||
|
type: 'profile',
|
||||||
|
position: 'left',
|
||||||
|
author: 'Your name',
|
||||||
|
author_title: 'Your title',
|
||||||
|
location: 'Your location',
|
||||||
|
avatar: null,
|
||||||
|
gravatar: null,
|
||||||
|
follow_link: 'http://github.com/ppoffice',
|
||||||
|
social_links: {
|
||||||
|
Github: {
|
||||||
|
icon: 'fab fa-github',
|
||||||
|
url: 'http://github.com/ppoffice'
|
||||||
|
},
|
||||||
|
Facebook: {
|
||||||
|
icon: 'fab fa-facebook',
|
||||||
|
url: 'http://facebook.com'
|
||||||
|
},
|
||||||
|
Twitter: {
|
||||||
|
icon: 'fab fa-twitter',
|
||||||
|
url: 'http://twitter.com'
|
||||||
|
},
|
||||||
|
Dribbble: {
|
||||||
|
icon: 'fab fa-dribbble',
|
||||||
|
url: 'http://dribbble.com'
|
||||||
|
},
|
||||||
|
RSS: {
|
||||||
|
icon: 'fas fa-rss',
|
||||||
|
url: '/'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'toc',
|
||||||
|
position: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'links',
|
||||||
|
position: 'left',
|
||||||
|
links: {
|
||||||
|
Hexo: 'https://hexo.io',
|
||||||
|
Github: 'https://github.com/ppoffice'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
position: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'tagcloud',
|
||||||
|
position: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'recent_posts',
|
||||||
|
position: 'right'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'archive',
|
||||||
|
position: 'right'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'tag',
|
||||||
|
position: 'right'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const ProfileSpec = {
|
||||||
|
author: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Author name to be shown in the profile widget',
|
||||||
|
[defaultValue]: 'Your name'
|
||||||
|
},
|
||||||
|
author_title: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Title of the author to be shown in the profile widget',
|
||||||
|
[defaultValue]: 'Your title'
|
||||||
|
},
|
||||||
|
location: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Author\'s current location to be shown in the profile widget',
|
||||||
|
[defaultValue]: 'Your location'
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Path or URL to the avatar to be shown in the profile widget',
|
||||||
|
[defaultValue]: '/images/avatar.png'
|
||||||
|
},
|
||||||
|
gravatar: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Email address for the Gravatar to be shown in the profile widget',
|
||||||
|
},
|
||||||
|
follow_link: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Path or URL for the follow button',
|
||||||
|
},
|
||||||
|
social_links: {
|
||||||
|
...require('./icon_link.spec'),
|
||||||
|
[doc]: 'Links to be shown on the bottom of the profile widget',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let key in ProfileSpec) {
|
||||||
|
ProfileSpec[key][requires] = widget => widget.type === 'profile';
|
||||||
|
}
|
||||||
|
|
||||||
|
const LinksSpec = {
|
||||||
|
links: {
|
||||||
|
[type]: 'object',
|
||||||
|
[doc]: 'Links to be shown in the links widget',
|
||||||
|
[requires]: parent => parent.type === 'links',
|
||||||
|
'*': {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Path or URL to the link',
|
||||||
|
[required]: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
[type]: 'array',
|
||||||
|
[doc]: 'Sidebar widget settings',
|
||||||
|
[defaultValue]: DEFAULT_WIDGETS,
|
||||||
|
'*': {
|
||||||
|
[type]: 'object',
|
||||||
|
[doc]: 'Single widget settings',
|
||||||
|
type: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Widget name',
|
||||||
|
[required]: true,
|
||||||
|
[defaultValue]: 'profile'
|
||||||
|
},
|
||||||
|
position: {
|
||||||
|
[type]: 'string',
|
||||||
|
[doc]: 'Where should the widget be placed, left or right',
|
||||||
|
[format]: /^(left|right)$/,
|
||||||
|
[required]: true,
|
||||||
|
[defaultValue]: 'left'
|
||||||
|
},
|
||||||
|
...ProfileSpec,
|
||||||
|
...LinksSpec
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,260 +1,47 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const util = require('util');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const yaml = require('js-yaml');
|
|
||||||
const logger = require('hexo-log')();
|
const logger = require('hexo-log')();
|
||||||
const Schema = require('js-yaml/lib/js-yaml/schema');
|
const yaml = require('js-yaml');
|
||||||
const Type = require('js-yaml/lib/js-yaml/type');
|
|
||||||
|
|
||||||
const rootSpec = require('../specs/_config.yml');
|
const { errors } = require('../common/utils');
|
||||||
const { projectName, is } = require('../specs/common');
|
const rootSpec = require('../specs/config.spec');
|
||||||
const { type, required, condition, defaultValue, description } = require('../specs/common').descriptor;
|
const ConfigValidator = require('../common/ConfigValidator');
|
||||||
|
const ConfigGenerator = require('../common/ConfigGenerator');
|
||||||
|
|
||||||
const UNDEFINED = Symbol('undefined');
|
|
||||||
const CONFIG_PATH = path.join(__dirname, '../..', '_config.yml');
|
const CONFIG_PATH = path.join(__dirname, '../..', '_config.yml');
|
||||||
const YAML_SCHEMA = new Schema({
|
|
||||||
include: [
|
|
||||||
require('js-yaml/lib/js-yaml/schema/default_full')
|
|
||||||
],
|
|
||||||
implicit: [
|
|
||||||
new Type('tag:yaml.org,2002:null', {
|
|
||||||
kind: 'scalar',
|
|
||||||
resolve(data) {
|
|
||||||
if (data === null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const max = data.length;
|
|
||||||
return (max === 1 && data === '~') ||
|
|
||||||
(max === 4 && (data === 'null' || data === 'Null' || data === 'NULL'));
|
|
||||||
},
|
|
||||||
construct: () => null,
|
|
||||||
predicate: object => object === null,
|
|
||||||
represent: {
|
|
||||||
empty: function () { return ''; }
|
|
||||||
},
|
|
||||||
defaultStyle: 'empty'
|
|
||||||
})
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
function toPlainObject(object, depth) {
|
|
||||||
if (object === null || (!is.object(object) && !is.array(object))) {
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
if (depth <= 0) {
|
|
||||||
return is.array(object) ? '[Array]' : '[Object]';
|
|
||||||
}
|
|
||||||
if (is.array(object)) {
|
|
||||||
const result = [];
|
|
||||||
for (let child of object) {
|
|
||||||
result.push(toPlainObject(child, depth - 1));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
const result = {};
|
|
||||||
for (let key in object) {
|
|
||||||
result[key] = toPlainObject(object[key], depth - 1);
|
|
||||||
}
|
|
||||||
for (let key of Object.getOwnPropertySymbols(object)) {
|
|
||||||
result[key.toString()] = toPlainObject(object[key], depth - 1);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isValidSpec(spec) {
|
|
||||||
if (!spec.hasOwnProperty(type)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkPrecondition(spec, parameter) {
|
|
||||||
if (!spec.hasOwnProperty(condition)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (spec[condition](parameter) === true) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (e) { }
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDefaultConfig(spec, parentConfig = null) {
|
|
||||||
if (!isValidSpec(spec)) {
|
|
||||||
return UNDEFINED;
|
|
||||||
}
|
|
||||||
if (!checkPrecondition(spec, parentConfig)) {
|
|
||||||
return UNDEFINED;
|
|
||||||
}
|
|
||||||
if (spec.hasOwnProperty(defaultValue)) {
|
|
||||||
return spec[defaultValue];
|
|
||||||
}
|
|
||||||
const types = is.array(spec[type]) ? spec[type] : [spec[type]];
|
|
||||||
if (types.includes('object')) {
|
|
||||||
let defaults = UNDEFINED;
|
|
||||||
for (let key in spec) {
|
|
||||||
if (typeof (key) === 'symbol' || key === '*') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const value = createDefaultConfig(spec[key], defaults);
|
|
||||||
if (value !== UNDEFINED) {
|
|
||||||
if (defaults === UNDEFINED) {
|
|
||||||
defaults = {};
|
|
||||||
}
|
|
||||||
if (spec[key].hasOwnProperty(description)) {
|
|
||||||
defaults['#' + key] = spec[key][description];
|
|
||||||
}
|
|
||||||
defaults[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaults;
|
|
||||||
} else if (types.includes('array') && spec.hasOwnProperty('*')) {
|
|
||||||
return [createDefaultConfig(spec['*'], {})];
|
|
||||||
}
|
|
||||||
return UNDEFINED;
|
|
||||||
}
|
|
||||||
|
|
||||||
function dumpConfig(config, path) {
|
|
||||||
const configYaml = yaml.safeDump(config, {
|
|
||||||
indent: 4,
|
|
||||||
lineWidth: 1024,
|
|
||||||
schema: YAML_SCHEMA
|
|
||||||
}).replace(/^(\s*)\'#.*?\':\s*\'*(.*?)\'*$/mg, '$1# $2');
|
|
||||||
fs.writeFileSync(path, configYaml);
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateConfigVersion(config, spec) {
|
|
||||||
function getMajorVersion(version) {
|
|
||||||
try {
|
|
||||||
return parseInt(version.split('.')[0]);
|
|
||||||
} catch (e) {
|
|
||||||
logger.error(`Configuration version number ${version} is malformed.`);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!config.hasOwnProperty('version')) {
|
|
||||||
logger.error('Failed to get the version number of the configuration file.');
|
|
||||||
logger.warn('You are probably using a previous version of confiugration.');
|
|
||||||
logger.warn(`Please be noted that it may not work in the newer versions of ${projectName}.`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const specMajorVersion = getMajorVersion(spec.version[defaultValue]);
|
|
||||||
const configMajorVersion = getMajorVersion(config.version);
|
|
||||||
if (configMajorVersion === null || specMajorVersion === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (configMajorVersion < specMajorVersion) {
|
|
||||||
logger.warn('You are using a previous version of confiugration.');
|
|
||||||
logger.warn(`Please be noted that it may not work in the newer versions of ${projectName}.`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (configMajorVersion > specMajorVersion) {
|
|
||||||
logger.warn('You are probably using a more recent version of confiugration.');
|
|
||||||
logger.warn(`Please be noted that it may not work in the previous versions of ${projectName}.`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateConfigType(config, specTypes) {
|
|
||||||
specTypes = is.array(specTypes) ? specTypes : [specTypes];
|
|
||||||
for (let specType of specTypes) {
|
|
||||||
if (is[specType](config)) {
|
|
||||||
return specType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.error(`Config ${toPlainObject(config, 2)} do not match types ${specTypes}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const INVALID_SPEC = Symbol();
|
|
||||||
const MISSING_REQUIRED = Symbol();
|
|
||||||
const INVALID_TYPE = Symbol();
|
|
||||||
|
|
||||||
function validateConfigAndWarn(config, spec, parentConfig, configPath = []) {
|
|
||||||
const result = validateConfig(config, spec, parentConfig, configPath);
|
|
||||||
if (result !== true) {
|
|
||||||
const pathString = configPath.join('.');
|
|
||||||
const specTypes = is.array(spec[type]) ? spec[type] : [spec[type]];
|
|
||||||
switch(result) {
|
|
||||||
case INVALID_SPEC:
|
|
||||||
logger.error(`Invalid specification! The specification '${pathString}' does not have a [type] field:`);
|
|
||||||
logger.error('The specification of this configuration is:');
|
|
||||||
logger.error(JSON.stringify(toPlainObject(spec, 2), null, 4));
|
|
||||||
break;
|
|
||||||
case MISSING_REQUIRED:
|
|
||||||
logger.error(`Configuration '${pathString}' in required by the specification but is missing from the configuration!`);
|
|
||||||
logger.error('The specification of this configuration is:');
|
|
||||||
logger.error(JSON.stringify(toPlainObject(spec, 2), null, 4));
|
|
||||||
break;
|
|
||||||
case INVALID_TYPE:
|
|
||||||
logger.error(`Type mismatch! Configuration '${pathString}' is not the '${specTypes.join(' or ')}' type.`);
|
|
||||||
logger.error('The configuration value is:');
|
|
||||||
logger.error(JSON.stringify(toPlainObject(config, 2), null, 4));
|
|
||||||
logger.error('The specification of this configuration is:');
|
|
||||||
logger.error(JSON.stringify(toPlainObject(spec, 2), null, 4));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateConfig(config, spec, parentConfig = null, configPath = []) {
|
|
||||||
if (!isValidSpec(spec)) {
|
|
||||||
return INVALID_SPEC;
|
|
||||||
}
|
|
||||||
if (!checkPrecondition(spec, parentConfig)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (typeof(config) === 'undefined' || config === null) {
|
|
||||||
if (spec[required] === true) {
|
|
||||||
return MISSING_REQUIRED;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const configType = validateConfigType(config, spec[type]);
|
|
||||||
if (configType === null) {
|
|
||||||
return INVALID_TYPE;
|
|
||||||
}
|
|
||||||
if (configType === 'array' && spec.hasOwnProperty('*')) {
|
|
||||||
for (let i = 0; i < config.length; i++) {
|
|
||||||
if (!validateConfigAndWarn(config[i], spec['*'], config, configPath.concat(`[${i}]`))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (configType === 'object') {
|
|
||||||
for (let key in spec) {
|
|
||||||
if (key === '*') {
|
|
||||||
for (let configKey in config) {
|
|
||||||
if (!validateConfigAndWarn(config[configKey], spec['*'], config, configPath.concat(configKey))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!validateConfigAndWarn(config[key], spec[key], config, configPath.concat(key))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('Checking if the configuration file exists...');
|
logger.info('Checking if the configuration file exists...');
|
||||||
|
|
||||||
if (!fs.existsSync(CONFIG_PATH)) {
|
if (!fs.existsSync(CONFIG_PATH)) {
|
||||||
const relativePath = path.relative(process.cwd(), CONFIG_PATH);
|
const relativePath = path.relative(process.cwd(), CONFIG_PATH);
|
||||||
logger.warn(`${relativePath} is not found. We are creating one for you...`);
|
logger.warn(`${relativePath} is not found. We are creating one for you...`);
|
||||||
dumpConfig(createDefaultConfig(rootSpec), CONFIG_PATH);
|
fs.writeFileSync(CONFIG_PATH, new ConfigGenerator(rootSpec).generate());
|
||||||
logger.info(`${relativePath} is created. Please restart Hexo to apply changes.`);
|
logger.info(`${relativePath} is created. Please restart Hexo to apply changes.`);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('Validating the configuration file...');
|
logger.info('Validating the configuration file...');
|
||||||
|
const validator = new ConfigValidator(rootSpec);
|
||||||
const config = yaml.safeLoad(fs.readFileSync(CONFIG_PATH));
|
const config = yaml.safeLoad(fs.readFileSync(CONFIG_PATH));
|
||||||
if (!validateConfigVersion(config, rootSpec)) {
|
try {
|
||||||
logger.info(`To let ${projectName} create a fresh configuration file for you, please delete or rename the following file:`);
|
validator.validate(config);
|
||||||
logger.info(CONFIG_PATH);
|
} catch (e) {
|
||||||
} else {
|
if (e instanceof errors.ConfigError) {
|
||||||
validateConfigAndWarn(config, rootSpec);
|
logger.error(e.message);
|
||||||
|
if (e.hasOwnProperty('spec')) {
|
||||||
|
logger.error('The specification of this configuration is:');
|
||||||
|
logger.error(util.inspect(e.spec));
|
||||||
|
}
|
||||||
|
if (e.hasOwnProperty('config')) {
|
||||||
|
logger.error('Configuration value is:');
|
||||||
|
logger.error(util.inspect(e.config));
|
||||||
|
}
|
||||||
|
} else if (e instanceof errors.VersionError) {
|
||||||
|
logger.error(e.message);
|
||||||
|
logger.warn(`To let us create a fresh configuration file for you, please rename or delete the following file:`);
|
||||||
|
logger.warn(CONFIG_PATH);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue