lark-router
Advanced tools
Comparing version 1.0.2 to 1.1.0
383
index.js
/** | ||
* Lark router, auto generate routes by directory structure | ||
* Lark Router | ||
**/ | ||
'use strict'; | ||
import _debug from 'debug'; | ||
import chalk from 'chalk'; | ||
import extend from 'extend'; | ||
import path from 'path'; | ||
import fs from 'fs'; | ||
import KoaRouter from 'koa-router'; | ||
import escapeRegexp from 'escape-string-regexp'; | ||
const $ = require('lodash'); | ||
const debug = require('debug')('lark-router.Router'); | ||
const assert = require('assert'); | ||
const extend = require('extend'); | ||
const fs = require('fs'); | ||
const methods = require('methods'); | ||
const path = require('path'); | ||
const path2regexp = require('path-to-regexp'); | ||
const Switcher = require('switch-case'); | ||
const EventEmitter = require('events').EventEmitter; | ||
const debug = _debug('lark-router'); | ||
debug('loading ...'); | ||
class Router extends EventEmitter { | ||
static get defaultConfig () { | ||
return extend(true, {}, defaultConfig); | ||
} | ||
/** | ||
* Extends KoaRouter with the following methods: | ||
* @method create(options) returns a new instance of Router | ||
* @method load(directory, prefix) generate routes by the directory structure | ||
**/ | ||
class Router extends KoaRouter { | ||
// @overwrite | ||
constructor (options = {}) { | ||
if (options && !(options instanceof Object)) { | ||
throw new Error('Options must be an object if given'); | ||
} | ||
debug('Router: Router.constructor'); | ||
super(options); | ||
debug('constructing ...'); | ||
super(); | ||
this.opts.param_prefix = this.opts.param_prefix || '_'; | ||
if ('string' !== typeof this.opts.param_prefix || !this.opts.param_prefix.match(/^\S+$/)) { | ||
throw new Error("Router options param_prefix must be a string matching patter \\S+"); | ||
} | ||
this.opts.prefix_esc = escapeRegexp(this.opts.param_prefix); | ||
this.adapter = { | ||
parseFileName: defaultParseFileName, | ||
}; | ||
this.opts.default = this.opts.default || 'index.js'; | ||
if ('string' !== typeof this.opts.default || this.opts.default.length === 0) { | ||
throw new Error("Router options default must be a string"); | ||
} | ||
this._switcher = new Switcher(); | ||
this._enabledMethods = []; | ||
this.configure(options); | ||
// overwrting switcher methods | ||
this._switcher.prepare = this._prepare.bind(this); | ||
this._switcher.match = this._match.bind(this); | ||
this._switcher.nesting = this._nesting.bind(this); | ||
this._switcher.execute = this._execute.bind(this); | ||
} | ||
static create (options) { | ||
debug('Router: Router.create'); | ||
return new Router(options); | ||
// @overwrite switcher | ||
_prepare (o, req, ...args) { | ||
debug('preparing ...'); | ||
o = extend(true, {}, o); | ||
return [o, req, ...args]; | ||
} | ||
load (root, prefix) { | ||
debug('Router: loading by root path ' + root); | ||
if ('string' !== typeof root) { | ||
throw new Error('Router loading root path is not a string'); | ||
// @overwrite switcher | ||
_match (condition, o, req, ...args) { | ||
debug('matching ...'); | ||
assert('string' === typeof o.method && 'string' === typeof o.path, 'Method and URL must be string!'); | ||
debug('testing [' + o.method.toUpperCase() + ' ' + o.path + '] with [' + condition.method.toUpperCase() + ' ' + condition.pathexp + '] ...'); | ||
if ((condition.method !== o.method || (this._config.max > 0 && this._config.max <= req.routed)) | ||
&& !this._specialMethods.includes(condition.method)) { | ||
return false; | ||
} | ||
root = path.normalize(root); | ||
const result = condition.regexp.exec(o.path); | ||
if (!result) return false; | ||
if (!path.isAbsolute(root)) { | ||
debug('Router: root is not absolute, make an absolute one'); | ||
root = path.join(path.dirname(process.mainModule.filename), root); | ||
if ((condition.method === 'routed' && !req.routed) || | ||
(condition.method === 'other' && req.routed)) { | ||
return false; | ||
} | ||
if (prefix) { | ||
prefix = path.normalize(prefix); | ||
if (!prefix || !prefix[0] || prefix[0] === '.') { | ||
throw new Error('Invalid router prefix ' + prefix); | ||
} | ||
debug('Router: create a new Router to load with prefix ' + prefix); | ||
const opts = extend(true, {}, this.opts); | ||
opts.routePrefix = opts.routePrefix || ''; | ||
opts.routePrefix += prefix; | ||
const router = Router.create(opts).load(root); | ||
debug('Router: using the router with prefix ' + prefix); | ||
this.use(prefix, router.routes()); | ||
return this; | ||
} | ||
assert(result.length >= 1, 'Internal Error!'); | ||
debug('Router; loading by directory structure of ' + root); | ||
debug('matched!'); | ||
req.routed++; | ||
/** | ||
* First load all files, then load directories recrusively | ||
**/ | ||
const dirlist = []; | ||
const filelist = []; | ||
const list = fs.readdirSync(root); | ||
for (const filename of list) { | ||
let routePath = name2routePath(filename, this.options); | ||
if (routePath === false) { | ||
continue; | ||
const keys = condition.regexp.keys; | ||
const startIndex = Object.keys(o.params).length; | ||
for (let i = 1; i < result.length; i++) { | ||
const index = i - 1; | ||
let name = keys[index].name; | ||
if ('number' === typeof name) { | ||
name += startIndex; | ||
} | ||
routePath = '/' + routePath; | ||
const item = { filename, routePath }; | ||
const absolutePath = path.join(root, filename); | ||
const stat = fs.statSync(absolutePath); | ||
if (stat.isDirectory()) { | ||
dirlist.push(item); | ||
} | ||
else if (stat.isFile()) { | ||
filelist.push(item); | ||
} | ||
assert(!o.params.hasOwnProperty(name), "Duplicated path param name [" + name + "]!"); | ||
o.params[name] = $.cloneDeep(result[i]) || '/'; | ||
} | ||
for (const item of filelist) { | ||
loadRouteByFilename(this, item.filename, item.routePath, root); | ||
} | ||
for (const item of dirlist) { | ||
this.load(path.join(root, item.filename), item.routePath); | ||
} | ||
return this; | ||
condition.nesting && (o.subroutine = this.subroutine); | ||
return true; | ||
} | ||
} | ||
// @overwrite switcher | ||
_nesting (o, req, ...args) { | ||
debug('passing args to the nested router ...'); | ||
o = extend(true, {}, o); | ||
const subroutine = this.subroutine; | ||
assert('string' === typeof o.params[o.subroutine], 'subroutine not found!'); | ||
o.path = o.params[o.subroutine]; | ||
if (o.path[0] != '/') o.path = '/' + o.path; | ||
delete o.params[o.subroutine]; | ||
delete o.subroutine; | ||
function name2routePath (name, options) { | ||
debug('Router: convert name to route path : ' + name); | ||
if ('string' !== typeof name) { | ||
throw new Error('Name must be a string'); | ||
// nesting match will add a count on the route counter | ||
// but it is not really routed, so -1 to fix the counter | ||
req.routed--; | ||
return [o, req, ...args]; | ||
} | ||
if (name === (options.default || 'index.js')) { | ||
return ''; | ||
// @overwrite switcher | ||
_execute (result, o, req, ...args) { | ||
req.params = $.cloneDeep(o.params); | ||
return result(req, ...args); | ||
} | ||
const extname = path.extname(name); | ||
if (extname && extname !== '.js') { | ||
return false; | ||
} | ||
name = path.basename(name, extname); | ||
if (!name || name[0] === '.') { | ||
return false; | ||
} | ||
configure (options = {}) { | ||
debug('configuring ...'); | ||
assert(options instanceof Object, 'Options must be an object!'); | ||
const prefix = options.param_prefix || '_'; | ||
const prefix_esc = escapeRegexp(prefix); | ||
this._config = this._config instanceof Object ? this._config : $.cloneDeep(Router.defaultConfig); | ||
assert(this._config instanceof Object, 'Internal Error'); | ||
let routePath = name.replace(new RegExp("^" + prefix_esc + "(?!(" + prefix_esc + ")|$)"), ":") | ||
.replace(new RegExp("^" + prefix_esc + prefix_esc), prefix); | ||
if (!Array.isArray(options.methods) || options.methods.length <= 0) { | ||
options.methods = methods; | ||
} | ||
debug('Router: convert result is ' + routePath); | ||
return routePath; | ||
} | ||
this._config = extend(true, this._config, options); | ||
assert(Array.isArray(this._config.methods), 'Methods must be an array!'); | ||
function loadRouteByFilename (router, filename, routePath, root) { | ||
if ('string' !== typeof filename || 'string' !== typeof root) { | ||
throw new Error('Invalid param to load by dirname'); | ||
this._httpMethods = $.cloneDeep(this._config.methods).map(o => o.toLowerCase()); | ||
this._specialMethods = ['all', 'routed', 'other']; | ||
this._methods = $.cloneDeep(this._httpMethods).concat($.cloneDeep(this._specialMethods)); | ||
this.bindMethods(); | ||
return this; | ||
} | ||
debug('Router: loading file ' + filename); | ||
if (path.extname(filename) !== '.js' || filename.length <= 3) { | ||
return; | ||
get methods () { | ||
return $.cloneDeep(this._methods); | ||
} | ||
get subroutine () { | ||
return this._config.subroutine || 'subroutine'; | ||
} | ||
route (method, pathexp, handler) { | ||
debug('setting route for [' + method + '] [' + pathexp + '] ...'); | ||
assert('string' === typeof method, 'Method must be a string!'); | ||
method = method.toLowerCase(); | ||
assert(this._methods.includes(method), 'Invalid Method!'); | ||
assert('string' === typeof pathexp || pathexp instanceof RegExp, 'Path expression must be a string or a Regular Expression!'); | ||
debug("Router: route path [" + routePath + "]"); | ||
const absolutePath = path.join(root, filename); | ||
//import fileModule from absolutePath; | ||
let fileModule = null; | ||
try { | ||
fileModule = require(absolutePath).default || require(absolutePath); | ||
let nesting = false; | ||
if (handler instanceof Router) { | ||
handler = handler._switcher; | ||
nesting = true; | ||
if (this._config['nesting-path-auto-complete'] && !(pathexp instanceof RegExp)) { | ||
if (!pathexp.endsWith('/')) pathexp += '/'; | ||
pathexp += `:${this.subroutine}*`; | ||
} | ||
} | ||
const regexp = path2regexp(pathexp); | ||
return this._switcher.case({ method, pathexp, regexp, nesting }, handler, { break: false }); | ||
} | ||
catch (e) { | ||
console.log("Failed to load route by file '" + absolutePath + "', error is " + e.message); | ||
return; | ||
} | ||
routes () { | ||
return (req, ...args) => { | ||
assert('string' === typeof req.url, 'URL must be a string'); | ||
assert('string' === typeof req.method, 'METHOD must be a string'); | ||
debug(`routing ${req.method.toUpperCase()} ${req.url} ...`); | ||
const o = { | ||
path: decodeURIComponent(req.url.split('?')[0]), | ||
method: req.method.toLowerCase(), | ||
params: {} | ||
}; | ||
assert(this._httpMethods.includes(o.method), 'Invalid METHOD [' + req.method + ']'); | ||
if (fileModule instanceof Function) { | ||
debug("Router: module is a function, use it to handle router directly"); | ||
let subRouter = Router.create(router.opts); | ||
let result = fileModule(subRouter); | ||
if (result instanceof Router) { | ||
subRouter = result; | ||
req.routed = 0; | ||
return this._switcher.dispatch(o, req, ...args).catch(e => { | ||
this.emit('error', e, req, ...args); | ||
}); | ||
} | ||
router.use(routePath, subRouter.routes()); | ||
} | ||
else if (fileModule instanceof Object) { | ||
loadByModule(router, routePath, fileModule); | ||
clear () { | ||
debug('clearing ...'); | ||
let method = null; | ||
while (method = this._enabledMethods.pop()) { | ||
delete this[method]; | ||
} | ||
this._switcher._cases = []; | ||
return this; | ||
} | ||
else { | ||
throw new Error('Invalid router module'); | ||
bindMethods () { | ||
debug('binding methods [' + $.truncate(this.methods.join(', '), 20) + '](' + this.methods.length + ' methods) ...'); | ||
this.clear(); | ||
for (let item of this.methods) { | ||
const Method = item[0].toUpperCase() + item.slice(1).toLowerCase(); | ||
const method = Method.toLowerCase(); | ||
const METHOD = Method.toUpperCase(); | ||
this[method] = this[Method] = this[METHOD] = (pathexp, handler) => { | ||
return this.route(method, pathexp, handler); | ||
} | ||
this._enabledMethods.push(method, Method, METHOD); | ||
} | ||
} | ||
} | ||
function loadByModule (router, routePath, module) { | ||
debug("Router: load route by module"); | ||
//handle redirect routes | ||
for (const method_ori in module) { | ||
const METHOD = method_ori.toUpperCase(); | ||
if (METHOD !== 'REDIRECT' || 'string' !== typeof module[method_ori]) { | ||
continue; | ||
load (filename) { | ||
assert('string' === typeof filename, 'File name must be a string!'); | ||
debug('loading router by path ...'); | ||
filename = path.normalize(filename).replace(/\\/g, '/'); | ||
if (!path.isAbsolute(filename)) { | ||
const rootDirectory = path.dirname(process.mainModule.filename); | ||
filename = path.join(rootDirectory, filename); | ||
} | ||
const desc = METHOD + ' ' + (router.opts.routePrefix || '') + routePath; | ||
debug("Router: add router " + chalk.yellow(desc + " => " + module[method_ori])); | ||
router.redirect(routePath, module[method_ori]); | ||
return; | ||
assert(path.isAbsolute(filename), 'File path must be or can be converted into an absolute path!'); | ||
debug('path is ' + filename); | ||
const stat = fs.statSync(filename); | ||
if (stat.isFile()) { | ||
debug('loading router by file ...'); | ||
const filemodule = require(filename); | ||
assert(filemodule instanceof Function || filemodule instanceof Object, 'File as router should export a Function or an Object!'); | ||
if (filemodule instanceof Function) { | ||
debug('file module is a function'); | ||
const router = filemodule(this) || this; | ||
assert(router instanceof Router, 'Function as router should return a Router or null'); | ||
if (router !== this) { | ||
this.all('/', router); | ||
} | ||
} | ||
else { | ||
debug('file module is an object'); | ||
for (let method in filemodule) { | ||
this.route(method, '/', filemodule[method]); | ||
} | ||
} | ||
} | ||
else { | ||
debug('loading router by directory ...'); | ||
const files = fs.readdirSync(filename); | ||
for (let file of files) { | ||
const filepath = path.join(filename, file); | ||
const router = new Router(); | ||
router.adapter = this.adapter; | ||
router.load(filepath); | ||
file = path.basename(file, path.extname(file)); | ||
const prefix = this.adapter.parseFileName(file) || file; | ||
this.all('/' + prefix, router); | ||
} | ||
} | ||
return this; | ||
} | ||
} | ||
//handle methods | ||
for (const method_ori in module) { | ||
const method = method_ori.toLowerCase(); | ||
const METHOD = method_ori.toUpperCase(); | ||
const defaultConfig = { | ||
max: 0, // max routed limit, 0 refers to unlimited | ||
methods: methods, // methods to support | ||
subroutine: 'subroutine', // subroutine param name for router's nesting | ||
'nesting-path-auto-complete': true, // whether if auto complete the route path for nesting routes | ||
} | ||
const desc = METHOD + ' ' + (router.opts.routePrefix || '') + routePath; | ||
if (!(module[method_ori] instanceof Function) || router.methods.indexOf(METHOD) < 0) { | ||
continue; | ||
} | ||
debug("Router: add router " + chalk.yellow(desc)); | ||
router[method](routePath, module[method_ori]); | ||
function defaultParseFileName (filename) { | ||
let name = filename; | ||
const PARAM = '.as.param'; | ||
const ASTERISK = '.as.asterisk'; | ||
if (filename.endsWith(PARAM)) { | ||
name = ':' + filename.slice(0, filename.length - PARAM.length); | ||
} | ||
else if (filename.endsWith(ASTERISK)) { | ||
name = ':' + filename.slice(0, filename.length - ASTERISK.length) + '*'; | ||
} | ||
return name; | ||
} | ||
export default Router; | ||
debug('Router: load ok'); | ||
debug('loaded!'); | ||
module.exports = Router; |
{ | ||
"name": "lark-router", | ||
"version": "1.0.2", | ||
"description": "An koa route initialization and configuration module.", | ||
"main": ".easy", | ||
"engines": { | ||
"node": ">=4.0.0" | ||
}, | ||
"scripts": { | ||
"test": "cd .easy && rm -rf node_modules && ln -s ../node_modules node_modules && NODE_ENV=testing ./node_modules/.bin/mocha --require should test/" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/larkjs/lark-router" | ||
}, | ||
"keywords": [ | ||
"bootstrap", | ||
"koa" | ||
], | ||
"author": "mdemo", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/larkjs/lark-router/issues" | ||
}, | ||
"homepage": "https://github.com/larkjs/lark-router", | ||
"dependencies": { | ||
"chalk": "^1.1.1", | ||
"debug": "^2.2.0", | ||
"escape-string-regexp": "^1.0.3", | ||
"extend": "^3.0.0", | ||
"koa-router": "^6.0.0", | ||
"methods": "^1.1.1", | ||
"path-to-regexp": "^1.2.1" | ||
}, | ||
"devDependencies": { | ||
"chalk": "^1.1.1", | ||
"easy-babel": "^1.0.1", | ||
"koa": "~2.0.0-alpha.3", | ||
"koa-convert": "^1.2.0", | ||
"mocha": "~2.0.1", | ||
"should": "~4.3.0", | ||
"supertest": "~0.15.0" | ||
}, | ||
"easy": { | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "NODE_ENV=testing ./node_modules/.bin/mocha --require should test/" | ||
} | ||
} | ||
} | ||
"name": "lark-router", | ||
"version": "1.1.0", | ||
"description": "An koa route initialization and configuration module.", | ||
"main": "index.js", | ||
"engines": { | ||
"node": ">=6.4.0" | ||
}, | ||
"scripts": { | ||
"test": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --require should --recursive test", | ||
"_test": "./node_modules/.bin/mocha --require should test/" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/larkjs/lark-router" | ||
}, | ||
"keywords": [ | ||
"router", | ||
"koa" | ||
], | ||
"author": "Sun Haohao", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/larkjs/lark-router/issues" | ||
}, | ||
"homepage": "https://github.com/larkjs/lark-router", | ||
"dependencies": { | ||
"debug": "^2.2.0", | ||
"extend": "^3.0.0", | ||
"lodash": "^4.15.0", | ||
"methods": "^1.1.2", | ||
"path-to-regexp": "^1.5.3", | ||
"switch-case": "^0.4.0" | ||
}, | ||
"devDependencies": { | ||
"istanbul": "^0.4.5", | ||
"koa": "^2.0.0-alpha.6", | ||
"lodash": "^4.16.2", | ||
"mocha": "~2.0.1", | ||
"should": "~4.3.0", | ||
"supertest": "^2.0.0" | ||
} | ||
} |
115
README.md
@@ -9,88 +9,101 @@ lark-router | ||
## Installation | ||
## Install | ||
``` | ||
$ npm install lark-router | ||
$ npm install --save lark-router | ||
``` | ||
## API | ||
### `app.use(new Router().load('controllers').routes())` | ||
## Get started | ||
Exmaple: | ||
_Lark-Router_ is a flexible and easy-to-use url router tool, compatible with native http apps, express apps and koa(v2) apps. | ||
* http apps | ||
``` | ||
import Koa from 'koa'; | ||
import Router from 'lark-router'; | ||
const router = new LarkRouter(); | ||
const router = new Router().load('controllers'); | ||
router.get('/foo/bar', (req, res) => res.end("/foo/bra requested!")); | ||
router.on('error', (error, req, res) => { | ||
res.statusCode = 500; | ||
res.end(error.message); | ||
}); | ||
const app = new Kao(); | ||
http.createServer(router.routes()).listen(3000); | ||
``` | ||
app.use(router.routes()); | ||
* koa apps | ||
``` | ||
const router = new LarkRouter(); | ||
const app = new Koa(); | ||
app.listen(3000); | ||
router.get('/foo/bar', (ctx, next) => ctx.body = '/foo/bar requested!'); | ||
router.on('error', (error, ctx, next) => { | ||
ctx.statusCode = 500; | ||
ctx.body = error.message; | ||
return next(); | ||
}); | ||
app.use(router.routes()).listen(3000); | ||
``` | ||
## load | ||
## Params | ||
### routes | ||
See [`path-to-regexp`](https://github.com/pillarjs/path-to-regexp). Params object is bind to the first argument of the app processor. | ||
`lark-router` extends `koa-router` with a method `load(directory, prefix)`. By calling `router.load(directory, prefix)`, `lark-router` will load all js files recursively under that directory, and use their exports as callbacks to the routes corresponding to their paths. | ||
```javascript | ||
router.get('/:foo/:bar', (ctx, next) => { console.log(ctx.params); }); // ===> { foo: xxx, bar: xxx } | ||
router.get(/^\/(\d+)\/(\w+)$/, (ctx, next) => { console.log(ctx.params); }); // ===> { 0: xxx, 1: xxx} | ||
``` | ||
## all, other, routed | ||
This is how file paths is converted into routes (with default options: `{ default: 'index.js', param_prefix: '_'}`) | ||
Lark router has 3 special methods. | ||
* all: match all requests | ||
``` | ||
directory | ||
├─ index.js => / | ||
├─ hello/ | ||
│ └─ world.js => /hello/world | ||
└─ _category/ | ||
└─ _title.js => /:category/:title | ||
router.all('/foo/bar', handler); // ===> response to GET/POST/DELETE/... /foo/bar | ||
``` | ||
#### methods | ||
* other: match all unmatched requests | ||
Methods should be defined in those js files, exported as verb properties. We recommand you use verbs in upper case to avoid using reserved words such as `delete`. | ||
``` | ||
/** | ||
* @file: hello/world.js | ||
**/ | ||
export const GET = async (ctx, next) => { | ||
// handle requests on GET /hello/world | ||
} | ||
router.other('*', response404notfound); // ===> response to GET/POST/DELETE/... /foo/bar if no other route matched | ||
``` | ||
export const DELETE = async (ctx, next) => { | ||
// handle request on DELETE /hello/world | ||
} | ||
* routed: match all matched requests | ||
``` | ||
router.routed('/foo/bar', () => console.log('/foo/bar has been routed')); // ===> response to GET/POST/DELETE/... /foo/bar if some routes matched | ||
``` | ||
or use `router` directly by exporting a function | ||
## Nesting | ||
You could nest routers together: | ||
``` | ||
/** | ||
* @file: hello/world.js | ||
**/ | ||
mainRouter.all('/api', apiRouter); | ||
``` | ||
export default router => { | ||
router.get('/', async (ctx, next) => { | ||
// handle requests on GET /hello/world | ||
} | ||
router.get('/:foo/:bar', async (ctx, next) => { | ||
// handle requests on GET /hello/world/:foo/:bar | ||
} | ||
} | ||
_Note that Lark-Router uses a path param to pass the unmatched part of path. That param name can be configured, usually is `subroutine`, and a string `'/:subroutine*'` will be append to that expression automatically._ | ||
``` | ||
mainRouter.configure({ | ||
'subroutine': 'sub', | ||
'nesting-path-auto-complete': false, | ||
}); | ||
## Tests | ||
mainRouter.all('/api/:sub*', api); // equivalent to the example above. | ||
``` | ||
npm test | ||
## Async processors | ||
For async processors, return promises. | ||
``` | ||
router.get('/', () => new Promise(...)); | ||
``` | ||
## Loading files and directories to generate route rules | ||
TBD... | ||
## DETAILED DOC | ||
TBD... | ||
[npm-image]: https://img.shields.io/npm/v/lark-router.svg?style=flat-square | ||
@@ -97,0 +110,0 @@ [npm-url]: https://npmjs.org/package/lark-router |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
6
6
112
2
38989
31
904
5
+ Addedlodash@^4.15.0
+ Addedswitch-case@^0.4.0
+ Addedlodash@4.17.21(transitive)
+ Addedswitch-case@0.4.1(transitive)
- Removedchalk@^1.1.1
- Removedescape-string-regexp@^1.0.3
- Removedkoa-router@^6.0.0
- Removedansi-regex@2.1.1(transitive)
- Removedansi-styles@2.2.1(transitive)
- Removedany-promise@1.3.0(transitive)
- Removedchalk@1.1.3(transitive)
- Removeddepd@1.1.2(transitive)
- Removedescape-string-regexp@1.0.5(transitive)
- Removedhas-ansi@2.0.0(transitive)
- Removedhttp-errors@1.8.1(transitive)
- Removedinherits@2.0.4(transitive)
- Removedkoa-compose@3.2.1(transitive)
- Removedkoa-router@6.2.0(transitive)
- Removedsetprototypeof@1.2.0(transitive)
- Removedstatuses@1.5.0(transitive)
- Removedstrip-ansi@3.0.1(transitive)
- Removedsupports-color@2.0.0(transitive)
- Removedtoidentifier@1.0.1(transitive)
Updatedmethods@^1.1.2
Updatedpath-to-regexp@^1.5.3