Initial commit

This commit is contained in:
retanoj
2020-07-29 10:03:15 +08:00
commit 8156d16ec4
33 changed files with 12812 additions and 0 deletions

206
.gitignore vendored Normal file
View File

@@ -0,0 +1,206 @@
# Created by https://www.toptal.com/developers/gitignore/api/webstorm+all,node
# Edit at https://www.toptal.com/developers/gitignore?templates=webstorm+all,node
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
### WebStorm+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### WebStorm+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
.idea/
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
*.iml
modules.xml
.idea/misc.xml
*.ipr
# Sonarlint plugin
.idea/sonarlint
# End of https://www.toptal.com/developers/gitignore/api/webstorm+all,node
src/*.js
*.js.map

12
CHANGES Normal file
View File

@@ -0,0 +1,12 @@
Changelog
=========
Version 1.0.1
- feature add endpoint param & MOSEC_ENDPOINT env param
- feature change --no-dev to --with-dev
Version 1.0.0
- Init

13
LICENSE Normal file
View File

@@ -0,0 +1,13 @@
Copyright 2020 momosecurity.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

21
README.md Normal file
View File

@@ -0,0 +1,21 @@
# MOSEC-NODE-PLUGIN
用于检测 node 项目的第三方依赖组件是否存在安全漏洞。
该项目是基于 [snyk/resolve-deps](https://github.com/snyk/resolve-deps.git) 的二次开发。
## 版本支持
npm >= 5.2.0
## 使用
首先运行 [MOSEC-X-PLUGIN Backend](https://github.com/momosecurity/mosec-x-plugin-backend.git)
#### 无需安装即可使用
```
> cd your_node_project/
> npx github:momosecurity/mosec-node-plugin \
--endpoint https://127.0.0.1:9000/api/plugin \
--only-provenance
```

26
index.js Executable file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env node
let program = require("commander");
let check = require("./dist/main");
let package = require("./package.json");
let logger = require("./dist/logger");
function main() {
program
.version(package.version)
.option('-e, --endpoint <value>', '上报API')
.option('-t, --target <path>', '项目所在目录', process.cwd())
.option('-s, --severity-level <value>', '威胁等级 [High|Medium|Low]', 'High')
.option('--only-provenance', '仅检查直接依赖', false)
.option('--with-dev', '包括devDependency', false)
.parse(process.argv);
check
.checkProject(program.target, program.endpoint, program.severityLevel, program.onlyProvenance, program.withDev)
.catch(function (e) {
logger.error(e.message);
});
}
main();

2509
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

61
package.json Normal file
View File

@@ -0,0 +1,61 @@
{
"name": "mosec-node-plugin",
"version": "1.0.1",
"description": "用于检测Node项目的第三方依赖组件是否存在安全漏洞",
"author": "retanoj <mmsrc@immomo.com>",
"homepage": "https://github.com/momosecurity/mosec-node-plugin.git",
"main": "index.js",
"directories": {
"test": "test"
},
"files": [
"index.js",
"package.json",
"/dist"
],
"scripts": {
"build": "tsc",
"build-watch": "tsc -w",
"tslint": "tslint --project tsconfig.json --format stylish --exclude **/src/**/*.js",
"prepare": "npm run build",
"lint": "npm run tslint",
"test": "npm run check-tests && npm run lint && tap test/*.test.[jt]s --timeout=60 --node-arg=-r --node-arg=ts-node/register"
},
"bin": {
"mosec": "./index.js"
},
"keywords": [
"security",
"dependencies",
"vulnerability"
],
"license": "Apache License 2.0",
"devDependencies": {
"@types/node": "^6.14.4",
"@types/semver": "^5.5.0",
"sinon": "^1.17.3",
"tap": "^12.6.0",
"tap-only": "0.0.5",
"ts-node": "^8.1.0",
"tslint": "^5.13.1",
"typescript": "^3.3.3333"
},
"dependencies": {
"ansicolors": "^0.3.2",
"axios": "^0.19.2",
"chalk": "^4.1.0",
"commander": "^5.1.0",
"debug": "^3.2.5",
"lodash.assign": "^4.2.0",
"lodash.assignin": "^4.2.0",
"lodash.clonedeep": "^4.3.0",
"lodash.flatten": "^4.4.0",
"lru-cache": "^4.0.0",
"semver": "^5.5.1",
"then-fs": "^2.0.0"
},
"engines": {
"node": ">= 6.0.0",
"npm": ">= 5.2.0"
}
}

47
src/dep-types.ts Normal file
View File

@@ -0,0 +1,47 @@
/*
Copyright 2017 Snyk Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
export = depTypes;
import { DepType, HasDependencySpecs } from "./types";
// Dependency types.
// We don't call out all of them, only the ones relevant to our behavior.
// extraneous means not found in package.json files, prod means not dev ATM
function depTypes(depName: string, pkg: HasDependencySpecs) {
let type: string | null = null;
let from = 'unknown';
if (pkg.devDependencies && pkg.devDependencies[depName]) {
type = depTypes.DEV;
from = pkg.devDependencies[depName];
}
// production deps trump all
if (pkg.dependencies && pkg.dependencies[depName]) {
type = depTypes.PROD;
from = pkg.dependencies[depName];
}
return {
type: type as string,
from: from,
};
}
depTypes.EXTRANEOUS = 'extraneous' as DepType;
depTypes.OPTIONAL = 'optional' as DepType;
depTypes.PROD = 'prod' as DepType;
depTypes.DEV = 'dev' as DepType;

263
src/deps.ts Normal file
View File

@@ -0,0 +1,263 @@
/*
Copyright 2017 Snyk Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
export = loadModules;
import * as depTypes from './dep-types';
import * as tryRequire from './try-require';
import * as resolve from './pkg-resolve';
import * as fs from 'then-fs';
import * as _assign from 'lodash.assign';
import * as _flatten from 'lodash.flatten';
import * as debugModule from 'debug';
import * as path from 'path';
import * as semver from 'semver';
import {AbbreviatedVersion, PackageExpanded, PackageJsonEnriched, DepExpandedDict} from './types';
const debug = debugModule('mosec:resolve:deps');
function loadModules(root, depType) {
tryRequire.cache.reset(); // reset the package cache on re-run
let pkgRoot = root;
return loadModulesInternal(
pkgRoot,
depType || null,
null,
).then(function (tree) {
// ensure there's no missing packages our known root deps
let missing: Array<Promise<PackageExpanded>> = [];
if (tree.__dependencies) {
Object.keys(tree.__dependencies).forEach(function (name) {
if (!tree.dependencies[name]) {
missing.push(resolve.resolvePkg(name, pkgRoot).then(function (dir) {
return loadModulesInternal(dir, depTypes.PROD, {
from: [tree.name + '@' + tree.version, name],
});
}).catch(function (e) {
if (e.code === 'NO_PACKAGE_FOUND') {
return false;
}
}));
}
});
}
if (missing.length) {
return Promise.all(missing).then(function (packages) {
packages.filter(Boolean).forEach(function (pkg) {
pkg.dep = tree.__dependencies[pkg.name];
tree.dependencies[pkg.name] = pkg;
});
return tree;
});
}
return tree;
});
}
function loadModulesInternal(root, rootDepType, parent): Promise<PackageExpanded> {
if (!rootDepType) {
rootDepType = depTypes.EXTRANEOUS;
}
if (typeof root !== 'string') {
return Promise.reject(new Error('module path must be a string'));
}
let modules;
let dir = path.resolve(root, 'package.json');
// 1. read package.json for written deps
return tryRequire(dir).then(function (pkg: PackageJsonEnriched) {
// create root pkg node
if (pkg) {
let full = pkg.name + '@' + (pkg.version || '0.0.0');
modules = {} as PackageExpanded;
_assign(modules, {
name: pkg.name,
version: pkg.version || '0.0.0',
license: pkg.license || 'none',
depType: rootDepType,
hasDevDependencies: !!pkg.devDependencies,
full: full,
from: (parent || {from: []}).from,
__devDependencies: pkg.devDependencies,
__dependencies: pkg.dependencies,
__filename: pkg.__filename,
});
// allows us to add to work out the full path that the package was
// introduced via
pkg.from = modules.from.concat(full);
pkg.full = modules.full;
// this is a special case for the root package to get a consistent
// from path, so that the complete path (including it's own pkg name)
if (modules.from.length === 0) {
modules.from.push(full);
}
} else {
throw new Error(dir + ' is not a node project');
}
modules.dependencies = {};
// 2. check actual installed deps
return fs.readdir(path.resolve(root, 'node_modules')).then(function (dirs) {
let res: AbbreviatedVersion[] = dirs.map(function (directory) {
// completely ignore `.bin` npm helper dir
// ~ can be a symlink to node_modules itself
// (https://www.npmjs.com/package/link-tilde)
if (['.bin', '.DS_Store', '~'].indexOf(directory) >= 0) {
return null;
}
// this is a scoped namespace, and we'd expect to find directories
// inside *this* `dir`, so treat differently
if (directory.indexOf('@') === 0) {
debug('scoped reset on %s', directory);
directory = path.resolve(root, 'node_modules', directory);
return fs.readdir(directory).then(function (directories) {
return Promise.all(directories.map(function (scopedDir) {
return tryRequire(path.resolve(directory, scopedDir, 'package.json'));
}));
});
}
// otherwise try to load a package.json from this node_module dir
directory = path.resolve(root, 'node_modules', directory, 'package.json');
return tryRequire(directory) as AbbreviatedVersion;
});
return Promise.all(res).then(function (response) {
response = _flatten(response).filter(Boolean);
response.reduce(function (acc, curr) {
let license;
let licenses = curr.license as any || curr.licenses as any;
if (Array.isArray(licenses)) {
license = licenses.reduce(function (accumulator, current) {
accumulator.push((current || {}).type || current);
return accumulator;
}, []).join('/');
} else {
license = (licenses || {}).type || licenses;
}
let depInfo = depTypes(curr.name!, pkg);
let depType = depInfo.type || rootDepType;
let depFrom = depInfo.from;
let valid = false;
if (depFrom) {
valid = semver.satisfies(curr.version as string, depFrom);
}
let full = curr.name + '@' + (curr.version || '0.0.0');
acc[curr.name!] = {} as PackageExpanded;
_assign(acc[curr.name!], {
name: curr.name,
version: curr.version || null,
full: full,
valid: valid,
depType: depType,
license: license || 'none',
dep: depFrom || null,
from: pkg.from.concat(full),
__devDependencies: curr.devDependencies,
__dependencies: curr.dependencies,
__filename: curr.__filename,
});
return acc;
}, modules.dependencies);
return modules;
});
}).then(function (mods) {
let deps = Object.keys(mods.dependencies);
let promises = deps.map(function (dep) {
let depType = mods.dependencies[dep].depType;
let directory = path.dirname(mods.dependencies[dep].__filename);
return loadModulesInternal(directory, depType, pkg);
});
return Promise.all(promises).then(function (res) {
res.forEach(function (mod) {
mods.dependencies[mod.name].dependencies = mod.dependencies;
});
return mods;
});
}).catch(function (error) {
/* istanbul ignore else */
if (error.code === 'ENOENT') {
// there's no node_modules directory, that's fine, there's no deps
modules.dependencies = {};
return modules;
}
/* istanbul ignore next */
throw error;
});
});
}
function simplifyDeps(deps: PackageExpanded, onlyProvenance: boolean, noDev: boolean) {
let allowProp = ['name', 'version', 'dependencies', 'from'];
if (!deps) {
return ;
}
// remove extraneous dependencies
Object.keys(deps.dependencies).forEach((name) => {
if (deps.dependencies[name].depType === depTypes.EXTRANEOUS) {
delete deps.dependencies[name];
}
});
if (noDev) {
// remove dev dependencies
Object.keys(deps.dependencies).forEach((name) => {
if (deps.dependencies[name].depType === depTypes.DEV) {
delete deps.dependencies[name];
}
});
}
if (onlyProvenance) {
// only check top level dependencies
if (deps.from.length > 1) {
deps.dependencies = {};
}
}
Object.keys(deps).forEach((name) => {
if (allowProp.indexOf(name) === -1) {
delete deps[name];
}
});
if (deps.dependencies) {
Object.keys(deps.dependencies).forEach((name) => {
simplifyDeps(deps.dependencies[name], onlyProvenance, noDev);
});
}
return deps;
}
loadModules.simplifyDeps = simplifyDeps;

34
src/logger.ts Normal file
View File

@@ -0,0 +1,34 @@
/*
Copyright 2020 momosecurity.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
export { error, info, warn, log };
import * as chalk from "chalk";
function error(msg) {
console.error("[MOSEC] %s", chalk.redBright(msg));
}
function info(msg) {
console.info("[MOSEC] %s", chalk.greenBright(msg));
}
function warn(msg) {
console.warn("[MOSEC] %s", chalk.yellowBright(msg));
}
function log(msg) {
console.log("[MOSEC] %s", msg);
}

125
src/main.ts Normal file
View File

@@ -0,0 +1,125 @@
/*
Copyright 2020 momosecurity.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import set = Reflect.set;
export { checkProject };
import * as process from 'process';
import * as fs from 'then-fs';
import * as path from 'path';
import * as logger from './logger';
import {SocVulnResponse, Vulnerability} from "./types";
let axios = require('axios');
let util = require('util');
let getDeps = require('./deps');
function statPath(fpath: string) {
try {
return fs.statSync(fpath);
} catch (e) {
return false;
}
}
function printSingleVuln(vuln: Vulnerability) {
logger.error(util.format("✗ %s severity vulnerability (%s - %s) found on %s@%s",
vuln.severity, vuln.title, vuln.cve, vuln.packageName, vuln.version));
if (vuln.from) {
let fromArr = vuln.from;
let fromStr = "";
fromArr.forEach((fromDep) => {
fromStr += fromDep + " > ";
});
fromStr = fromStr.substr(0, fromStr.length - 3);
logger.log(util.format("- from: %s", fromStr));
}
if (vuln.target_version.length) {
logger.info(util.format("! Fix version %s", JSON.stringify(vuln.target_version)));
}
logger.log('');
}
function checkProject(root: string, endpoint?: string, severity?: string, onlyProvenance?: boolean, withDev?: boolean) {
let envEndpoint = process.env.MOSEC_ENDPOINT;
if (!!envEndpoint) {
endpoint = envEndpoint;
}
if (!endpoint) {
return Promise.reject(new Error("endpoint not set. use --endpoint param or MOSEC_ENDPOINT env."));
}
if (statPath(path.resolve(root)) === false) {
return Promise.reject(new Error("dir is not exists: " + root));
}
if (statPath(path.resolve(root, 'package.json')) === false) {
return Promise.reject(new Error(root + " is not a node project."));
}
if (statPath(path.resolve(root, 'node_modules')) === false) {
return Promise.reject(new Error("run 'npm install' first, please."));
}
return getDeps(root)
.then((deps) => getDeps.simplifyDeps(deps, onlyProvenance || false, withDev || false))
.then((deps) => {
// feed extra info
set(deps, 'type', 'npm');
set(deps, 'language', 'javascript');
set(deps, 'severityLevel', severity || 'High');
return axios({
method: 'POST',
url: endpoint,
headers: {
'Content-Type': 'application/json',
},
data: deps,
timeout: 15 * 1000,
}).then((res) => {
let responseJson = res.data as SocVulnResponse;
if (responseJson.ok) {
logger.info(
util.format("✓ Tested %s dependencies for known vulnerabilities, no vulnerable paths found.",
responseJson.dependencyCount));
return Promise.resolve();
}
if (responseJson.vulnerabilities) {
responseJson.vulnerabilities.forEach((vuln) => {
printSingleVuln(vuln);
});
logger.warn(
util.format("Tested %s dependencies for known vulnerabilities, found %d vulnerable paths.",
responseJson.dependencyCount, responseJson.vulnerabilities.length));
}
}).catch((error) => {
if (error.response) {
throw new Error("API return data format error.");
}
});
}).catch((error) => {
return Promise.reject(error);
});
}

113
src/pkg-resolve.ts Normal file
View File

@@ -0,0 +1,113 @@
/*
Copyright 2017 Snyk Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import set = Reflect.set;
export { resolvePkg, sync };
import * as fs from 'then-fs';
import * as path from 'path';
import * as debugModule from 'debug';
let debug = debugModule('mosec:resolve');
function resolvePkg(name, basedir) {
if (!basedir) {
basedir = process.cwd();
}
let filename = path.resolve(basedir, 'node_modules', name, 'package.json');
debug('%s: %s', name, filename);
return fs.stat(filename).then(function (stat) {
if (stat.isFile()) {
return path.dirname(filename);
}
}).catch(function (error) {
debug('%s: not found on %s (root? %s)', name, basedir, isRoot(basedir));
if (isRoot(basedir)) {
debug('at root');
error = new Error('package not found ' + name);
error.code = 'NO_PACKAGE_FOUND';
throw error;
}
}).then(function (dir) {
if (dir) {
debug('%s: FOUND AT %s', name, dir);
return dir;
}
debug('%s: cycling down', name);
return resolvePkg(name, path.resolve(basedir, '..'));
});
}
function sync(name, basedir) {
if (!basedir) {
basedir = process.cwd();
}
let filename = path.resolve(basedir, 'node_modules', name, 'package.json');
debug('%s: %s', name, filename);
let isFile = function (file) {
let stat;
try {
stat = fs.statSync(file);
} catch (error) {
if (error && error.code === 'ENOENT') {
return false;
}
}
return stat.isFile() || stat.isFIFO();
};
if (isFile(filename)) {
debug('%s: FOUND AT %s', name, filename);
return path.dirname(filename);
}
if (isRoot(basedir)) {
debug('%s: not found on %s (now at root)', name, filename);
let error = new Error('package not found ' + name);
set(error, 'code', 'NO_PACKAGE_FOUND');
throw error;
}
debug('%s: cycling down', name);
return sync(name, path.resolve(basedir, '..'));
}
function isRoot(dir) {
let parsed = parse(dir);
return parsed.root === parsed.dir && !parsed.base;
}
// FIXME determine whether this would work properly on windows in 0.10
function parse(dir) {
/* istanbul ignore else */
// jscs:disable requireEarlyReturn
if (path.parse) {
return path.parse(dir);
} else {
let split = dir.split(path.sep);
let root = split[0] + path.sep;
return {
base: split[1],
root: root,
dir: dir,
};
}
// jscs:enable requireEarlyReturn
}

80
src/try-require.ts Normal file
View File

@@ -0,0 +1,80 @@
/*
Copyright 2017 Snyk Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
export = tryRequire;
import * as fs from 'then-fs';
import * as path from 'path';
import * as debugModule from 'debug';
import * as cloneDeep from 'lodash.clonedeep';
import * as lru from 'lru-cache';
let options = { max: 100, maxAge: 1000 * 60 * 60 };
let cache = lru(options);
let debug = debugModule('mosec:resolve:try-require');
function tryRequire(filename) {
let cached = cache.get(filename);
if (cached) {
let res = cloneDeep(cached);
/* istanbul ignore else */
if (process.env.TAP) {
res.__cached = true;
}
return Promise.resolve(res);
}
return fs.readFile(filename, 'utf8').then(function (pkgStr) {
let leadingBOM = '';
if (pkgStr && pkgStr[0] === '\ufeff') {
// String starts with UTF BOM. Remove it so that JSON.parse doesn't
// stumble, but remember it for later use.
pkgStr = pkgStr.slice(1);
leadingBOM = '\ufeff';
}
let pkg = JSON.parse(pkgStr);
pkg.leading = leadingBOM + pkgStr.match(/^(\s*){/)[1];
pkg.trailing = pkgStr.match(/}(\s*)$/)[1];
return pkg;
}).catch(function (e) {
debug('tryRequire silently failing on %s', e.message);
return null;
}).then(function (pkg) {
if (!pkg) {
return pkg;
}
// fixes potential issues later on
if (!pkg.devDependencies) {
pkg.devDependencies = {};
}
if (!pkg.dependencies) {
pkg.dependencies = {};
}
if (!pkg.name) {
pkg.name = path.basename(path.dirname(filename));
}
pkg.__filename = filename;
return pkg;
}).then(function (pkg) {
cache.set(filename, pkg);
return cloneDeep(pkg);
});
}
tryRequire.cache = cache;

83
src/types.ts Normal file
View File

@@ -0,0 +1,83 @@
/*
Copyright 2017 Snyk Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
export type DepType = 'extraneous' | 'optional'| 'prod' | 'dev';
export interface DepSpecDict {
[name: string]: string;
}
export interface DepExpandedDict {
[name: string]: PackageExpanded;
}
export interface AbbreviatedVersion {
readonly name: string;
readonly version: string;
readonly deprecated?: string;
readonly dependencies?: {readonly [name: string]: string};
readonly devDependencies?: {readonly [name: string]: string};
readonly peerDependencies?: {readonly [name: string]: string};
readonly directories?: readonly string[];
readonly [key: string]: unknown;
}
// Intermediate type used during parsing
export interface PackageJsonEnriched extends AbbreviatedVersion {
full: string;
from: string[];
}
export interface HasDependencySpecs {
readonly dependencies?: {readonly [name: string]: string};
readonly devDependencies?: {readonly [name: string]: string};
}
// Similar to package-json.AbbreviatedVersion, but with deps expanded
export interface PackageExpanded {
name: string;
version: string;
dep: string; // this is the npm version range spec that was resolved to `version`
license: string;
depType: DepType;
hasDevDependencies: boolean;
full: string;
from: string[];
__devDependencies: DepSpecDict;
__dependencies: DepSpecDict;
__filename: string;
devDependencies: DepExpandedDict;
dependencies: DepExpandedDict;
__used?: boolean;
problems?: string[];
extraneous?: boolean;
}
export interface Vulnerability {
id: string;
severity: string;
packageName: string;
version: string;
from: string[];
target_version: string[];
title: string;
cve: string;
}
export interface SocVulnResponse {
ok: boolean;
dependencyCount: bigint;
vulnerabilities: Vulnerability[];
}

86
test/deps.test.js Normal file
View File

@@ -0,0 +1,86 @@
/*
Copyright 2017 Snyk Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
let test = require('tap-only');
let deps = require('../src/deps');
let path = require('path');
let npm2fixture = path.resolve(__dirname,
'fixtures/bundle/node_modules/snyk-resolve-deps-fixtures',
'node_modules/uglify-package');
let npm3fixture = path.resolve(__dirname,
'fixtures/bundle/node_modules/snyk-resolve-deps-fixtures');
test('deps - npm@3', function (t) {
deps(npm3fixture).then(function (res) {
t.ok(!!res, 'package loaded');
}).catch(t.fail).then(t.end);
});
// fixture uglify-package does not exist, and newer versions of npm care
const legacyNpm = Number(
require('child_process').execSync('npm -v').toString().split('.', 1)[0]
) < 5;
legacyNpm && test('deps - with uglify-package', function (t) {
deps(npm2fixture).then(function (res) {
t.equal(res.name, 'uglify-package', 'package name matches');
t.type(res.dependencies, 'object', 'has dependencies');
t.equal(Object.keys(res.dependencies).length, 3, 'has 3 file dependencies');
let ugdeep = res.dependencies['ug-deep'];
t.equal(ugdeep.name, 'ug-deep', 'ug-deep exists');
}).catch(function (e) {
t.fail(e.stack);
}).then(t.end);
});
legacyNpm && test('deps - with extraFields', function (t) {
deps(npm2fixture, null, { extraFields: [ 'main', 'super-bogus-field' ]}).then(function (res) {
t.equal(res.main, 'index.js', 'includes extraFields');
t.equal(res['super-bogus-field'], null, 'produces null for empty extraFields fields');
}).catch(function (e) {
t.fail(e.stack);
}).then(t.end);
});
test('deps - throws without path', function (t) {
deps().then(function () {
t.fail('without a path deps should not succeed');
}).catch(function (e) {
t.type(e, 'Error', 'error received');
t.equal(e.message, 'module path must be a string', 'error is correct');
}).then(t.end);
});
// See test/fixtures/pkg-yarn-renamed-deps/README.md
test('deps - yarn with renamed dep', function (t) {
deps('fixtures/pkg-yarn-renamed-deps').then(function (res) {
t.equal(res.name, 'pkg-renamed-dep', 'package name matches');
t.type(res.dependencies, 'object', 'has dependencies');
t.equal(Object.keys(res.dependencies).length, 2, 'has 2 deps');
}).catch(function (e) {
t.fail(e.stack);
}).then(t.end);
});
test('deps - pkg undefined deps', function (t) {
deps('fixtures/pkg-undef-deps').then(function (res) {
t.equal(res.name, 'pkg-undef-deps', 'package name matches');
t.type(res.dependencies, 'object', 'has dependencies');
}).catch(function (e) {
t.fail(e.stack);
}).then(t.end);
});

15
test/fixtures/bundle/WARNING.md vendored Normal file
View File

@@ -0,0 +1,15 @@
# WARNING
`snyk-resolve-deps-fixtures` runs a `postinstall` command which adds
orphaned packages. On purpose.
`npm` is *gagging* to clean this up for you, and will do so if you
so much as look at this directory, and randomly anyway.
This breaks all the tests in confusing ways. `undefsafe` and `debug`,
but of course, you won't see those in the test output.
As of `6.13.0`, you can re-run the `postinstall`, without breaking
anything else, by running `npm i --save-dev snyk-resolve-deps-fixtures`.
I added it to every `package.json` in the hope that at least one of
them would run last, and there wouldn't be a race.

2407
test/fixtures/bundle/package-lock.json generated vendored Normal file

File diff suppressed because it is too large Load Diff

21
test/fixtures/bundle/package.json vendored Normal file
View File

@@ -0,0 +1,21 @@
{
"name": "bundle",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"massive-massive-hack-see-warnings": "npm --prefix node_modules/snyk-resolve-deps-fixtures install --no-save --no-safe-dev undefsafe debug",
"postinstall": "npm run massive-massive-hack-see-warnings",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"sinon": "7.5.0",
"snyk-resolve-deps-fixtures": "^1.1.6",
"tap": "12.7.0"
},
"dependencies": {
"snyk-tree": "1.0.0"
}
}

1023
test/fixtures/not-found.json vendored Normal file

File diff suppressed because it is too large Load Diff

4839
test/fixtures/oui.json vendored Normal file

File diff suppressed because it is too large Load Diff

657
test/fixtures/out.json vendored Normal file
View File

@@ -0,0 +1,657 @@
{
"name": "snyk-resolve-deps-fixtures",
"version": "1.0.11",
"license": "UNLICENSED",
"depType": "extraneous",
"hasDevDependencies": true,
"full": "snyk-resolve-deps-fixtures@1.0.11",
"dependencies": {
"@remy/npm-tree": {
"name": "@remy/npm-tree",
"version": "1.0.2",
"license": "MIT",
"depType": "prod",
"hasDevDependencies": true,
"full": "@remy/npm-tree@1.0.2",
"dependencies": {
"archy": {
"name": "archy",
"version": "1.0.0",
"full": "archy@1.0.0",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "MIT",
"dep": "^1.0.0",
"dependencies": {}
}
},
"dep": "^1.0.2"
},
"@remy/vuln-test": {
"name": "@remy/vuln-test",
"version": "1.0.1",
"full": "@remy/vuln-test@1.0.1",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "ISC",
"dep": "^1.0.1",
"dependencies": {
"semver": {
"name": "semver",
"version": "2.3.2",
"full": "semver@2.3.2",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "BSD",
"dep": "^2.3.2",
"dependencies": {}
}
}
},
"dref": {
"name": "dref",
"version": "0.0.6",
"full": "dref@0.0.6",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "none",
"dep": "0.0.6",
"dependencies": {
"structr": {
"name": "structr",
"version": "0.2.4",
"full": "structr@0.2.4",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "none",
"dep": "0.2.x",
"dependencies": {}
},
"dref": {
"name": "dref",
"version": "0.0.6",
"full": "dref@0.0.6",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "none",
"dep": "0.0.6",
"dependencies": {
"structr": {
"name": "structr",
"version": "0.2.4",
"full": "structr@0.2.4",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "none",
"dep": "0.2.x",
"__from": [
"snyk-resolve-deps-fixtures",
"dref",
"structr"
],
"__devDependencies": {},
"__dependencies": {},
"__filename": "/Users/remy/Sites/snyk-resolve-deps-npm@2/node_modules/snyk-resolve-deps-fixtures/node_modules/dref/node_modules/structr/package.json",
"dependencies": false,
"__used": true
},
"type-component": {
"name": "type-component",
"version": "0.0.1",
"full": "type-component@0.0.1",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "none",
"dep": "0.0.x",
"__from": [
"snyk-resolve-deps-fixtures",
"dref",
"type-component"
],
"__devDependencies": {},
"__dependencies": {},
"__filename": "/Users/remy/Sites/snyk-resolve-deps-npm@2/node_modules/snyk-resolve-deps-fixtures/node_modules/dref/node_modules/type-component/package.json",
"dependencies": false,
"__used": true
}
}
},
"type-component": {
"name": "type-component",
"version": "0.0.1",
"full": "type-component@0.0.1",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "none",
"dep": "0.0.x",
"dependencies": {}
}
}
},
"semver-rs-demo": {
"name": "semver-rs-demo",
"version": "1.0.0",
"full": "semver-rs-demo@1.0.0",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "ISC",
"dep": "^1.0.0",
"dependencies": {
"semver": {
"name": "semver",
"version": "2.3.2",
"full": "semver@2.3.2",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "BSD",
"dep": "^2.3.2",
"dependencies": {}
}
}
},
"uglify-package": {
"name": "uglify-package",
"version": "1.0.0",
"full": "uglify-package@1.0.0",
"valid": true,
"depType": "prod",
"snyk": "/Users/remy/Sites/snyk-resolve-deps-npm@2/node_modules/snyk-resolve-deps-fixtures/node_modules/uglify-package",
"license": "ISC",
"dep": "*",
"dependencies": {
"ug-deep": {
"name": "ug-deep",
"version": "1.0.0",
"full": "ug-deep@1.0.0",
"valid": true,
"depType": "prod",
"snyk": "/Users/remy/Sites/snyk-resolve-deps-npm@2/node_modules/snyk-resolve-deps-fixtures/node_modules/uglify-package/node_modules/ug-deep",
"license": "ISC",
"dep": "^1.0.0",
"dependencies": {
"uglify-js": {
"name": "uglify-js",
"version": "2.4.24",
"full": "uglify-js@2.4.24",
"valid": false,
"depType": "prod",
"snyk": false,
"license": "BSD",
"dep": null,
"dependencies": {
"async": {
"name": "async",
"version": "0.2.10",
"full": "async@0.2.10",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "MIT",
"dep": "~0.2.6",
"dependencies": {}
},
"source-map": {
"name": "source-map",
"version": "0.1.34",
"full": "source-map@0.1.34",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "BSD",
"dep": "0.1.34",
"dependencies": {
"amdefine": {
"name": "amdefine",
"version": "1.0.0",
"full": "amdefine@1.0.0",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "BSD-3-Clause AND MIT",
"dep": ">=0.0.4",
"dependencies": {}
}
}
},
"uglify-to-browserify": {
"name": "uglify-to-browserify",
"version": "1.0.2",
"full": "uglify-to-browserify@1.0.2",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "MIT",
"dep": "~1.0.0",
"dependencies": {}
},
"yargs": {
"name": "yargs",
"version": "3.5.4",
"full": "yargs@3.5.4",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "MIT/X11",
"dep": "~3.5.4",
"dependencies": {
"camelcase": {
"name": "camelcase",
"version": "1.2.1",
"full": "camelcase@1.2.1",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "MIT",
"dep": "^1.0.2",
"dependencies": {}
},
"decamelize": {
"name": "decamelize",
"version": "1.1.2",
"full": "decamelize@1.1.2",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "MIT",
"dep": "^1.0.0",
"dependencies": {
"escape-string-regexp": {
"name": "escape-string-regexp",
"version": "1.0.4",
"full": "escape-string-regexp@1.0.4",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "MIT",
"dep": "^1.0.4",
"dependencies": {}
}
}
},
"window-size": {
"name": "window-size",
"version": "0.1.0",
"full": "window-size@0.1.0",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "MIT",
"dep": "0.1.0",
"dependencies": {}
},
"wordwrap": {
"name": "wordwrap",
"version": "0.0.2",
"full": "wordwrap@0.0.2",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "MIT/X11",
"dep": "0.0.2",
"dependencies": {}
}
}
}
}
}
}
},
"ug-deep-alt": {
"name": "ug-deep-alt",
"version": "1.0.0",
"full": "ug-deep-alt@1.0.0",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "ISC",
"dep": "^1.0.0",
"dependencies": {
"uglify-js": {
"name": "uglify-js",
"version": "2.4.24",
"full": "uglify-js@2.4.24",
"valid": false,
"depType": "prod",
"snyk": false,
"license": "BSD",
"dep": null,
"dependencies": {
"async": {
"name": "async",
"version": "0.2.10",
"full": "async@0.2.10",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "MIT",
"dep": "~0.2.6",
"__from": [
"snyk-resolve-deps-fixtures",
"uglify-package",
"uglify-js",
"async"
],
"__devDependencies": {
"nodeunit": ">0.0.0",
"uglify-js": "1.2.x",
"nodelint": ">0.0.0"
},
"__dependencies": {},
"__filename": "/Users/remy/Sites/snyk-resolve-deps-npm@2/node_modules/snyk-resolve-deps-fixtures/node_modules/uglify-package/node_modules/uglify-js/node_modules/async/package.json",
"dependencies": false,
"__used": true
},
"source-map": {
"name": "source-map",
"version": "0.1.34",
"full": "source-map@0.1.34",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "BSD",
"dep": "0.1.34",
"__from": [
"snyk-resolve-deps-fixtures",
"uglify-package",
"uglify-js",
"source-map"
],
"__devDependencies": {
"dryice": ">=0.4.8"
},
"__dependencies": {
"amdefine": ">=0.0.4"
},
"__filename": "/Users/remy/Sites/snyk-resolve-deps-npm@2/node_modules/snyk-resolve-deps-fixtures/node_modules/uglify-package/node_modules/uglify-js/node_modules/source-map/package.json",
"dependencies": {
"amdefine": {
"name": "amdefine",
"version": "1.0.0",
"full": "amdefine@1.0.0",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "BSD-3-Clause AND MIT",
"dep": ">=0.0.4",
"__from": [
"snyk-resolve-deps-fixtures",
"uglify-package",
"uglify-js",
"source-map",
"amdefine"
],
"__devDependencies": {},
"__dependencies": {},
"__filename": "/Users/remy/Sites/snyk-resolve-deps-npm@2/node_modules/snyk-resolve-deps-fixtures/node_modules/uglify-package/node_modules/uglify-js/node_modules/source-map/node_modules/amdefine/package.json",
"dependencies": false,
"__used": true
}
},
"__used": true
},
"uglify-to-browserify": {
"name": "uglify-to-browserify",
"version": "1.0.2",
"full": "uglify-to-browserify@1.0.2",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "MIT",
"dep": "~1.0.0",
"__from": [
"snyk-resolve-deps-fixtures",
"uglify-package",
"uglify-js",
"uglify-to-browserify"
],
"__devDependencies": {
"uglify-js": "~2.4.0",
"source-map": "~0.1.27"
},
"__dependencies": {},
"__filename": "/Users/remy/Sites/snyk-resolve-deps-npm@2/node_modules/snyk-resolve-deps-fixtures/node_modules/uglify-package/node_modules/uglify-js/node_modules/uglify-to-browserify/package.json",
"dependencies": false,
"__used": true
},
"yargs": {
"name": "yargs",
"version": "3.5.4",
"full": "yargs@3.5.4",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "MIT/X11",
"dep": "~3.5.4",
"__from": [
"snyk-resolve-deps-fixtures",
"uglify-package",
"uglify-js",
"yargs"
],
"__devDependencies": {
"blanket": "^1.1.6",
"chai": "^1.10.0",
"coveralls": "^2.11.2",
"hashish": "0.0.4",
"mocha": "2.1.0",
"mocha-lcov-reporter": "0.0.1",
"mocoverage": "^1.0.0"
},
"__dependencies": {
"camelcase": "^1.0.2",
"decamelize": "^1.0.0",
"window-size": "0.1.0",
"wordwrap": "0.0.2"
},
"__filename": "/Users/remy/Sites/snyk-resolve-deps-npm@2/node_modules/snyk-resolve-deps-fixtures/node_modules/uglify-package/node_modules/uglify-js/node_modules/yargs/package.json",
"dependencies": {
"camelcase": {
"name": "camelcase",
"version": "1.2.1",
"full": "camelcase@1.2.1",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "MIT",
"dep": "^1.0.2",
"__from": [
"snyk-resolve-deps-fixtures",
"uglify-package",
"uglify-js",
"yargs",
"camelcase"
],
"__devDependencies": {
"ava": "0.0.4"
},
"__dependencies": {},
"__filename": "/Users/remy/Sites/snyk-resolve-deps-npm@2/node_modules/snyk-resolve-deps-fixtures/node_modules/uglify-package/node_modules/uglify-js/node_modules/yargs/node_modules/camelcase/package.json",
"dependencies": false,
"__used": true
},
"decamelize": {
"name": "decamelize",
"version": "1.1.2",
"full": "decamelize@1.1.2",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "MIT",
"dep": "^1.0.0",
"__from": [
"snyk-resolve-deps-fixtures",
"uglify-package",
"uglify-js",
"yargs",
"decamelize"
],
"__devDependencies": {
"ava": "*",
"xo": "*"
},
"__dependencies": {
"escape-string-regexp": "^1.0.4"
},
"__filename": "/Users/remy/Sites/snyk-resolve-deps-npm@2/node_modules/snyk-resolve-deps-fixtures/node_modules/uglify-package/node_modules/uglify-js/node_modules/yargs/node_modules/decamelize/package.json",
"dependencies": {
"escape-string-regexp": {
"name": "escape-string-regexp",
"version": "1.0.4",
"full": "escape-string-regexp@1.0.4",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "MIT",
"dep": "^1.0.4",
"__from": [
"snyk-resolve-deps-fixtures",
"uglify-package",
"uglify-js",
"yargs",
"decamelize",
"escape-string-regexp"
],
"__devDependencies": {
"ava": "*",
"xo": "*"
},
"__dependencies": {},
"__filename": "/Users/remy/Sites/snyk-resolve-deps-npm@2/node_modules/snyk-resolve-deps-fixtures/node_modules/uglify-package/node_modules/uglify-js/node_modules/yargs/node_modules/decamelize/node_modules/escape-string-regexp/package.json",
"dependencies": false,
"__used": true
}
},
"__used": true
},
"window-size": {
"name": "window-size",
"version": "0.1.0",
"full": "window-size@0.1.0",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "MIT",
"dep": "0.1.0",
"__from": [
"snyk-resolve-deps-fixtures",
"uglify-package",
"uglify-js",
"yargs",
"window-size"
],
"__devDependencies": {},
"__dependencies": {},
"__filename": "/Users/remy/Sites/snyk-resolve-deps-npm@2/node_modules/snyk-resolve-deps-fixtures/node_modules/uglify-package/node_modules/uglify-js/node_modules/yargs/node_modules/window-size/package.json",
"dependencies": false,
"__used": true
},
"wordwrap": {
"name": "wordwrap",
"version": "0.0.2",
"full": "wordwrap@0.0.2",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "MIT/X11",
"dep": "0.0.2",
"__from": [
"snyk-resolve-deps-fixtures",
"uglify-package",
"uglify-js",
"yargs",
"wordwrap"
],
"__devDependencies": {
"expresso": "=0.7.x"
},
"__dependencies": {},
"__filename": "/Users/remy/Sites/snyk-resolve-deps-npm@2/node_modules/snyk-resolve-deps-fixtures/node_modules/uglify-package/node_modules/uglify-js/node_modules/yargs/node_modules/wordwrap/package.json",
"dependencies": false,
"__used": true
}
},
"__used": true
}
}
},
"ug-no-deps": {
"name": "ug-no-deps",
"version": "1.0.0",
"full": "ug-no-deps@1.0.0",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "none",
"dep": "*",
"dependencies": {}
}
}
}
}
},
"debug": {
"name": "debug",
"version": "2.2.0",
"full": "debug@2.2.0",
"valid": false,
"depType": "dev",
"snyk": false,
"license": "MIT",
"dep": "^2.2.0",
"dependencies": {
"ms": {
"name": "ms",
"version": "0.7.1",
"full": "ms@0.7.1",
"valid": true,
"depType": "prod",
"snyk": false,
"license": "none",
"dep": "0.7.1",
"dependencies": {}
}
}
},
"undefsafe": {
"name": "undefsafe",
"version": "0.0.3 null",
"full": "undefsafe@0.0.3",
"valid": false,
"depType": "extraneous",
"snyk": false,
"license": "MIT / http://rem.mit-license.org",
"dep": null,
"__from": [
"snyk-resolve-deps-fixtures",
"undefsafe"
],
"__devDependencies": {
"mocha": "~1.16.2"
},
"__dependencies": {},
"__filename": "/Users/remy/Sites/snyk-resolve-deps-npm@2/node_modules/snyk-resolve-deps-fixtures/node_modules/undefsafe/package.json",
"dependencies": false,
"extraneous": true,
"problems": [
"\u001b[40m\u001b[32mextraneous\u001b[39m\u001b[49m: snyk-resolve-deps-fixtures > undefsafe > undefsafe@0.0.3"
]
}
},
"problems": [
"missing: ansicolors@^0.3.2, required by @remy/npm-tree@1.0.2",
"missing: semantic-release@^4.3.5, required by @remy/npm-tree@1.0.2",
"missing: tap@^5.1.1, required by @remy/npm-tree@1.0.2",
"\u001b[40m\u001b[32mextraneous\u001b[39m\u001b[49m: snyk-resolve-deps-fixtures > undefsafe > undefsafe@0.0.3"
]
}

View File

@@ -0,0 +1,5 @@
{
"name": "pkg-missing-deps",
"version": "1.0.0",
"dependencies": { "doesnotexist": "1.0.0" }
}

View File

@@ -0,0 +1,6 @@
{
"version": "0.0.1",
"dependencies": {
"undefsafe": "^1.0.0"
}
}

View File

@@ -0,0 +1,7 @@
{
"name": "pkg-not-install",
"version": "0.0.1",
"dependencies": {
"debug": "^4.2.0"
}
}

View File

@@ -0,0 +1,7 @@
{
"name": "pkg-undef-deps",
"version": "0.0.1",
"devDependencies": {
"debug": "^2.2.0"
}
}

View File

@@ -0,0 +1,4 @@
{
"name": "pkg-undef-deps",
"version": "0.0.1"
}

View File

@@ -0,0 +1,14 @@
Both npm and Yarn support "GitHub URL" kind of dependency:
"somepackage": "githubuser/somepackage",
It is possible to actually use a different name for the package:
"myname": "githubuser/somepackage",
Npm claims to support that (https://docs.npmjs.com/files/package.json#github-urls), but in practice will just not pull the dependency.
But Yarn actually allows it: https://yarnpkg.com/lang/en/docs/cli/add/#toc-yarn-add-alias
This fixture helps to test that we are actually supporting such renamed dependencies
during the resolution.

View File

@@ -0,0 +1,8 @@
{
"name": "pkg-renamed-dep",
"version": "1.0.0",
"dependencies": {
"githubrenamed": "stevemao/left-pad",
"npmrenamed": "npm:ms"
}
}

View File

@@ -0,0 +1,12 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
githubrenamed@stevemao/left-pad:
version "1.3.0"
resolved "https://codeload.github.com/stevemao/left-pad/tar.gz/cc0aa707ca1a3158f392a689142d64691bc12a53"
"npmrenamed@npm:ms":
version "2.1.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==

44
test/main.test.js Normal file
View File

@@ -0,0 +1,44 @@
/*
Copyright 2017 Snyk Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
let test = require('tap-only');
let check = require("../src/main");
test('main - empty dir', function (t) {
check.checkProject('fixtures/empty-dir', 'fake endpoint').then(function (res) {
t.fail('empty dir should not succeed', res);
}).catch(function (e) {
t.type(e, 'Error', 'error received');
t.notEqual(e.message.indexOf(' is not a node project'), -1, 'error is correct');
}).then(t.end);
});
test('main - none exist dir', function (t) {
check.checkProject('fixtures/im_not_exist', 'fake endpoint').then(function (res) {
t.fail('none exist dir should not succeed', res);
}).catch(function (e) {
t.type(e, 'Error', 'error received');
t.notEqual(e.message.indexOf('dir is not exists'), -1, 'error is correct');
}).then(t.end);
});
test('main - pkg-not-install', function (t) {
check.checkProject('fixtures/pkg-not-install', 'fake endpoint').then(function (res) {
t.fail('not install pkg dir should not succeed.', res)
}).catch(function (e) {
t.type(e, 'Error', 'error received');
t.notEqual(e.message.indexOf("run 'npm install' first"), -1, 'error is correct')
}).then(t.end);
});

14
test/manual.js Executable file
View File

@@ -0,0 +1,14 @@
let resolve = require('../src/deps');
function main() {
let target = process.argv[2];
resolve(target).then(function (result) {
console.log(JSON.stringify(result, null, 2));
}).catch(function (error) {
console.log('Error:', error.stack);
});
};
main();

18
tsconfig.json Normal file
View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"outDir": "./dist",
"pretty": true,
"target": "es2015",
"lib": [
"es2015"
],
"module": "commonjs",
"allowJs": true,
"sourceMap": true,
"strict": true,
"noImplicitAny": false // Needed to compile tap and ansi-escapes
},
"include": [
"./src/**/*"
]
}

32
tslint.json Normal file
View File

@@ -0,0 +1,32 @@
{
"extends": "tslint:recommended",
"rules": {
"max-line-length": {
"options": [120]
},
"trailing-comma": [true, {"multiline": "always"}],
"triple-equals": true,
"curly": true,
"indent": [true, "spaces", 4],
"interface-name": false,
"object-literal-sort-keys": false,
"ordered-imports": false,
// "quotemark": [true, "single", "avoid-escape", "avoid-template"],
"semicolon": [true, "always"],
"no-console": [false],
"quotemark": [false],
"no-default-export": true,
"no-var-requires": false,
"prefer-const": false,
"only-arrow-functions": false,
"space-before-function-paren": false,
"object-literal-shorthand": false,
"prefer-for-of": false
},
"linterOptions": {
"exclude": [
"./text/fixtures",
"./node_modules"
]
}
}