docs(): add groups by topic

This commit is contained in:
Andy Joslin
2014-03-14 14:13:38 -06:00
parent e81bd96528
commit 8619d5e8ec
25 changed files with 189 additions and 286 deletions

View File

@@ -23,7 +23,7 @@ module.exports = function(config) {
var versionData = require('./generate-versions')(config);
config.set('versionData', versionData);
config.set('rendering.contentsFolder', 'docs/' + versionData.current.folder);
config.set('rendering.contentsFolder', path.join('docs', versionData.current.folder));
config.set('processing.api-docs', {
outputPath: 'api/${docType}/${name}/index.md',
@@ -62,10 +62,11 @@ module.exports = function(config) {
});
config.append('processing.processors', [
require('./processors/version-data'),
require('./processors/latest-version'),
require('./processors/keywords'),
require('./processors/pages-data'),
require('./processors/index-page'),
require('./processors/version-data')
]);
return config;

View File

@@ -10,61 +10,28 @@ module.exports = function(config) {
var docsBaseFolder = path.resolve(basePath, outputFolder, 'docs');
var versions;
try {
versions = require(docsBaseFolder + '/version-data.json');
} catch(e) {
versions = [];
}
var versions = fs.readdirSync(docsBaseFolder)
.filter(semver.valid)
.sort(semver.rcompare);
var hasNightlyVersion = currentVersion == 'nightly' ||
_.find(versions, {name: 'nightly'});
var nightlyVersion = {
href: '/docs/nightly',
folder: 'nightly',
name: 'nightly'
};
!_.contains(versions, currentVersion) && versions.unshift(currentVersion);
!_.contains(versions, 'nightly') && versions.unshift('nightly');
//Remove nightly from versions before trying to sort things out
//because it's different (the only non-semver version)
_.remove(versions, {name: 'nightly'});
if (currentVersion == 'nightly') {
//do nothing
} else if ( !_.find(versions, {name: currentVersion}) ) {
//If version doesn't exist, add it...
versions.unshift({
href: '/docs/latest',
folder: 'latest',
name: currentVersion
});
versions.forEach(function(version, index) {
//Make sure the other versions aren't still latest,
//if so rename them
if (_.contains(version.href, 'latest') && index > 0) {
version.href = '/docs/'+version.name,
version.folder = version.name;
fs.unlinkSync(docsBaseFolder + '/' + version.name);
fs.renameSync(
docsBaseFolder + '/latest',
docsBaseFolder + '/' + version.name
);
}
});
}
//Add nightly back to front if it exists
if (hasNightlyVersion) {
versions.unshift(nightlyVersion);
}
console.log(versions);
//First semver valid version is latest
var latestVersion = _.find(versions, semver.valid);
versions = versions.map(function(version) {
//Latest version is in docs root
var folder = version == latestVersion ? '' : version;
return {
href: path.join('/docs', folder),
folder: folder,
name: version
};
});
return {
list: versions,
current: _.find(versions, { name: currentVersion }),
latest: _.find(versions, function(v) { return semver.valid(v.name); }) ||
_.first(versions)
latest: _.find(versions, {name: latestVersion}) || _.first(versions)
};
};

View File

@@ -0,0 +1,22 @@
var copy = require('cp-r');
var mkdirp = require('mkdirp');
var path = require('canonical-path');
module.exports = {
name: 'latest-version',
runAfter: ['write-files'],
description: 'Copy the latest version (that was compiled to docs/latest) into docs/versionName',
process: function(docs, config) {
var versionData = config.get('versionData');
var docsBase = path.join(config.get('basePath'), config.get('rendering.outputFolder'), 'docs');
var versionDir = path.join(docsBase, versionData.latest.name);
var latestDir = path.join(docsBase, 'api');
mkdirp(versionDir, function() {
copy(latestDir, path.join(versionDir, 'api'), {
deleteFirst: true
});
});
}
};

View File

@@ -2,130 +2,6 @@ var _ = require('lodash');
var path = require('canonical-path');
var log = require('winston');
var AREA_NAMES = {
api: 'API',
guide: 'Developer Guide',
misc: 'Miscellaneous',
tutorial: 'Tutorial',
error: 'Error Reference'
};
function getNavGroup(pages, area, pageSorter, pageMapper) {
var navItems = _(pages)
// We don't want the child to include the index page as this is already catered for
.omit(function(page) { return page.id === 'index'; })
// Apply the supplied sorting function
.sortBy(pageSorter)
// Apply the supplied mapping function
.map(pageMapper)
.value();
return {
name: area.name,
type: 'group',
href: area.id,
navItems: navItems
};
}
var navGroupMappers = {
api: function(areaPages, area) {
var navGroups = _(areaPages)
.groupBy('module')
.map(function(modulePages, moduleName) {
log.debug('moduleName: ' + moduleName);
var navItems = [];
var modulePath;
_(modulePages)
.groupBy('docType')
.tap(function(docTypes) {
log.debug(_.keys(docTypes));
modulePath = 'api/ionic';
delete docTypes.module;
})
.tap(function(docTypes) {
if ( docTypes.input ) {
docTypes.directive = docTypes.directive || [];
// Combine input docTypes into directive docTypes
docTypes.directive = docTypes.directive.concat(docTypes.input);
delete docTypes.input;
}
})
.forEach(function(sectionPages, sectionName) {
sectionPages = _.sortBy(sectionPages, 'name');
if ( sectionPages.length > 0 ) {
// Push a navItem for this section
navItems.push({
name: sectionName,
type: 'section',
href: path.dirname(sectionPages[0].path)
});
// Push the rest of the sectionPages for this section
_.forEach(sectionPages, function(sectionPage) {
navItems.push({
name: sectionPage.name,
href: sectionPage.path,
type: sectionPage.docType
});
});
}
});
return {
name: moduleName,
href: modulePath,
type: 'group',
navItems: navItems
};
})
.value();
return navGroups;
},
tutorial: function(pages, area) {
return [getNavGroup(pages, area, 'step', function(page) {
return {
name: page.name,
step: page.step,
href: page.path,
type: 'tutorial'
};
})];
},
error: function(pages, area) {
return [getNavGroup(pages, area, 'path', function(page) {
return {
name: page.name,
href: page.path,
type: page.docType === 'errorNamespace' ? 'section' : 'error'
};
})];
},
pages: function(pages, area) {
return [getNavGroup(pages, area, 'path', function(page) {
return {
name: page.name,
href: page.path,
type: 'page'
};
})];
}
};
var outputFolder;
var processorConfig;
var currentVersion;
@@ -142,53 +18,61 @@ module.exports = {
currentVersion = config.get('currentVersion');
},
process: function(docs) {
_(docs)
.filter(function(doc) { return doc.area === 'api'; })
.filter(function(doc) { return doc.docType === 'module'; })
.map(function(doc) { return _.pick(doc, ['id', 'module', 'docType', 'area']); })
.tap(function(docs) {
log.debug(docs);
});
// We are only interested in docs that are in a area and not landing pages
var navPages = _.filter(docs, function(page) {
return page.area &&
page.docType != 'componentGroup';
});
// Generate an object collection of pages that is grouped by area e.g.
// - area "api"
// - group "ng"
// - section "directive"
// - ngApp
// - ngBind
// - section "global"
// - angular.element
// - angular.bootstrap
// - section "service"
// - $compile
// - group "ngRoute"
// - section "directive"
// - ngView
// - section "service"
// - $route
// Generate an object collection of pages that is grouped by section e.g.
// - section "directive"
// - group "Tab Bar"
// - ion-tabs
// - ion-tab
// - group ""
// - ion-toggle
// - ion-checkbox
// - ...
//
var areas = {};
_(navPages)
.groupBy('area')
.forEach(function(pages, areaId) {
var area = {
id: areaId,
name: AREA_NAMES[areaId]
var groups = _(docs)
.filter(function(doc) { return doc.area === 'api'; })
.filter(function(doc) { return doc.module === 'ionic'; })
.filter(function(doc) { return doc.docType !== 'componentGroup'; })
.groupBy(function(doc) { if (!doc.group) doc.group = 'Other'; return doc.group; })
.map(function(pages, groupName) {
var sections = _(pages)
.groupBy('docType')
.map(function(pages, docType) {
return {
name: docType,
pages: _(pages)
.sortBy(function(doc) {
return doc.groupMainItem;
})
.map(function(doc) {
return {
href: doc.path,
name: doc.name,
docType: doc.docType,
type: doc.docType,
};
})
.filter(function(doc) {
return !!doc.name;
})
.value()
};
})
.sortBy(function(section) {
//Directives always first
return section.name != 'directive';
})
.value();
return {
name: groupName,
sections: sections
};
areas[areaId] = area;
var navGroupMapper = navGroupMappers[area.id] || navGroupMappers['pages'];
area.navGroups = navGroupMapper(pages, area);
});
})
.sortBy(function(group) {
//Sort by groups with most items last
return _.values(group.sections).length;
})
.value();
_.forEach(docs, function(doc) {
if ( !doc.path ) {
@@ -196,25 +80,13 @@ module.exports = {
}
});
// Extract a list of basic page information for mapping paths to paritals and for client side searching
var pages = _(docs)
.map(function(doc) {
var page = _.pick(doc, [
'docType', 'id', 'name', 'area', 'outputPath', 'path', 'searchTerms'
]);
return page;
})
.indexBy('path')
.value();
var docData = {
docType: 'pages-data',
id: 'pages-data',
template: processorConfig.template || 'pages-data.template.js',
outputPath: processorConfig.outputPath || 'js/pages-data.js',
areas: areas,
pages: pages
groups: groups
};
docs.push(docData);

View File

@@ -1,13 +1,8 @@
module.exports = {
name: 'version-data',
runBefore: ['pages-data'],
process: function(docs, config, extraData) {
runBefore: ['api-docs'],
description: 'Expose version data to templates',
process: function(extraData, config) {
extraData.version = config.get('versionData');
docs.push({
docType: 'version-data',
id: 'version-data',
template: 'version-data.template.json',
outputPath: 'docs/version-data.json'
});
}
};

View File

@@ -1,19 +1,19 @@
function linkify(type, id) {
return '{@link ionic.' + type + ':' + id.trim() + '}';
}
module.exports = [
{
name: 'controller',
transformFn: function(doc, tag) {
return linkify('controller', tag.description);
return '{@link ionic.controller:' + tag.description.trim() + '}';
}
},
{
name: 'parent',
transformFn: function(doc, tag) {
return tag.description.split(',').map(function(id) {
return linkify('directive', id);
doc.parentLinks = tag.description.split(',').map(function(id) {
return '{@link ' + id.trim() + '}';
}).join(' or ');
return tag.description.split(',').map(function(parent) {
return parent.trim();
});
}
},
{
@@ -21,5 +21,11 @@ module.exports = [
},
{
name: 'alias'
},
{
name: 'group'
},
{
name: 'groupMainItem'
}
];

View File

@@ -21,9 +21,9 @@ docType: "<$ doc.docType $>"
<@ else @>
<$ doc.name $>
<@ endif @>
<@ if doc.parent @>
<@ if doc.parentLinks @>
<small>
(child of <$ doc.parent $>)
(child of <$ doc.parentLinks $>)
</small>
<@ endif @>
<@ if doc.controller @>

View File

@@ -2,3 +2,4 @@ layout: docs_api
version: "<$ version.current.name $>"
versionHref: "<$ version.current.href $>"
path: "<$ doc.path $>"
group: "<$ doc.group $>"

View File

@@ -57,26 +57,34 @@
<!-- Docs: JavaScript -->
<ul class="nav left-menu active-menu">
<li class="menu-title">
<a href="{% if page.versionHref %}{{page.versionHref}}{% else %}}<$ version.latest.href $>{% endif %}">
<a href="{% if page.versionHref %}{{page.versionHref}}{% else %}}<$ version.current.href $>{% endif %}">
JavaScript
</a>
</li>
<@ for component in doc.areas.api.navGroups[0].navItems @>
<@ if component.type == "section" @>
<li class="menu-subsection{% if page.docType == "<$ component.name $>" %} active{% endif %}">
<$ component.name $>
</li>
<@ else @>
<li class="menu-item{% if page.doc == "<$ component.name $>" %} active{% endif %}">
<a href="{{page.versionHref}}/<$ component.href $>">
<@ if component.type == "directive" @>
<$ component.name | dashCase $>
<@ else @>
<$ component.name $>
<@ endif @>
<@ for group in doc.groups @>
<div class="menu-group-container{% if page.group == "<$ group.name $>" %} active{% endif %}">
<li class="menu-group">
<a href="{{page.versionHref}}/<$ group.sections[0].pages[0].href $>">
<@ if group.name == "Other" @>User Interaction<@ else @><$ group.name $><@ endif @>
</a>
</li>
<@ endif @>
<@ for section in group.sections @>
<div class="menu-section">
<$ section.name $>
</div>
<@ for component in section.pages @>
<li class="menu-item{% if page.doc == "<$ component.name $>" %} active{% endif %}">
<a href="{{page.versionHref}}/<$ component.href $>">
<@ if component.type == "directive" @>
<$ component.name | dashCase $>
<@ else @>
<$ component.name $>
<@ endif @>
</a>
</li>
<@ endfor @>
<@ endfor @>
</div>
<@ endfor @>
</ul>

View File

@@ -18,6 +18,7 @@ angular.module('ionic.ui.header', ['ngAnimate', 'ngSanitize'])
* @name ionHeaderBar
* @module ionic
* @restrict E
* @group page layout
* @controller ionicBar
*
* @description
@@ -26,8 +27,8 @@ angular.module('ionic.ui.header', ['ngAnimate', 'ngSanitize'])
* Is able to have left or right buttons, and additionally its title can be
* aligned through the {@link ionic.controller:ionicBar ionicBar controller}.
*
* @param {string=} model The model to assign this headerBar's
* {@link ionic.controller:ionicBar ionicBar controller} to.
* @param {string=} model The model to assign this headerBar's
* {@link ionic.controller:ionicBar ionicBar controller} to.
* Defaults to assigning to $scope.headerBarController.
* @param {string=} align-title Where to align the title at the start.
* Avaialble: 'left', 'right', or 'center'. Defaults to 'center'.
@@ -55,6 +56,7 @@ angular.module('ionic.ui.header', ['ngAnimate', 'ngSanitize'])
* @name ionFooterBar
* @module ionic
* @restrict E
* @group page layout
* @controller ionicBar
*
* @description
@@ -63,8 +65,8 @@ angular.module('ionic.ui.header', ['ngAnimate', 'ngSanitize'])
* Is able to have left or right buttons, and additionally its title can be
* aligned through the {@link ionic.controller:ionicBar ionicBar controller}.
*
* @param {string=} model The model to assign this footerBar's
* {@link ionic.controller:ionicBar ionicBar controller} to.
* @param {string=} model The model to assign this footerBar's
* {@link ionic.controller:ionicBar ionicBar controller} to.
* Defaults to assigning to $scope.footerBarController.
* @param {string=} align-title Where to align the title at the start.
* Avaialble: 'left', 'right', or 'center'. Defaults to 'center'.
@@ -91,8 +93,8 @@ function barDirective(isHeader) {
var BAR_TEMPLATE = isHeader ?
'<header class="bar bar-header" ng-transclude></header>' :
'<footer class="bar bar-header" ng-transclude></footer>';
var BAR_MODEL_DEFAULT = isHeader ?
'headerBarController' :
var BAR_MODEL_DEFAULT = isHeader ?
'headerBarController' :
'footerBarController';
return ['$parse', function($parse) {
return {

View File

@@ -11,6 +11,7 @@ angular.module('ionic.ui.content', ['ionic.ui.service', 'ionic.ui.scroll'])
* @ngdoc directive
* @name ionPane
* @module ionic
* @group page layout
* @restrict E
*
* @description A simple container that fits content, with no side effects. Adds the 'pane' class to the element.
@@ -28,6 +29,8 @@ angular.module('ionic.ui.content', ['ionic.ui.service', 'ionic.ui.scroll'])
* @ngdoc directive
* @name ionContent
* @module ionic
* @group page layout
* @groupMainItem
*
* @description
* The ionContent directive provides an easy to use content area that can be configured
@@ -167,7 +170,8 @@ function($parse, $timeout, $ionicScrollDelegate, $controller, $ionicBind) {
* @name ionRefresher
* @module ionic
* @restrict E
* @parent ionContent, ionScroll
* @group page layout
* @parent ionic.directive:ionContent, ionic.directive:ionScroll
* @description
* Allows you to add pull-to-refresh to a scrollView.
*
@@ -260,7 +264,8 @@ function($parse, $timeout, $ionicScrollDelegate, $controller, $ionicBind) {
* @ngdoc directive
* @name ionInfiniteScroll
* @module ionic
* @parent ionContent, ionScroll
* @group page layout
* @parent ionic.directive:ionContent, ionic.directive:ionScroll
* @restrict E
*
* @description

View File

@@ -8,7 +8,7 @@ angular.module('ionic.ui.list', ['ngAnimate'])
* @name ionItem
* @module ionic
* @restrict E
* @parent ionList
* @parent ionic.directive:ionList
*
* @description
* The ionItem directive creates a list-item that can easily be swiped,

View File

@@ -4,7 +4,7 @@ angular.module('ionic.ui.navAnimation', [])
* @name ionNavAnimation
* @module ionic
* @restrict A
* @parent ionNavView
* @parent ionic.directive:ionNavView
*
* @description
* When used under an {@link ionic.directive:ionNavView} and on an `<a>` element,

View File

@@ -5,6 +5,7 @@ angular.module('ionic.ui.navBar', ['ionic.service.view', 'ngSanitize'])
* @ngdoc controller
* @name ionicNavBar
* @module ionic
* @group navigation
* @description
* Controller for the {@link ionic.directive:ionNavBar} directive.
*/
@@ -157,6 +158,7 @@ function($scope, $element, $ionicViewService, $animate, $compile) {
* @ngdoc directive
* @name ionNavBar
* @module ionic
* @group navigation
* @controller ionicNavBar
* @restrict E
*
@@ -245,6 +247,7 @@ function($ionicViewService, $rootScope, $animate, $compile, $parse) {
* @name ionNavBackButton
* @module ionic
* @restrict E
* @group navigation
* @parent ionNavBar
* @description
* Creates a back button inside an {@link ionic.directive:ionNavBar}.
@@ -317,6 +320,7 @@ function($ionicViewService, $rootScope, $animate, $compile, $parse) {
* @name ionNavButtons
* @module ionic
* @restrict E
* @group navigation
* @parent ionNavView
*
* @description

View File

@@ -24,6 +24,7 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture', 'ionic.service.vie
* @ngdoc controller
* @name ionicSideMenus
* @module ionic
* @group side menu
*
* @description
* Controller for the {@link ionic.directive:ionSideMenus} directive.
@@ -44,6 +45,8 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture', 'ionic.service.vie
* @name ionSideMenus
* @module ionic
* @restrict E
* @group side menu
* @groupMainItem
* @controller ionicSideMenus
*
* @description
@@ -116,7 +119,8 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture', 'ionic.service.vie
* @name ionSideMenuContent
* @module ionic
* @restrict A
* @parent ionSideMenus
* @group side menu
* @parent ionic.directive:ionSideMenus
*
* @description
* A container for the main visible content, sibling to one or more
@@ -242,7 +246,8 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture', 'ionic.service.vie
* @name ionSideMenu
* @module ionic
* @restrict E
* @parent ionSideMenus
* @group side menu
* @parent ionic.directive:ionSideMenus
*
* @description
* A container for a side menu, sibling to an {@link ionic.directive:ionSideMenuContent} directive.

View File

@@ -8,6 +8,7 @@ angular.module('ionic.ui.tabs', ['ionic.service.view'])
/**
* @ngdoc controller
* @group tab bar
* @name ionicTabs
* @module ionic
*
@@ -132,6 +133,8 @@ angular.module('ionic.ui.tabs', ['ionic.service.view'])
* @name ionTabs
* @module ionic
* @restrict E
* @group tab bar
* @groupMainItem
* @controller ionicTabs
* @codepen KbrzJ
*
@@ -158,7 +161,7 @@ angular.module('ionic.ui.tabs', ['ionic.service.view'])
* </ion-tabs>
* ```
*
* @param {expression=} model The model to assign this tabbar's {@link ionic.controller:ionicTabs} controller to. By default, assigns to $scope.tabsController.
* @param {expression=} model The model to assign this tab bar's {@link ionic.controller:ionicTabs} controller to. By default, assigns to $scope.tabsController.
* @param {string=} animation The animation to use when changing between tab pages.
* @param {string=} tabs-style The class to apply to the tabs. Defaults to 'tabs-positive'.
* @param {string=} tabs-type Whether to put the tabs on the top or bottom. Defaults to 'tabs-bottom'.
@@ -208,10 +211,11 @@ function($scope, $ionicViewService, $rootScope, $element) {
/**
* @ngdoc directive
* @group tab bar
* @name ionTab
* @module ionic
* @restrict E
* @parent ionTabs
* @parent ionic.directive:ionTabs
*
* @description
* Contains a tab's content. The content only exists while the given tab is selected.

View File

@@ -8,7 +8,8 @@ angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gestu
* @name ionView
* @module ionic
* @restrict E
* @parent ionNavBar
* @group navigation
* @parent ionic.directive:ionNavBar
*
* @description
* A container for content, used to tell a parent {@link ionic.directive:ionNavBar}
@@ -78,6 +79,8 @@ angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gestu
* @name ionNavView
* @module ionic
* @restrict E
* @group navigation
* @groupMainItem
* @codepen HjnFx
*
* @description

View File

@@ -51,4 +51,4 @@ angular.element.prototype.removeClass = function(cssClasses) {
}
}
return this;
};
};

View File

@@ -7,6 +7,7 @@ angular.module('ionic.ui.service.scrollDelegate', [])
* @ngdoc service
* @name $ionicScrollDelegate
* @module ionic
* @group page layout
* @description
* Allows you to have some control over a scrollable area (created by an
* {@link ionic.directive:ionContent} or {@link ionic.directive:ionScroll}

View File

@@ -6,6 +6,7 @@ angular.module('ionic.service.platform', [])
* @ngdoc service
* @name $ionicPlatform
* @module ionic
* @group utilities
* @description
* An angular abstraction of {@link ionic.utility:ionic.Platform}.
*

View File

@@ -27,6 +27,7 @@
* @ngdoc utility
* @name ionic.DomUtil
* @module ionic
* @group utilities
*/
ionic.DomUtil = {
//Call with proper context
@@ -181,7 +182,7 @@
* @name ionic.DomUtil#getParentWithClass
* @param {DOMElement} element
* @param {string} className
* @returns {DOMElement} The closest parent of element matching the
* @returns {DOMElement} The closest parent of element matching the
* className, or null.
*/
getParentWithClass: function(e, className) {
@@ -198,7 +199,7 @@
* @name ionic.DomUtil#getParentWithClass
* @param {DOMElement} element
* @param {string} className
* @returns {DOMElement} The closest parent or self matching the
* @returns {DOMElement} The closest parent or self matching the
* className, or null.
*/
getParentOrSelfWithClass: function(e, className) {
@@ -220,7 +221,7 @@
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @returns {boolean} Whether {x,y} fits within the rectangle defined by
* @returns {boolean} Whether {x,y} fits within the rectangle defined by
* {x1,y1,x2,y2}.
*/
rectContains: function(x, y, x1, y1, x2, y2) {

View File

@@ -49,6 +49,7 @@
* @ngdoc utility
* @name ionic.EventController
* @module ionic
* @group utilities
*/
ionic.EventController = {
VIRTUALIZED_EVENTS: ['tap', 'swipe', 'swiperight', 'swipeleft', 'drag', 'hold', 'release'],

View File

@@ -4,6 +4,7 @@
* @ngdoc utility
* @name ionic.Platform
* @module ionic
* @group utilities
*/
ionic.Platform = {
@@ -33,7 +34,7 @@
grade: null,
ua: navigator.userAgent,
/**
/**
* @ngdoc method
* @name ionic.Platform#ready
* @description

View File

@@ -5,6 +5,7 @@
* @ngdoc controller
* @name ionicBar
* @module ionic
* @group page layout
* @description
* Controller for the {@link ionic.directive:ionHeaderBar} and
* {@link ionic.directive:ionFooterBar} directives.

View File

@@ -36,7 +36,9 @@
"winston": "^0.7.2",
"minimist": "0.0.8",
"gulp-minify-css": "^0.3.0",
"semver": "^2.2.1"
"semver": "^2.2.1",
"cp-r": "^0.1.1",
"mkdirp": "^0.3.5"
},
"licenses": [
{