Compare commits

...

20 Commits

Author SHA1 Message Date
Samy Pessé ebe845d46c Bump version to 3.0.3 2016-05-27 12:16:56 +02:00
Samy Pessé edee8d2fc5 Update gitbook-theme-default@1.0.3 2016-05-27 12:07:35 +02:00
Samy Pessé 5f3de0876f Add header 'X-Current-Location' to serve and fix handling of directories 2016-05-27 12:01:28 +02:00
Samy Pessé 2ceab04d01 Increase timeout for tests of plugin installation 2016-05-27 11:40:05 +02:00
Samy Pessé de37de44e8 Add tests for installation of all plugins 2016-05-27 11:18:12 +02:00
Samy Pessé ccb87d5a13 Add unit tests for installPlugin 2016-05-27 11:10:39 +02:00
Samy Pessé e2e0ca3f05 Add test for plugin version resolution 2016-05-27 10:54:56 +02:00
Samy Pessé b33a79704b Switch plugin installation and resolveVersion to use PluginDependency instead of Plugin 2016-05-27 09:38:23 +02:00
Samy Pessé 6def174b84 Merge pull request #1334 from ryanswanson/master
Fixed two issues for handling git URLs for plugins
2016-05-27 09:27:11 +02:00
Samy Pessé 346a18b444 Merge pull request #1335 from GitbookIO/fix/1333
Fix #1333: fix invalid URLs for glossary annotations
2016-05-26 22:30:06 +02:00
Samy Pesse b32685c698 Add test for custom glossary file 2016-05-26 22:01:40 +02:00
Samy Pesse 09b3618da8 Annotate glossary terms before resolving links 2016-05-26 21:38:27 +02:00
Samy Pesse 901328f247 Complete basic tests for glossary 2016-05-26 21:37:10 +02:00
Samy Pesse b47ebaa9d9 Use real glossary name when annotating 2016-05-26 21:10:06 +02:00
Ryan Swanson 465e8d6c0a Fixed two issues for handling git URLs for plugins that are published to git repositories rather than on npmjs.org.
Fixed issue in pluginDependency to dereference 'name' and 'version' using Immutable Map.get(...) accessors since config values are now wrapped by Immutable.fromJS(...) in config.js > setValue(...).  Added associated unit test.

Fixed issue in resolveVersion where a plugin may be using a git URL rather than a semver for the version portion of the plugin config definition.  In addition, refactored the resolveVersion function into a new module to allow for unit testing.  Added associated unit test.
2016-05-26 14:30:29 -04:00
Samy Pessé ecd86ec877 Method for parsing glossary when not listed in summary 2016-05-26 17:26:11 +02:00
Samy Pessé 5d7864154c Start test for glossary generation 2016-05-26 17:25:49 +02:00
Samy Pessé 7b915428f7 Fix #1316: fix regression for supporting uppercase filenames in structure 2016-05-26 11:07:13 +02:00
Samy Pesse 0bce3517fd Bump version to 3.0.2 2016-05-25 21:50:38 +02:00
Samy Pesse 089815a8fd Fix #1328: fix crash when anchor links 2016-05-25 21:49:58 +02:00
22 changed files with 464 additions and 176 deletions
+9
View File
@@ -2,6 +2,15 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## 3.0.3
- Fix redirection in `gitbook serve` when accessing a folder without trailing slash
- Fix links/annotations for glossary not being correctly resolved
- Fix regression for supporting uppercase filenames in structure files
- Fix `gitbook install` when using Git URLs for plugins
## 3.0.2
- Fix crash for anchor links
## 3.0.1
- Fix regression in link resolution when contain anchor
- Fix `structure.<name>` configuration not supporting filenames with dots
+21 -2
View File
@@ -71,11 +71,18 @@ Server.prototype.start = function(dir, port) {
// Redirect to directory's index.html
function redirect() {
var resultURL = urlTransform(req.url, function(parsed) {
parsed.pathname += '/';
return parsed;
});
res.statusCode = 301;
res.setHeader('Location', req.url + '/');
res.end('Redirecting to ' + req.url + '/');
res.setHeader('Location', resultURL);
res.end('Redirecting to ' + resultURL);
}
res.setHeader('X-Current-Location', req.url);
// Send file
send(req, url.parse(req.url).pathname, {
root: dir
@@ -106,4 +113,16 @@ Server.prototype.start = function(dir, port) {
});
};
/**
urlTransform is a helper function that allows a function to transform
a url string in it's parsed form and returns the new url as a string
@param {String} uri
@param {Function} fn
@return {String}
*/
function urlTransform(uri, fn) {
return url.format(fn(url.parse(uri)));
}
module.exports = Server;
+10
View File
@@ -22,6 +22,16 @@ describe('configSchema', function() {
expect(result.errors.length).toBe(0);
});
it('should accept uppercase in filename', function() {
var result = validate({
structure: {
readme: 'BOOK.adoc'
}
});
expect(result.errors.length).toBe(0);
});
it('should not accept filepath', function() {
var result = validate({
structure: {
+1 -1
View File
@@ -1,4 +1,4 @@
var FILENAME_REGEX = '^[a-z-._\d,\s]+$';
var FILENAME_REGEX = '^[a-zA-Z-._\d,\s]+$';
module.exports = {
'$schema': 'http://json-schema.org/schema#',
+32 -14
View File
@@ -42,21 +42,39 @@ describe('PluginDependency', function() {
expect(plugin.getVersion()).toBe('git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
});
});
});
describe('listToArray', function() {
var list = PluginDependency.listToArray(Immutable.List([
PluginDependency.createFromString('hello@1.0.0'),
PluginDependency.createFromString('noversion'),
PluginDependency.createFromString('-disabled')
]));
describe('listToArray', function() {
it('must create an array from a list of plugin dependencies', function() {
var list = PluginDependency.listToArray(Immutable.List([
PluginDependency.createFromString('hello@1.0.0'),
PluginDependency.createFromString('noversion'),
PluginDependency.createFromString('-disabled')
]));
expect(list).toEqual([
'hello@1.0.0',
'noversion',
'-disabled'
]);
expect(list).toEqual([
'hello@1.0.0',
'noversion',
'-disabled'
]);
});
});
describe('listFromArray', function() {
it('must create an array from a list of plugin dependencies', function() {
var arr = Immutable.fromJS([
'hello@1.0.0',
{
'name': 'plugin-ga',
'version': 'git+ssh://samy@github.com/GitbookIO/plugin-ga.git'
}
]);
var list = PluginDependency.listFromArray(arr);
expect(list.first().getName()).toBe('hello');
expect(list.first().getVersion()).toBe('1.0.0');
expect(list.last().getName()).toBe('plugin-ga');
expect(list.last().getVersion()).toBe('git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
});
});
});
});
+4 -11
View File
@@ -1,9 +1,10 @@
var Immutable = require('immutable');
var TemplateBlock = require('./templateBlock');
var PREFIX = require('../constants/pluginPrefix');
var DEFAULT_VERSION = '*';
var PluginDependency = require('./pluginDependency');
var Plugin = Immutable.Record({
name: String(),
@@ -53,7 +54,7 @@ Plugin.prototype.getDepth = function() {
@return {String}
*/
Plugin.prototype.getNpmID = function() {
return Plugin.nameToNpmID(this.getName());
return PluginDependency.nameToNpmID(this.getName());
};
/**
@@ -152,14 +153,6 @@ Plugin.createFromDep = function(dep) {
});
};
/**
Return NPM id for a plugin name
@param {String}
@return {String}
*/
Plugin.nameToNpmID = function(s) {
return PREFIX + s;
};
Plugin.nameToNpmID = PluginDependency.nameToNpmID;
module.exports = Plugin;
+32 -2
View File
@@ -1,6 +1,8 @@
var is = require('is');
var semver = require('semver');
var Immutable = require('immutable');
var PREFIX = require('../constants/pluginPrefix');
var DEFAULT_VERSION = '*';
/*
@@ -29,6 +31,24 @@ PluginDependency.prototype.isEnabled = function() {
return this.get('enabled');
};
/**
Return NPM ID for the dependency
@return {String}
*/
PluginDependency.prototype.getNpmID = function() {
return PluginDependency.nameToNpmID(this.getName());
};
/**
Is the plugin using a git dependency
@return {Boolean}
*/
PluginDependency.prototype.isGitDependency = function() {
return !semver.validRange(this.getVersion());
};
/**
Create a plugin with a name and a plugin
@@ -95,8 +115,8 @@ PluginDependency.listFromArray = function(arr) {
return PluginDependency.createFromString(entry);
} else {
return PluginDependency({
name: entry.name,
version: entry.version
name: entry.get('name'),
version: entry.get('version')
});
}
})
@@ -130,4 +150,14 @@ PluginDependency.listToArray = function(list) {
.toJS();
};
/**
Return NPM id for a plugin name
@param {String}
@return {String}
*/
PluginDependency.nameToNpmID = function(s) {
return PREFIX + s;
};
module.exports = PluginDependency;
+49
View File
@@ -1,3 +1,4 @@
var fs = require('fs');
var generateMock = require('../generateMock');
var WebsiteGenerator = require('../website');
@@ -12,6 +13,54 @@ describe('WebsiteGenerator', function() {
});
});
describe('Glossary', function() {
var folder;
before(function() {
return generateMock(WebsiteGenerator, {
'README.md': 'Hello World',
'SUMMARY.md': '* [Deep](folder/page.md)',
'folder': {
'page.md': 'Hello World'
},
'GLOSSARY.md': '# Glossary\n\n## Hello\n\nHello World'
})
.then(function(_folder) {
folder = _folder;
});
});
it('should generate a GLOSSARY.html', function() {
expect(folder).toHaveFile('GLOSSARY.html');
});
it('should correctly resolve glossary links in README', function() {
var html = fs.readFileSync(folder + '/index.html', 'utf8');
expect(html).toHaveDOMElement('.page-inner a[href="GLOSSARY.html#hello"]');
});
it('should correctly resolve glossary links in directory', function() {
var html = fs.readFileSync(folder + '/folder/page.html', 'utf8');
expect(html).toHaveDOMElement('.page-inner a[href="../GLOSSARY.html#hello"]');
});
it('should accept a custom glossary file', function() {
return generateMock(WebsiteGenerator, {
'README.md': 'Hello World',
'book.json': '{ "structure": { "glossary": "custom.md" } }',
'custom.md': '# Glossary\n\n## Hello\n\nHello World'
})
.then(function(folder) {
expect(folder).toHaveFile('custom.html');
expect(folder).toNotHaveFile('GLOSSARY.html');
var html = fs.readFileSync(folder + '/index.html', 'utf8');
expect(html).toHaveDOMElement('.page-inner a[href="custom.html#hello"]');
});
});
});
it('should copy asset files', function() {
return generateMock(WebsiteGenerator, {
'README.md': 'Hello World',
+12 -7
View File
@@ -4,6 +4,7 @@ var Api = require('../api');
var Plugins = require('../plugins');
var Promise = require('../utils/promise');
var defaultBlocks = require('../constants/defaultBlocks');
var fileToOutput = require('./helper/fileToOutput');
var CODEBLOCK = 'code';
@@ -17,9 +18,13 @@ function getModifiers(output, page) {
var book = output.getBook();
var plugins = output.getPlugins();
var glossary = book.getGlossary();
var entries = glossary.getEntries();
var file = page.getFile();
// Glossary entries
var entries = glossary.getEntries();
var glossaryFile = glossary.getFile();
var glossaryFilename = fileToOutput(output, glossaryFile.getPath());
// Current file path
var currentFilePath = file.getPath();
@@ -34,18 +39,18 @@ function getModifiers(output, page) {
// Normalize IDs on headings
Modifiers.addHeadingId,
// Annotate text with glossary entries
Modifiers.annotateText.bind(null, entries, glossaryFilename),
// Resolve images
Modifiers.resolveImages.bind(null, currentFilePath),
// Resolve links (.md -> .html)
Modifiers.resolveLinks.bind(null,
currentFilePath,
resolveFileToURL.bind(null, output)
),
// Resolve images
Modifiers.resolveImages.bind(null, currentFilePath),
// Annotate text with glossary entries
Modifiers.annotateText.bind(null, entries),
// Highlight code blocks using "code" block
Modifiers.highlightCode.bind(null, function(lang, source) {
return Promise(code.applyBlock({
@@ -12,7 +12,7 @@ describe('annotateText', function() {
it('should annotate text', function() {
var $ = cheerio.load('<p>This is a word, and multiple words</p>');
annotateText(entries, $);
annotateText(entries, 'GLOSSARY.md', $);
var links = $('a');
expect(links.length).toBe(2);
@@ -31,14 +31,14 @@ describe('annotateText', function() {
it('should not annotate scripts', function() {
var $ = cheerio.load('<script>This is a word, and multiple words</script>');
annotateText(entries, $);
annotateText(entries, 'GLOSSARY.md', $);
expect($('a').length).toBe(0);
});
it('should not annotate when has class "no-glossary"', function() {
var $ = cheerio.load('<p class="no-glossary">This is a word, and multiple words</p>');
annotateText(entries, $);
annotateText(entries, 'GLOSSARY.md', $);
expect($('a').length).toBe(0);
});
});
+12 -2
View File
@@ -40,9 +40,8 @@ describe('resolveLinks', function() {
});
describe('Anchor', function() {
var TEST = '<p>This is a <a href="test/cool.md#an-anchor"></a></p>';
it('should prevent anchors in resolution', function() {
var TEST = '<p>This is a <a href="test/cool.md#an-anchor"></a></p>';
var $ = cheerio.load(TEST);
return resolveLinks('hello.md', resolveFileCustom, $)
@@ -51,6 +50,17 @@ describe('resolveLinks', function() {
expect(link.attr('href')).toBe('test/cool.html#an-anchor');
});
});
it('should ignore pure anchor links', function() {
var TEST = '<p>This is a <a href="#an-anchor"></a></p>';
var $ = cheerio.load(TEST);
return resolveLinks('hello.md', resolveFileCustom, $)
.then(function() {
var link = $('a');
expect(link.attr('href')).toBe('#an-anchor');
});
});
});
describe('Custom Resolver', function() {
+3 -2
View File
@@ -62,9 +62,10 @@ function replaceText($, el, search, replace, text_only ) {
Annotate text using a list of GlossaryEntry
@param {List<GlossaryEntry>}
@param {String} glossaryFilePath
@param {HTMLDom} $
*/
function annotateText(entries, $) {
function annotateText(entries, glossaryFilePath, $) {
entries.forEach(function(entry) {
var entryId = entry.getID();
var name = entry.getName();
@@ -81,7 +82,7 @@ function annotateText(entries, $) {
) return;
replaceText($, this, searchRegex, function(match) {
return '<a href="/GLOSSARY.md#' + entryId + '" '
return '<a href="/' + glossaryFilePath + '#' + entryId + '" '
+ 'class="glossary-term" title="' + escape(description) + '">'
+ match
+ '</a>';
+9 -7
View File
@@ -30,16 +30,18 @@ function resolveLinks(currentFile, resolveFile, $) {
// Split anchor
var parsed = url.parse(href);
href = parsed.pathname;
href = parsed.pathname || '';
// Calcul absolute path for this
href = LocationUtils.toAbsolute(href, currentDirectory, '.');
if (href) {
// Calcul absolute path for this
href = LocationUtils.toAbsolute(href, currentDirectory, '.');
// Resolve file
href = resolveFile(href);
// Resolve file
href = resolveFile(href);
// Convert back to relative
href = LocationUtils.relative(currentDirectory, href);
// Convert back to relative
href = LocationUtils.relative(currentDirectory, href);
}
// Add back anchor
href = href + (parsed.hash || '');
+41 -10
View File
@@ -5,6 +5,25 @@ var Page = require('../models/page');
var walkSummary = require('./walkSummary');
var parsePage = require('./parsePage');
/**
Parse a page from a path
@param {Book} book
@param {String} filePath
@return {Page}
*/
function parseFilePage(book, filePath) {
var fs = book.getContentFS();
return fs.statFile(filePath)
.then(function(file) {
var page = Page.createForFile(file);
return parsePage(book, page);
});
}
/**
Parse all pages from a book as an OrderedMap
@@ -12,10 +31,11 @@ var parsePage = require('./parsePage');
@return {Promise<OrderedMap<Page>>}
*/
function parsePagesList(book) {
var fs = book.getContentFS();
var summary = book.getSummary();
var glossary = book.getGlossary();
var map = Immutable.OrderedMap();
// Parse pages from summary
return timing.measure(
'parse.listPages',
walkSummary(summary, function(article) {
@@ -26,21 +46,32 @@ function parsePagesList(book) {
// Is the page ignored?
if (book.isContentFileIgnored(filepath)) return;
return fs.statFile(filepath)
.then(function(file) {
var page = Page.createForFile(file);
return parsePage(book, page);
})
return parseFilePage(book, filepath)
.then(function(page) {
map = map.set(filepath, page);
}, function() {
// file doesn't exist
});
})
.then(function() {
return map;
})
);
)
// Parse glossary
.then(function() {
var file = glossary.getFile();
if (!file.exists()) {
return;
}
return parseFilePage(book, file.getPath())
.then(function(page) {
map = map.set(file.getPath(), page);
});
})
.then(function() {
return map;
});
}
+29
View File
@@ -0,0 +1,29 @@
var path = require('path');
var PluginDependency = require('../../models/pluginDependency');
var Book = require('../../models/book');
var NodeFS = require('../../fs/node');
var installPlugin = require('../installPlugin');
var Parse = require('../../parse');
describe('installPlugin', function() {
var book;
this.timeout(30000);
before(function() {
var fs = NodeFS(path.resolve(__dirname, '../../../'));
var baseBook = Book.createForFS(fs);
return Parse.parseConfig(baseBook)
.then(function(_book) {
book = _book;
});
});
it('must install a plugin from NPM', function() {
var dep = PluginDependency.createFromString('ga');
return installPlugin(book, dep);
});
});
+30
View File
@@ -0,0 +1,30 @@
var path = require('path');
var Book = require('../../models/book');
var NodeFS = require('../../fs/node');
var installPlugins = require('../installPlugins');
var Parse = require('../../parse');
describe('installPlugins', function() {
var book;
this.timeout(30000);
before(function() {
var fs = NodeFS(path.resolve(__dirname, '../../../'));
var baseBook = Book.createForFS(fs);
return Parse.parseConfig(baseBook)
.then(function(_book) {
book = _book;
});
});
it('must install all plugins from NPM', function() {
return installPlugins(book)
.then(function(n) {
expect(n).toBe(2);
});
});
});
+22
View File
@@ -0,0 +1,22 @@
var PluginDependency = require('../../models/pluginDependency');
var resolveVersion = require('../resolveVersion');
describe('resolveVersion', function() {
it('must skip resolving and return non-semver versions', function() {
var plugin = PluginDependency.createFromString('ga@git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
return resolveVersion(plugin)
.then(function(version) {
expect(version).toBe('git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
});
});
it('must resolve a normal plugin dependency', function() {
var plugin = PluginDependency.createFromString('ga@>0.9.0 < 1.0.1');
return resolveVersion(plugin)
.then(function(version) {
expect(version).toBe('1.0.0');
});
});
});
+47
View File
@@ -0,0 +1,47 @@
var npmi = require('npmi');
var Promise = require('../utils/promise');
var resolveVersion = require('./resolveVersion');
/**
Install a plugin for a book
@param {Book}
@param {PluginDependency}
@return {Promise}
*/
function installPlugin(book, plugin) {
var logger = book.getLogger();
var installFolder = book.getRoot();
var name = plugin.getName();
var requirement = plugin.getVersion();
logger.info.ln('');
logger.info.ln('installing plugin "' + name + '"');
// Find a version to install
return resolveVersion(plugin)
.then(function(version) {
if (!version) {
throw new Error('Found no satisfactory version for plugin "' + name + '" with requirement "' + requirement + '"');
}
logger.info.ln('install plugin "' + name +'" (' + requirement + ') from NPM with version', version);
return Promise.nfcall(npmi, {
'name': plugin.getNpmID(),
'version': version,
'path': installFolder,
'npmLoad': {
'loglevel': 'silent',
'loaded': true,
'prefix': installFolder
}
});
})
.then(function() {
logger.info.ok('plugin "' + name + '" installed with success');
});
}
module.exports = installPlugin;
+12 -113
View File
@@ -1,134 +1,32 @@
var npm = require('npm');
var npmi = require('npmi');
var semver = require('semver');
var Immutable = require('immutable');
var pkg = require('../../package.json');
var DEFAULT_PLUGINS = require('../constants/defaultPlugins');
var Promise = require('../utils/promise');
var Plugin = require('../models/plugin');
var gitbook = require('../gitbook');
var listForBook = require('./listForBook');
var npmIsReady;
/**
Initialize and prepare NPM
@return {Promise}
*/
function initNPM() {
if (npmIsReady) return npmIsReady;
npmIsReady = Promise.nfcall(npm.load, {
silent: true,
loglevel: 'silent'
});
return npmIsReady;
}
/**
Resolve a plugin to a version
@param {Plugin}
@return {Promise<String>}
*/
function resolveVersion(plugin) {
var npmId = Plugin.nameToNpmID(plugin.getName());
var requiredVersion = plugin.getVersion();
return initNPM()
.then(function() {
return Promise.nfcall(npm.commands.view, [npmId + '@' + requiredVersion, 'engines'], true);
})
.then(function(versions) {
versions = Immutable.Map(versions).entrySeq();
var result = versions
.map(function(entry) {
return {
version: entry[0],
gitbook: (entry[1].engines || {}).gitbook
};
})
.filter(function(v) {
return v.gitbook && gitbook.satisfies(v.gitbook);
})
.sort(function(v1, v2) {
return semver.lt(v1.version, v2.version)? 1 : -1;
})
.get(0);
if (!result) {
return undefined;
} else {
return result.version;
}
});
}
/**
Install a plugin for a book
@param {Book}
@param {Plugin}
@return {Promise}
*/
function installPlugin(book, plugin) {
var logger = book.getLogger();
var installFolder = book.getRoot();
var name = plugin.getName();
var requirement = plugin.getVersion();
logger.info.ln('');
logger.info.ln('installing plugin "' + name + '"');
// Find a version to install
return resolveVersion(plugin)
.then(function(version) {
if (!version) {
throw new Error('Found no satisfactory version for plugin "' + name + '" with requirement "' + requirement + '"');
}
logger.info.ln('install plugin "' + name +'" (' + requirement + ') from NPM with version', version);
return Promise.nfcall(npmi, {
'name': plugin.getNpmID(),
'version': version,
'path': installFolder,
'npmLoad': {
'loglevel': 'silent',
'loaded': true,
'prefix': installFolder
}
});
})
.then(function() {
logger.info.ok('plugin "' + name + '" installed with success');
});
}
var installPlugin = require('./installPlugin');
/**
Install plugin requirements for a book
@param {Book}
@return {Promise}
@return {Promise<Number>}
*/
function installPlugins(book) {
var logger = book.getLogger();
var plugins = listForBook(book);
var config = book.getConfig();
var plugins = config.getPluginDependencies();
// Remove default plugins
// (only if version is same as installed)
plugins = plugins.filterNot(function(plugin) {
return (
DEFAULT_PLUGINS.includes(plugin.getName()) &&
plugin.getVersion() === pkg.dependencies[plugin.getNpmID()]
// Disabled plugin
!plugin.isEnabled() ||
// Or default one installed in GitBook itself
(DEFAULT_PLUGINS.includes(plugin.getName()) &&
plugin.getVersion() === pkg.dependencies[plugin.getNpmID()])
);
});
@@ -141,7 +39,8 @@ function installPlugins(book) {
return Promise.forEach(plugins, function(plugin) {
return installPlugin(book, plugin);
});
})
.thenResolve(plugins.size);
}
module.exports = installPlugins;
+71
View File
@@ -0,0 +1,71 @@
var npm = require('npm');
var semver = require('semver');
var Immutable = require('immutable');
var Promise = require('../utils/promise');
var Plugin = require('../models/plugin');
var gitbook = require('../gitbook');
var npmIsReady;
/**
Initialize and prepare NPM
@return {Promise}
*/
function initNPM() {
if (npmIsReady) return npmIsReady;
npmIsReady = Promise.nfcall(npm.load, {
silent: true,
loglevel: 'silent'
});
return npmIsReady;
}
/**
Resolve a plugin dependency to a version
@param {PluginDependency} plugin
@return {Promise<String>}
*/
function resolveVersion(plugin) {
var npmId = Plugin.nameToNpmID(plugin.getName());
var requiredVersion = plugin.getVersion();
if (plugin.isGitDependency()) {
return Promise.resolve(requiredVersion);
}
return initNPM()
.then(function() {
return Promise.nfcall(npm.commands.view, [npmId + '@' + requiredVersion, 'engines'], true);
})
.then(function(versions) {
versions = Immutable.Map(versions).entrySeq();
var result = versions
.map(function(entry) {
return {
version: entry[0],
gitbook: (entry[1].engines || {}).gitbook
};
})
.filter(function(v) {
return v.gitbook && gitbook.satisfies(v.gitbook);
})
.sort(function(v1, v2) {
return semver.lt(v1.version, v2.version)? 1 : -1;
})
.get(0);
if (!result) {
return undefined;
} else {
return result.version;
}
});
}
module.exports = resolveVersion;
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "gitbook",
"version": "3.0.1",
"version": "3.0.3",
"homepage": "https://www.gitbook.com",
"description": "Library and cmd utility to generate GitBooks",
"main": "lib/index.js",
@@ -29,7 +29,7 @@
"gitbook-plugin-lunr": "1.1.0",
"gitbook-plugin-search": "2.2.1",
"gitbook-plugin-sharing": "1.0.2",
"gitbook-plugin-theme-default": "1.0.2",
"gitbook-plugin-theme-default": "1.0.3",
"github-slugid": "1.0.1",
"graceful-fs": "4.1.4",
"i18n-t": "1.0.0",
+13
View File
@@ -2,6 +2,7 @@ var is = require('is');
var path = require('path');
var fs = require('fs');
var expect = require('expect');
var cheerio = require('cheerio');
expect.extend({
/**
@@ -43,6 +44,18 @@ expect.extend({
'expected to be defined'
);
return this;
},
/**
Check that a dom element exists in HTML
@param {String} selector
*/
toHaveDOMElement: function(selector) {
var $ = cheerio.load(this.actual);
var $el = $(selector);
expect.assert($el.length > 0, 'expected HTML to contains %s', selector);
}
});