refactor(schema): rewrite config generator & add migration
parent
46530f3c4e
commit
02d1996215
|
@ -106,5 +106,5 @@ dist
|
||||||
# Stores VSCode versions used for testing VSCode extensions
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
.vscode-test
|
.vscode-test
|
||||||
|
|
||||||
_config.yml
|
_config.yml*
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
|
|
@ -1,103 +0,0 @@
|
||||||
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 appendDoc(spec, defaults) {
|
|
||||||
if (defaults === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (is.array(defaults) && spec.hasOwnProperty('*')) {
|
|
||||||
return defaults.map(value => appendDoc(spec['*'], value));
|
|
||||||
} else if (is.object(defaults)) {
|
|
||||||
const _defaults = {};
|
|
||||||
for (let key in defaults) {
|
|
||||||
if (spec.hasOwnProperty(key) && spec[key].hasOwnProperty(doc)) {
|
|
||||||
let i = 0;
|
|
||||||
for (let line of spec[key][doc].split('\n')) {
|
|
||||||
_defaults['#' + key + i++] = line;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_defaults[key] = appendDoc(spec.hasOwnProperty(key) ? spec[key] : {}, defaults[key]);
|
|
||||||
}
|
|
||||||
return _defaults;
|
|
||||||
}
|
|
||||||
return defaults;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 appendDoc(spec, 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 = {};
|
|
||||||
}
|
|
||||||
defaults[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return appendDoc(spec, 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;
|
|
|
@ -1,132 +0,0 @@
|
||||||
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 || base.minor !== ver.minor;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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;
|
|
|
@ -1,151 +0,0 @@
|
||||||
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 = (() => ({
|
|
||||||
number(value) {
|
|
||||||
return typeof (value) === 'number';
|
|
||||||
},
|
|
||||||
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
|
|
||||||
};
|
|
|
@ -0,0 +1 @@
|
||||||
|
module.exports = require('./v2_v3');
|
|
@ -0,0 +1,89 @@
|
||||||
|
const logger = require('hexo-log')();
|
||||||
|
const deepmerge = require('deepmerge');
|
||||||
|
const Migration = require('../util/migrate').Migration;
|
||||||
|
|
||||||
|
module.exports = class extends Migration {
|
||||||
|
constructor() {
|
||||||
|
super('3.0.0', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
doMigrate(config) {
|
||||||
|
const result = deepmerge({}, config);
|
||||||
|
result.head = {
|
||||||
|
favicon: config.favicon || null,
|
||||||
|
canonical_url: config.canonical_url || null,
|
||||||
|
open_graph: config.open_graph || null,
|
||||||
|
meta: config.meta || null,
|
||||||
|
rss: config.rss || null
|
||||||
|
};
|
||||||
|
delete result.favicon;
|
||||||
|
delete result.canonical_url;
|
||||||
|
delete result.open_graph;
|
||||||
|
delete result.meta;
|
||||||
|
delete result.rss;
|
||||||
|
|
||||||
|
if (result.search && Object.prototype.hasOwnProperty.call(result.search, 'type')) {
|
||||||
|
switch (result.search.type) {
|
||||||
|
case 'google-cse':
|
||||||
|
result.search.type = 'google_cse';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.comment && Object.prototype.hasOwnProperty.call(result.comment, 'type')) {
|
||||||
|
switch (result.comment.type) {
|
||||||
|
case 'changyan':
|
||||||
|
result.comment.app_id = config.comment.appid;
|
||||||
|
delete result.comment.appid;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(result.widgets) && result.widgets.length) {
|
||||||
|
for (const widget of result.widgets) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(widget, 'type')) {
|
||||||
|
switch (widget.type) {
|
||||||
|
case 'archive':
|
||||||
|
widget.type = 'archives';
|
||||||
|
break;
|
||||||
|
case 'category':
|
||||||
|
widget.type = 'categories';
|
||||||
|
break;
|
||||||
|
case 'tag':
|
||||||
|
widget.type = 'tags';
|
||||||
|
break;
|
||||||
|
case 'tagcloud':
|
||||||
|
logger.warn('The tagcloud widget has been removed from Icarus in version 3.0.0.');
|
||||||
|
logger.warn('Please remove it from your configuration file.');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.plugins && typeof result.plugins === 'object') {
|
||||||
|
for (const name in result.plugins) {
|
||||||
|
switch (name) {
|
||||||
|
case 'outdated-browser':
|
||||||
|
result.plugins.outdated_browser = result.plugins[name];
|
||||||
|
delete result.plugins[name];
|
||||||
|
break;
|
||||||
|
case 'back-to-top':
|
||||||
|
result.plugins.back_to_top = result.plugins[name];
|
||||||
|
delete result.plugins[name];
|
||||||
|
break;
|
||||||
|
case 'baidu-analytics':
|
||||||
|
result.plugins.baidu_analytics = result.plugins[name];
|
||||||
|
delete result.plugins[name];
|
||||||
|
break;
|
||||||
|
case 'google-analytics':
|
||||||
|
result.plugins.google_analytics = result.plugins[name];
|
||||||
|
delete result.plugins[name];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
|
@ -12,7 +12,7 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Changyan app ID"
|
"description": "Changyan app ID"
|
||||||
},
|
},
|
||||||
"shortname": {
|
"conf": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Changyan configuration ID"
|
"description": "Changyan configuration ID"
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,17 +34,20 @@
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Disqus moderator username"
|
"description": "Disqus moderator username",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"admin_label": {
|
"admin_label": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Disqus moderator badge text",
|
"description": "Disqus moderator badge text",
|
||||||
"default": false
|
"default": false,
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"nesting": {
|
"nesting": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Maximum number of comment nesting level",
|
"description": "Maximum number of comment nesting level",
|
||||||
"default": 4
|
"default": 4,
|
||||||
|
"nullable": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
@ -34,35 +34,42 @@
|
||||||
"per_page": {
|
"per_page": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"description": "Pagination size, with maximum 100",
|
"description": "Pagination size, with maximum 100",
|
||||||
"default": 10
|
"default": 10,
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"distraction_free_mode": {
|
"distraction_free_mode": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Facebook-like distraction free mode",
|
"description": "Facebook-like distraction free mode",
|
||||||
"default": false
|
"default": false,
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"pager_direction": {
|
"pager_direction": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Comment sorting direction, available values are `last` and `first`",
|
"description": "Comment sorting direction, available values are `last` and `first`",
|
||||||
"default": "last"
|
"default": "last",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"create_issue_manually": {
|
"create_issue_manually": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Create GitHub issues manually for each page",
|
"description": "Create GitHub issues manually for each page",
|
||||||
"default": false
|
"default": false,
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"proxy": {
|
"proxy": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "GitHub oauth request reverse proxy for CORS"
|
"description": "GitHub oauth request reverse proxy for CORS",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"flip_move_options": {
|
"flip_move_options": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Comment list animation"
|
"description": "Comment list animation",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"enable_hotkey": {
|
"enable_hotkey": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Enable hot key (cmd|ctrl + enter) submit comment",
|
"description": "Enable hot key (cmd|ctrl + enter) submit comment",
|
||||||
"default": true
|
"default": true,
|
||||||
|
"nullable": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
@ -27,17 +27,20 @@
|
||||||
"theme": {
|
"theme": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "An optional Gitment theme object",
|
"description": "An optional Gitment theme object",
|
||||||
"default": "gitment.defaultTheme"
|
"default": "gitment.defaultTheme",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"per_page": {
|
"per_page": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"description": "An optional number to which comments will be paginated",
|
"description": "An optional number to which comments will be paginated",
|
||||||
"default": 20
|
"default": 20,
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"max_comment_height": {
|
"max_comment_height": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"description": "An optional number to limit comments' max height, over which comments will be folded",
|
"description": "An optional number to limit comments' max height, over which comments will be folded",
|
||||||
"default": 250
|
"default": 250,
|
||||||
|
"nullable": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
@ -18,17 +18,20 @@
|
||||||
},
|
},
|
||||||
"placeholder": {
|
"placeholder": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Comment box placeholders"
|
"description": "Comment box placeholders",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"notify": {
|
"notify": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Enable email notification when someone comments",
|
"description": "Enable email notification when someone comments",
|
||||||
"default": false
|
"default": false,
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"verify": {
|
"verify": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Enable verification code service",
|
"description": "Enable verification code service",
|
||||||
"default": false
|
"default": false,
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"avatar": {
|
"avatar": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@ -44,12 +47,14 @@
|
||||||
"hide",
|
"hide",
|
||||||
"mm"
|
"mm"
|
||||||
],
|
],
|
||||||
"default": "mm"
|
"default": "mm",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"avatar_force": {
|
"avatar_force": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Pull the latest avatar upon page visit",
|
"description": "Pull the latest avatar upon page visit",
|
||||||
"default": false
|
"default": false,
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"meta": {
|
"meta": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
@ -61,27 +66,32 @@
|
||||||
"nick",
|
"nick",
|
||||||
"mail",
|
"mail",
|
||||||
"link"
|
"link"
|
||||||
]
|
],
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"page_size": {
|
"page_size": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Number of comments per page",
|
"description": "Number of comments per page",
|
||||||
"default": 10
|
"default": 10,
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"visitor": {
|
"visitor": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Show visitor count",
|
"description": "Show visitor count",
|
||||||
"default": false
|
"default": false,
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"highlight": {
|
"highlight": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Enable code highlighting",
|
"description": "Enable code highlighting",
|
||||||
"default": true
|
"default": true,
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"record_ip": {
|
"record_ip": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Record reviewer IP address",
|
"description": "Record reviewer IP address",
|
||||||
"default": false
|
"default": false,
|
||||||
|
"nullable": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
@ -11,12 +11,14 @@
|
||||||
"theme": {
|
"theme": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Code highlight themes\nhttps://github.com/highlightjs/highlight.js/tree/master/src/styles",
|
"description": "Code highlight themes\nhttps://github.com/highlightjs/highlight.js/tree/master/src/styles",
|
||||||
"default": "atom-one-light"
|
"default": "atom-one-light",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"clipboard": {
|
"clipboard": {
|
||||||
"type": "string",
|
"type": "boolean",
|
||||||
"description": "Show copy code button",
|
"description": "Show copy code button",
|
||||||
"default": true
|
"default": true,
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"fold": {
|
"fold": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@ -26,19 +28,23 @@
|
||||||
"folded",
|
"folded",
|
||||||
"unfolded"
|
"unfolded"
|
||||||
],
|
],
|
||||||
"default": "unfolded"
|
"default": "unfolded",
|
||||||
}
|
"nullable": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
"thumbnail": {
|
"thumbnail": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Whether to show thumbnail image for every article",
|
"description": "Whether to show thumbnail image for every article",
|
||||||
"default": true
|
"default": true,
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"readtime": {
|
"readtime": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Whether to show estimated article reading time",
|
"description": "Whether to show estimated article reading time",
|
||||||
"default": true
|
"default": true,
|
||||||
|
"nullable": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,11 +7,13 @@
|
||||||
"favicon": {
|
"favicon": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "URL or path to the website's icon",
|
"description": "URL or path to the website's icon",
|
||||||
"default": "/img/favicon.svg"
|
"default": "/img/favicon.svg",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"canonical_url": {
|
"canonical_url": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Canonical URL of the current page"
|
"description": "Canonical URL of the current page",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"open_graph": {
|
"open_graph": {
|
||||||
"$ref": "/misc/open_graph.json"
|
"$ref": "/misc/open_graph.json"
|
||||||
|
@ -21,7 +23,8 @@
|
||||||
},
|
},
|
||||||
"rss": {
|
"rss": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "URL or path to the website's RSS atom.xml"
|
"description": "URL or path to the website's RSS atom.xml",
|
||||||
|
"nullable": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -21,7 +21,8 @@
|
||||||
"Tags": "/tags",
|
"Tags": "/tags",
|
||||||
"About": "/about"
|
"About": "/about"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
"$ref": "/misc/poly_links.json",
|
"$ref": "/misc/poly_links.json",
|
||||||
|
|
|
@ -7,17 +7,20 @@
|
||||||
"cdn": {
|
"cdn": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Name or URL template of the JavaScript and/or stylesheet CDN provider",
|
"description": "Name or URL template of the JavaScript and/or stylesheet CDN provider",
|
||||||
"default": "jsdelivr"
|
"default": "jsdelivr",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"fontcdn": {
|
"fontcdn": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Name or URL template of the webfont CDN provider",
|
"description": "Name or URL template of the webfont CDN provider",
|
||||||
"default": "google"
|
"default": "google",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"iconcdn": {
|
"iconcdn": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Name or URL of the webfont Icon CDN provider",
|
"description": "Name or URL of the webfont Icon CDN provider",
|
||||||
"default": "fontawesome"
|
"default": "fontawesome",
|
||||||
|
"nullable": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,7 +6,8 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"version": {
|
"version": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Version of the configuration file"
|
"description": "Version of the configuration file",
|
||||||
|
"default": "3.0.0"
|
||||||
},
|
},
|
||||||
"logo": {
|
"logo": {
|
||||||
"type": [
|
"type": [
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "patreon"
|
"const": "buymeacoffee"
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
@ -23,6 +23,6 @@
|
||||||
"required": [
|
"required": [
|
||||||
"type",
|
"type",
|
||||||
"business",
|
"business",
|
||||||
"currencyCode"
|
"currency_code"
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -6,5 +6,6 @@
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Meta tag specified in <attribute>=<value> style\nE.g., name=theme-color;content=#123456 => <meta name=\"theme-color\" content=\"#123456\">"
|
"description": "Meta tag specified in <attribute>=<value> style\nE.g., name=theme-color;content=#123456 => <meta name=\"theme-color\" content=\"#123456\">"
|
||||||
}
|
},
|
||||||
|
"nullable": true
|
||||||
}
|
}
|
|
@ -6,16 +6,19 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"title": {
|
"title": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Page title (og:title)"
|
"description": "Page title (og:title)",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Page type (og:type)",
|
"description": "Page type (og:type)",
|
||||||
"default": "blog"
|
"default": "blog",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Page URL (og:url)"
|
"description": "Page URL (og:url)",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"image": {
|
"image": {
|
||||||
"type": [
|
"type": [
|
||||||
|
@ -25,39 +28,49 @@
|
||||||
"description": "Page cover (og:image)",
|
"description": "Page cover (og:image)",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
},
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"site_name": {
|
"site_name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Site name (og:site_name)"
|
"description": "Site name (og:site_name)",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Page description (og:description)"
|
"description": "Page description (og:description)",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"twitter_card": {
|
"twitter_card": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Twitter card type (twitter:card)"
|
"description": "Twitter card type (twitter:card)",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"twitter_id": {
|
"twitter_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Twitter ID (twitter:creator)"
|
"description": "Twitter ID (twitter:creator)",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"twitter_site": {
|
"twitter_site": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Twitter ID (twitter:creator)"
|
"description": "Twitter ID (twitter:creator)",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"google_plus": {
|
"google_plus": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Google+ profile link (deprecated)"
|
"description": "Google+ profile link (deprecated)",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"fb_admins": {
|
"fb_admins": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Facebook admin ID"
|
"description": "Facebook admin ID",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"fb_app_id": {
|
"fb_app_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Facebook App ID"
|
"description": "Facebook App ID",
|
||||||
}
|
"nullable": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"nullable": true
|
||||||
}
|
}
|
|
@ -36,5 +36,6 @@
|
||||||
"icon": "fab fa-github"
|
"icon": "fab fa-github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"nullable": true
|
||||||
}
|
}
|
|
@ -5,8 +5,9 @@
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"tracking_id": {
|
"tracking_id": {
|
||||||
"type": "object",
|
"type": "string",
|
||||||
"description": "Baidu Analytics tracking ID"
|
"description": "Baidu Analytics tracking ID",
|
||||||
|
"nullable": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
@ -5,8 +5,9 @@
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"tracking_id": {
|
"tracking_id": {
|
||||||
"type": "object",
|
"type": "string",
|
||||||
"description": "Google Analytics tracking ID"
|
"description": "Google Analytics tracking ID",
|
||||||
|
"nullable": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
@ -9,7 +9,11 @@
|
||||||
"string",
|
"string",
|
||||||
"number"
|
"number"
|
||||||
],
|
],
|
||||||
"description": "Hotjar site id"
|
"description": "Hotjar site id",
|
||||||
}
|
"nullable": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"site_id"
|
||||||
|
]
|
||||||
}
|
}
|
|
@ -22,7 +22,8 @@
|
||||||
"Hexo": "https://hexo.io",
|
"Hexo": "https://hexo.io",
|
||||||
"Bulma": "https://bulma.io"
|
"Bulma": "https://bulma.io"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"nullable": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
@ -6,48 +6,56 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "profile"
|
"const": "profile",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"author": {
|
"author": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Author name",
|
"description": "Author name",
|
||||||
"examples": [
|
"examples": [
|
||||||
"Your name"
|
"Your name"
|
||||||
]
|
],
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"author_title": {
|
"author_title": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Author title",
|
"description": "Author title",
|
||||||
"examples": [
|
"examples": [
|
||||||
"Your title"
|
"Your title"
|
||||||
]
|
],
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"location": {
|
"location": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Author's current location",
|
"description": "Author's current location",
|
||||||
"examples": [
|
"examples": [
|
||||||
"Your location"
|
"Your location"
|
||||||
]
|
],
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"avatar": {
|
"avatar": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "URL or path to the avatar image"
|
"description": "URL or path to the avatar image",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"avatar_rounded": {
|
"avatar_rounded": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Whether show the rounded avatar image",
|
"description": "Whether show the rounded avatar image",
|
||||||
"default": false
|
"default": false,
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"gravatar": {
|
"gravatar": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Email address for the Gravatar"
|
"description": "Email address for the Gravatar",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"follow_link": {
|
"follow_link": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "URL or path for the follow button",
|
"description": "URL or path for the follow button",
|
||||||
"examples": [
|
"examples": [
|
||||||
"https://github.com/ppoffice"
|
"https://github.com/ppoffice"
|
||||||
]
|
],
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"social_links": {
|
"social_links": {
|
||||||
"$ref": "/misc/poly_links.json",
|
"$ref": "/misc/poly_links.json",
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Hint text under the email input"
|
"description": "Hint text under the email input",
|
||||||
|
"nullable": true
|
||||||
},
|
},
|
||||||
"feedburner_id": {
|
"feedburner_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
const fs = require('fs');
|
|
||||||
const util = require('util');
|
|
||||||
const path = require('path');
|
|
||||||
const logger = require('hexo-log')();
|
|
||||||
const yaml = require('js-yaml');
|
|
||||||
|
|
||||||
const { errors } = require('../common/utils');
|
|
||||||
const rootSpec = require('../specs/config.spec');
|
|
||||||
const ConfigValidator = require('../common/ConfigValidator');
|
|
||||||
const ConfigGenerator = require('../common/ConfigGenerator');
|
|
||||||
|
|
||||||
const CONFIG_PATH = path.join(__dirname, '../..', '_config.yml');
|
|
||||||
|
|
||||||
logger.info('Validating the configuration file');
|
|
||||||
|
|
||||||
if (!fs.existsSync(CONFIG_PATH)) {
|
|
||||||
const relativePath = path.relative(process.cwd(), CONFIG_PATH);
|
|
||||||
logger.warn(`${relativePath} is not found. We are creating one for you...`);
|
|
||||||
fs.writeFileSync(CONFIG_PATH, new ConfigGenerator(rootSpec).generate());
|
|
||||||
logger.info(`${relativePath} is created. Please restart Hexo to apply changes.`);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const validator = new ConfigValidator(rootSpec);
|
|
||||||
const config = yaml.safeLoad(fs.readFileSync(CONFIG_PATH));
|
|
||||||
try {
|
|
||||||
validator.validate(config);
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof errors.ConfigError) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
const logger = require('hexo-log')();
|
|
||||||
const packageInfo = require('../../package.json');
|
|
||||||
|
|
||||||
// FIXME: will not check against package version
|
|
||||||
function checkDependency(name) {
|
|
||||||
try {
|
|
||||||
require.resolve(name);
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
logger.error(`Package ${name} is not installed.`);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('Checking if required dependencies are installed...');
|
|
||||||
const missingDeps = Object.keys(packageInfo.peerDependencies)
|
|
||||||
.map(checkDependency)
|
|
||||||
.some(installed => !installed);
|
|
||||||
if (missingDeps) {
|
|
||||||
logger.error('Please install the missing dependencies from the root directory of your Hexo site.');
|
|
||||||
/* eslint no-process-exit: "off" */
|
|
||||||
process.exit(-1);
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
const logger = require('hexo-log')();
|
|
||||||
|
|
||||||
logger.info(`=======================================
|
|
||||||
██╗ ██████╗ █████╗ ██████╗ ██╗ ██╗███████╗
|
|
||||||
██║██╔════╝██╔══██╗██╔══██╗██║ ██║██╔════╝
|
|
||||||
██║██║ ███████║██████╔╝██║ ██║███████╗
|
|
||||||
██║██║ ██╔══██║██╔══██╗██║ ██║╚════██║
|
|
||||||
██║╚██████╗██║ ██║██║ ██║╚██████╔╝███████║
|
|
||||||
╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝
|
|
||||||
=============================================`);
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
const path = require('path');
|
||||||
|
const logger = require('hexo-log')();
|
||||||
|
|
||||||
|
class Version {
|
||||||
|
constructor(version) {
|
||||||
|
const ver = version.split('.').map(i => parseInt(i, 10));
|
||||||
|
if (ver.length !== 3) {
|
||||||
|
throw new Error('Malformed version number ' + version);
|
||||||
|
}
|
||||||
|
this.major = ver[0];
|
||||||
|
this.minor = ver[1];
|
||||||
|
this.patch = ver[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `${this.major}.${this.minor}.${this.patch}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Version.compare = function(a, b) {
|
||||||
|
if (!(a instanceof Version) || !(b instanceof Version)) {
|
||||||
|
throw new Error('Cannot compare non-Versions');
|
||||||
|
}
|
||||||
|
if (a.major !== b.major) {
|
||||||
|
return a.major - b.major;
|
||||||
|
}
|
||||||
|
if (a.minor !== b.minor) {
|
||||||
|
return a.minor - b.minor;
|
||||||
|
}
|
||||||
|
if (a.patch !== b.patch) {
|
||||||
|
return a.patch - b.patch;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Migration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} version Target version
|
||||||
|
* @param {string} head File name of the previous migration
|
||||||
|
*/
|
||||||
|
constructor(version, head) {
|
||||||
|
this.version = new Version(version);
|
||||||
|
this.head = head;
|
||||||
|
}
|
||||||
|
|
||||||
|
doMigrate(config) {
|
||||||
|
throw new Error('Not implemented!');
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate(config) {
|
||||||
|
logger.info(`Updating configurations from ${config.version} to ${this.version.toString()}...`);
|
||||||
|
const result = this.doMigrate(config);
|
||||||
|
result.version = this.version.toString();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Migrator {
|
||||||
|
constructor(root) {
|
||||||
|
this.versions = [];
|
||||||
|
this.migrations = {};
|
||||||
|
|
||||||
|
let head = 'head';
|
||||||
|
while (head) {
|
||||||
|
const migration = new(require(path.join(root, head)))();
|
||||||
|
if (!(migration instanceof Migration)) {
|
||||||
|
throw new Error(`Migration ${head} is not a Migration class.`);
|
||||||
|
}
|
||||||
|
this.versions.push(migration.version);
|
||||||
|
this.migrations[migration.version.toString()] = migration;
|
||||||
|
head = migration.head;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.versions.sort(Version.compare);
|
||||||
|
}
|
||||||
|
|
||||||
|
isOudated(version) {
|
||||||
|
if (!this.versions.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Version.compare(new Version(version), this.getLatestVersion()) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLatestVersion() {
|
||||||
|
if (!this.versions.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.versions[this.versions.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
migrate(config, toVersion = null) {
|
||||||
|
const fVer = new Version(config.version);
|
||||||
|
const tVer = toVersion ? new Version(toVersion) : this.getLatestVersion();
|
||||||
|
// find all migrations whose version is larger than fromVer, smaller or equal to toVer
|
||||||
|
// and run migrations on the config one by one
|
||||||
|
return this.versions.filter(ver => Version.compare(ver, fVer) > 0 && Version.compare(ver, tVer) <= 0)
|
||||||
|
.sort(Version.compare)
|
||||||
|
.reduce((cfg, ver) => {
|
||||||
|
const migration = this.migrations[ver.toString()];
|
||||||
|
return migration.migrate(cfg);
|
||||||
|
}, config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Migrator.Version = Version;
|
||||||
|
Migrator.Migration = Migration;
|
||||||
|
|
||||||
|
module.exports = Migrator;
|
|
@ -1,6 +1,9 @@
|
||||||
const Ajv = require('ajv');
|
const Ajv = require('ajv');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const deepmerge = require('deepmerge');
|
const deepmerge = require('deepmerge');
|
||||||
|
const yaml = require('./yaml');
|
||||||
|
|
||||||
|
const MAGIC = 'c823d4d4';
|
||||||
|
|
||||||
const PRIMITIVE_DEFAULTS = {
|
const PRIMITIVE_DEFAULTS = {
|
||||||
'null': null,
|
'null': null,
|
||||||
|
@ -23,16 +26,84 @@ class DefaultValue {
|
||||||
this.description = source.description;
|
this.description = source.description;
|
||||||
}
|
}
|
||||||
if ('value' in source && source.value) {
|
if ('value' in source && source.value) {
|
||||||
|
if (this.value instanceof DefaultValue) {
|
||||||
|
this.value.merge(source.value);
|
||||||
|
} else if (Array.isArray(this.value) && Array.isArray(source.value)) {
|
||||||
|
this.value.concat(...source.value);
|
||||||
|
} else if (typeof this.value === 'object' && typeof source.value === 'object') {
|
||||||
|
for (const key in source.value) {
|
||||||
|
this.value[key] = source.value[key];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
this.value = deepmerge(this.value, source.value);
|
this.value = deepmerge(this.value, source.value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
clone() {
|
||||||
return '[DefaultValue]' + JSON.stringify(value);
|
const result = new DefaultValue(this.value, this.description);
|
||||||
|
if (result.value instanceof DefaultValue) {
|
||||||
|
result.value = result.value.clone();
|
||||||
|
} else if (Array.isArray(result.value)) {
|
||||||
|
result.value = [].concat(...result.value);
|
||||||
|
} else if (typeof result.value === 'object') {
|
||||||
|
result.value = Object.assign({}, result.value);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
toCommentedArray() {
|
||||||
|
return [].concat(...this.value.map(item => {
|
||||||
|
if (item instanceof DefaultValue) {
|
||||||
|
if (typeof item.description !== 'string' || !item.description.trim()) {
|
||||||
|
return [item.toCommented()];
|
||||||
|
}
|
||||||
|
return item.description.split('\n').map((line, i) => {
|
||||||
|
return MAGIC + i + ': ' + line;
|
||||||
|
}).concat(item.toCommented());
|
||||||
|
}
|
||||||
|
return [item];
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
toCommentedObject() {
|
||||||
|
if (this.value instanceof DefaultValue) {
|
||||||
|
return this.value.toCommented();
|
||||||
|
}
|
||||||
|
const result = {};
|
||||||
|
for (const key in this.value) {
|
||||||
|
const item = this.value[key];
|
||||||
|
if (item instanceof DefaultValue) {
|
||||||
|
if (typeof item.description === 'string' && item.description.trim()) {
|
||||||
|
item.description.split('\n').forEach((line, i) => {
|
||||||
|
result[MAGIC + key + i] = line;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
result[key] = item.toCommented();
|
||||||
|
} else {
|
||||||
|
result[key] = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
toCommented() {
|
||||||
|
if (Array.isArray(this.value)) {
|
||||||
|
return this.toCommentedArray();
|
||||||
|
} else if (typeof this.value === 'object' && this.value !== null) {
|
||||||
|
return this.toCommentedObject();
|
||||||
|
}
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
toYaml() {
|
||||||
|
const regex = new RegExp('^(\\s*)(?:-\\s*\\\')?' + MAGIC + '.*?:\\s*\\\'?(.*?)\\\'*$', 'mg');
|
||||||
|
return yaml.stringify(this.toCommented()).replace(regex, '$1# $2');// restore comments
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* eslint-disable no-use-before-define */
|
||||||
class Schema {
|
class Schema {
|
||||||
constructor(loader, def) {
|
constructor(loader, def) {
|
||||||
if (!(loader instanceof SchemaLoader)) {
|
if (!(loader instanceof SchemaLoader)) {
|
||||||
|
@ -55,37 +126,40 @@ class Schema {
|
||||||
|
|
||||||
getArrayDefaultValue(def) {
|
getArrayDefaultValue(def) {
|
||||||
let value;
|
let value;
|
||||||
|
const defaultValue = new DefaultValue(null, def.description);
|
||||||
if ('items' in def && typeof def.items === 'object') {
|
if ('items' in def && typeof def.items === 'object') {
|
||||||
const items = Object.assign({}, def.items);
|
const items = Object.assign({}, def.items);
|
||||||
delete items.oneOf;
|
delete items.oneOf;
|
||||||
value = this.getDefaultValue(items);
|
value = this.getDefaultValue(items);
|
||||||
}
|
}
|
||||||
if ('oneOf' in def.items && Array.isArray(def.items.oneOf)) {
|
if ('oneOf' in def.items && Array.isArray(def.items.oneOf)) {
|
||||||
value = def.items.oneOf.map(one => {
|
defaultValue.value = def.items.oneOf.map(one => {
|
||||||
if (!value) {
|
if (!(value instanceof DefaultValue)) {
|
||||||
return this.getDefaultValue(one);
|
return this.getDefaultValue(one);
|
||||||
}
|
}
|
||||||
return new DefaultValue(value.value, value.description)
|
return value.clone().merge(this.getDefaultValue(one));
|
||||||
.merge(this.getDefaultValue(one));
|
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
if (!Array.isArray(value)) {
|
if (!Array.isArray(value)) {
|
||||||
value = [value];
|
value = [value];
|
||||||
}
|
}
|
||||||
return new DefaultValue(value, def.description);
|
defaultValue.value = value;
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
getObjectDefaultValue(def) {
|
getObjectDefaultValue(def) {
|
||||||
let value = {};
|
const value = {};
|
||||||
if ('properties' in def && typeof def.properties === 'object') {
|
if ('properties' in def && typeof def.properties === 'object') {
|
||||||
for (let property in def.properties) {
|
for (const property in def.properties) {
|
||||||
value[property] = this.getDefaultValue(def.properties[property]);
|
value[property] = this.getDefaultValue(def.properties[property]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const defaultValue = new DefaultValue(value, def.description);
|
||||||
if ('oneOf' in def && Array.isArray(def.oneOf) && def.oneOf.length) {
|
if ('oneOf' in def && Array.isArray(def.oneOf) && def.oneOf.length) {
|
||||||
value = deepmerge(value, this.getDefaultValue(def.oneOf[0]));
|
return defaultValue.merge(this.getDefaultValue(def.oneOf[0]));
|
||||||
}
|
}
|
||||||
return new DefaultValue(value, def.description);
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
getTypedDefaultValue(def) {
|
getTypedDefaultValue(def) {
|
||||||
|
@ -96,9 +170,13 @@ class Schema {
|
||||||
} else if (type === 'object') {
|
} else if (type === 'object') {
|
||||||
defaultValue = this.getObjectDefaultValue(def);
|
defaultValue = this.getObjectDefaultValue(def);
|
||||||
} else if (type in PRIMITIVE_DEFAULTS) {
|
} else if (type in PRIMITIVE_DEFAULTS) {
|
||||||
defaultValue = new DefaultValue(PRIMITIVE_DEFAULTS[type], def.description);
|
if ('nullable' in def && def.nullable) {
|
||||||
|
defaultValue = new DefaultValue(null, def.description);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Cannot get default value for type ${type}`)
|
defaultValue = new DefaultValue(PRIMITIVE_DEFAULTS[type], def.description);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`Cannot get default value for type ${type}`);
|
||||||
}
|
}
|
||||||
// referred default value always get overwritten by its parent default value
|
// referred default value always get overwritten by its parent default value
|
||||||
if ('$ref' in def && def.$ref) {
|
if ('$ref' in def && def.$ref) {
|
||||||
|
@ -120,10 +198,10 @@ class Schema {
|
||||||
def = this.def;
|
def = this.def;
|
||||||
}
|
}
|
||||||
if ('const' in def) {
|
if ('const' in def) {
|
||||||
return new DefaultValue(def['const'], def.description);
|
return new DefaultValue(def.const, def.description);
|
||||||
}
|
}
|
||||||
if ('default' in def) {
|
if ('default' in def) {
|
||||||
return new DefaultValue(def['default'], def.description);
|
return new DefaultValue(def.default, def.description);
|
||||||
}
|
}
|
||||||
if ('examples' in def && Array.isArray(def.examples) && def.examples.length) {
|
if ('examples' in def && Array.isArray(def.examples) && def.examples.length) {
|
||||||
return new DefaultValue(def.examples[0], def.description);
|
return new DefaultValue(def.examples[0], def.description);
|
||||||
|
@ -141,7 +219,7 @@ class Schema {
|
||||||
class SchemaLoader {
|
class SchemaLoader {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.schemas = {};
|
this.schemas = {};
|
||||||
this.ajv = new Ajv();
|
this.ajv = new Ajv({ nullable: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
getSchema($id) {
|
getSchema($id) {
|
||||||
|
@ -153,11 +231,11 @@ class SchemaLoader {
|
||||||
throw new Error('The schema definition does not have an $id field');
|
throw new Error('The schema definition does not have an $id field');
|
||||||
}
|
}
|
||||||
this.ajv.addSchema(def);
|
this.ajv.addSchema(def);
|
||||||
this.schemas[def['$id']] = new Schema(this, def);
|
this.schemas[def.$id] = new Schema(this, def);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeSchema($id) {
|
removeSchema($id) {
|
||||||
this.ajv.removeSchema(def);
|
this.ajv.removeSchema($id);
|
||||||
delete this.schemas[$id];
|
delete this.schemas[$id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +282,7 @@ SchemaLoader.load = (rootSchemaDef, resolveDirs = []) => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (typeof def !== 'object' || def['$id'] !== $ref) {
|
if (typeof def !== 'object' || def.$id !== $ref) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
loader.addSchema(def);
|
loader.addSchema(def);
|
||||||
|
@ -217,6 +295,10 @@ SchemaLoader.load = (rootSchemaDef, resolveDirs = []) => {
|
||||||
|
|
||||||
traverseObj(rootSchemaDef, '$ref', handler);
|
traverseObj(rootSchemaDef, '$ref', handler);
|
||||||
return loader;
|
return loader;
|
||||||
}
|
};
|
||||||
|
|
||||||
module.exports = SchemaLoader;
|
module.exports = {
|
||||||
|
Schema,
|
||||||
|
SchemaLoader,
|
||||||
|
DefaultValue
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
const yaml = require('js-yaml');
|
||||||
|
const YamlType = require('js-yaml/lib/js-yaml/type');
|
||||||
|
const YamlSchema = require('js-yaml/lib/js-yaml/schema');
|
||||||
|
|
||||||
|
// output null as empty in yaml
|
||||||
|
const YAML_SCHEMA = new YamlSchema({
|
||||||
|
include: [
|
||||||
|
require('js-yaml/lib/js-yaml/schema/default_full')
|
||||||
|
],
|
||||||
|
implicit: [
|
||||||
|
new YamlType('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: () => ''
|
||||||
|
},
|
||||||
|
defaultStyle: 'empty'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
parse(str) {
|
||||||
|
return yaml.safeLoad(str);
|
||||||
|
},
|
||||||
|
|
||||||
|
stringify(object) {
|
||||||
|
return yaml.safeDump(object, {
|
||||||
|
indent: 4,
|
||||||
|
lineWidth: 1024,
|
||||||
|
schema: YAML_SCHEMA
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -27,6 +27,7 @@
|
||||||
"hexo-log": "^1.0.0",
|
"hexo-log": "^1.0.0",
|
||||||
"hexo-pagination": "^1.0.0",
|
"hexo-pagination": "^1.0.0",
|
||||||
"hexo-renderer-inferno": "^0.1.1",
|
"hexo-renderer-inferno": "^0.1.1",
|
||||||
|
"hexo-renderer-stylus": "^1.1.0",
|
||||||
"hexo-util": "^1.8.0",
|
"hexo-util": "^1.8.0",
|
||||||
"inferno": "^7.3.3",
|
"inferno": "^7.3.3",
|
||||||
"inferno-create-element": "^7.3.3",
|
"inferno-create-element": "^7.3.3",
|
||||||
|
|
127
scripts/index.js
127
scripts/index.js
|
@ -1,7 +1,119 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const util = require('util');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const logger = require('hexo-log')();
|
||||||
|
const packageInfo = require('../package.json');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print welcome message
|
||||||
|
*/
|
||||||
|
logger.info(`=======================================
|
||||||
|
██╗ ██████╗ █████╗ ██████╗ ██╗ ██╗███████╗
|
||||||
|
██║██╔════╝██╔══██╗██╔══██╗██║ ██║██╔════╝
|
||||||
|
██║██║ ███████║██████╔╝██║ ██║███████╗
|
||||||
|
██║██║ ██╔══██║██╔══██╗██║ ██║╚════██║
|
||||||
|
██║╚██████╗██║ ██║██║ ██║╚██████╔╝███████║
|
||||||
|
╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝
|
||||||
|
=============================================`);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if all dependencies are installed
|
||||||
|
*/
|
||||||
|
// FIXME: will not check against package version
|
||||||
|
function checkDependency(name) {
|
||||||
|
try {
|
||||||
|
require.resolve(name);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(`Package ${name} is not installed.`);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('=== Checking package dependencies ===');
|
||||||
|
const missingDeps = Object.keys(packageInfo.peerDependencies)
|
||||||
|
.map(checkDependency)
|
||||||
|
.some(installed => !installed);
|
||||||
|
if (missingDeps) {
|
||||||
|
logger.error('Please install the missing dependencies from the root directory of your Hexo site.');
|
||||||
|
/* eslint no-process-exit: "off" */
|
||||||
|
process.exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration file checking and migration
|
||||||
|
*/
|
||||||
|
if (!process.argv.includes('--icarus-dont-check-config')) {
|
||||||
|
const SCHEMA_ROOT = path.join(hexo.theme_dir, 'include/schema/');
|
||||||
|
const CONFIG_PATH = path.join(hexo.theme_dir, '_config.yml');
|
||||||
|
|
||||||
|
const yaml = require('../include/util/yaml');
|
||||||
|
const { SchemaLoader } = require('../include/util/schema');
|
||||||
|
const loader = SchemaLoader.load(require(path.join(SCHEMA_ROOT, 'config.json')), SCHEMA_ROOT);
|
||||||
|
const schema = loader.getSchema('/config.json');
|
||||||
|
logger.info('=== Checking the configuration file ===');
|
||||||
|
|
||||||
|
// Generate config file if not exist
|
||||||
|
if (!process.argv.includes('--icarus-dont-generate-config')) {
|
||||||
|
if (!fs.existsSync(CONFIG_PATH)) {
|
||||||
|
logger.warn(`${CONFIG_PATH} is not found. We are creating one for you...`);
|
||||||
|
logger.info('You may add \'--icarus-dont-generate-config\' to prevent creating the configuration file.');
|
||||||
|
const defaultValue = schema.getDefaultValue();
|
||||||
|
fs.writeFileSync(CONFIG_PATH, defaultValue.toYaml());
|
||||||
|
logger.info(`${CONFIG_PATH} created successfully.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cfgStr = fs.readFileSync(CONFIG_PATH);
|
||||||
|
let cfg = yaml.parse(cfgStr);
|
||||||
|
// Check config version
|
||||||
|
if (!process.argv.includes('--icarus-dont-upgrade-config')) {
|
||||||
|
const migrator = new(require('../include/util/migrate'))(path.join(hexo.theme_dir, 'include/migration'));
|
||||||
|
// Upgrade config
|
||||||
|
if (migrator.isOudated(cfg.version)) {
|
||||||
|
logger.info(`Your configuration file is outdated (${cfg.version} < ${migrator.getLatestVersion()}). `
|
||||||
|
+ 'Trying to upgrade it...');
|
||||||
|
// Backup old config
|
||||||
|
const hash = crypto.createHash('sha256').update(cfgStr).digest('hex');
|
||||||
|
const backupPath = CONFIG_PATH + '.' + hash.substring(0, 16);
|
||||||
|
fs.writeFileSync(backupPath, cfgStr);
|
||||||
|
logger.info(`Current configurations are written up to ${backupPath}`);
|
||||||
|
// Migrate config
|
||||||
|
cfg = migrator.migrate(cfg);
|
||||||
|
// Save config
|
||||||
|
fs.writeFileSync(CONFIG_PATH, yaml.stringify(cfg));
|
||||||
|
logger.info(`${CONFIG_PATH} upgraded successfully.`);
|
||||||
|
const defaultValue = schema.getDefaultValue();
|
||||||
|
fs.writeFileSync(CONFIG_PATH + '.example', defaultValue.toYaml());
|
||||||
|
logger.info(`We also created an example at ${CONFIG_PATH + '.example'} for your reference.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check config file against schemas
|
||||||
|
const result = schema.validate(cfg);
|
||||||
|
if (result !== true) {
|
||||||
|
logger.warn('Configuration file failed one or more checks.');
|
||||||
|
logger.warn('Icarus may still run, but you will encounter excepted results.');
|
||||||
|
logger.warn('Here is some information for you to correct the configuration file.');
|
||||||
|
logger.warn(util.inspect(result));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(e);
|
||||||
|
logger.error(`Failed to load the configuration file ${CONFIG_PATH}.`);
|
||||||
|
logger.error('Please add \'--icarus-dont-check-config\' to your Hexo command if you');
|
||||||
|
logger.error('wish to skip the config file checking.');
|
||||||
|
process.exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register Hexo extensions
|
||||||
|
*/
|
||||||
|
logger.info('=== Patching Hexo ===');
|
||||||
/* global hexo */
|
/* global hexo */
|
||||||
require('../include/task/welcome');
|
|
||||||
require('../include/task/dependencies');
|
|
||||||
// require('../include/task/check_config');
|
|
||||||
require('../include/generator/categories')(hexo);
|
require('../include/generator/categories')(hexo);
|
||||||
require('../include/generator/category')(hexo);
|
require('../include/generator/category')(hexo);
|
||||||
require('../include/generator/tags')(hexo);
|
require('../include/generator/tags')(hexo);
|
||||||
|
@ -10,7 +122,9 @@ require('../include/filter/locals')(hexo);
|
||||||
require('../include/helper/cdn')(hexo);
|
require('../include/helper/cdn')(hexo);
|
||||||
require('../include/helper/page')(hexo);
|
require('../include/helper/page')(hexo);
|
||||||
|
|
||||||
// Fix large blog rendering OOM
|
/**
|
||||||
|
* Remove Hexo filters that could cause OOM
|
||||||
|
*/
|
||||||
const hooks = [
|
const hooks = [
|
||||||
'after_render:html',
|
'after_render:html',
|
||||||
'after_post_render'
|
'after_post_render'
|
||||||
|
@ -24,8 +138,3 @@ hooks.forEach(hook => {
|
||||||
.filter(filter => filters.includes(filter.name))
|
.filter(filter => filters.includes(filter.name))
|
||||||
.forEach(filter => hexo.extend.filter.unregister(hook, filter));
|
.forEach(filter => hexo.extend.filter.unregister(hook, filter));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Debug helper
|
|
||||||
hexo.extend.helper.register('console', function() {
|
|
||||||
console.log(arguments);
|
|
||||||
});
|
|
||||||
|
|
Loading…
Reference in New Issue