Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(registry): Add support for specifying registry #47

Merged
merged 9 commits into from
Mar 6, 2019
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ before_install:
| -a | --advisories | Vulnerable advisory ids to whitelist from preventing integration (default `none`) |
| -w | --whitelist | Vulnerable modules to whitelist from preventing integration (default `none`) |
| -d | --directory | The directory containing the package.json to audit. (default `./`) |
| | --registry | The registry to resolve packages by name and version (default to unspecified) |
| | --config | Path to JSON config file |

### (_Optional_) Config file specification
Expand All @@ -96,7 +97,8 @@ A config file can manage auditing preferences `audit-ci`. The config file's keys
"summary": <boolean>, // [Optional] defaults `true`
"package-manager": <string>, // [Optional] defaults `"auto"`
"advisories": <number[]>, // [Optional] defaults `[]`
"whitelist": <string[]> // [Optional] defaults `[]`
"whitelist": <string[]>, // [Optional] defaults `[]`,
"registry": <string> // [Optional] defaults `undefined`
}
```

Expand Down Expand Up @@ -140,7 +142,8 @@ audit-ci
"low": true,
"package-manager": "auto",
"advisories": [100, 101],
"whitelist": ["example1", "example2"]
"whitelist": ["example1", "example2"],
"registry": "https://registry.npmjs.org"
}
```

Expand Down
5 changes: 5 additions & 0 deletions lib/audit-ci.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ const { argv } = yargs
describe: 'The directory containing the package.json to audit',
type: 'string',
},
registry: {
default: undefined,
describe: 'The registry to resolve packages by name and version',
type: 'string',
},
})
.help('help');

Expand Down
14 changes: 10 additions & 4 deletions lib/npm-auditer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
const { runProgram, reportAudit } = require('./common');
const Model = require('./Model');

function runNpmAudit(directory) {
function runNpmAudit(config) {
const { directory, registry } = config;

const stdoutBuffer = [];
function outListener(line) {
stdoutBuffer.push(line);
Expand All @@ -18,6 +20,9 @@ function runNpmAudit(directory) {
}

const args = ['audit', '--json'];
if (registry) {
args.push('--registry', registry);
}
const options = { cwd: directory };
return Promise.resolve()
.then(() => runProgram('npm', args, options, outListener, errListener))
Expand All @@ -32,7 +37,7 @@ function runNpmAudit(directory) {
try {
return JSON.parse(stdout);
} catch (e) {
console.log(stdout);
console.error(stdout);
throw e;
}
});
Expand All @@ -53,17 +58,18 @@ function printReport(parsedOutput, report) {
/**
* Audit your NPM project!
*
* @param {{directory: string, report: { full?: boolean, summary?: boolean }, whitelist: string[], advisories: string[], levels: { low: boolean, moderate: boolean, high: boolean, critical: boolean }}} config
* @param {{directory: string, report: { full?: boolean, summary?: boolean }, whitelist: string[], advisories: string[], registry: string, levels: { low: boolean, moderate: boolean, high: boolean, critical: boolean }}} config
* `directory`: the directory containing the package.json to audit.
* `report`: report level: `full` for full report, `summary` for summary
* `whitelist`: a list of packages that should not break the build if their vulnerability is found.
* `advisories`: a list of advisory ids that should not break the build if found.
* `registry`: the registry to resolve packages by name and version.
* `levels`: the vulnerability levels to fail on, if `moderate` is set `true`, `high` and `critical` should be as well.
* @returns {Promise<any>} Returns the audit report summary on resolve, `Error` on rejection.
*/
function audit(config, reporter = reportAudit) {
return Promise.resolve()
.then(() => runNpmAudit(config.directory))
.then(() => runNpmAudit(config))
.then(parsedOutput => printReport(parsedOutput, config.report))
.then(parsedOutput =>
reporter(new Model(config).load(parsedOutput), config, parsedOutput)
Expand Down
26 changes: 24 additions & 2 deletions lib/yarn-auditer.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ const { reportAudit, runProgram } = require('./common');
const Model = require('./Model');

const MINIMUM_YARN_VERSION = '1.12.3';
/**
* Change this to the appropriate version when
* yarn audit --registry is supported:
* @see https://github.com/yarnpkg/yarn/issues/7012
*/
const MINIMUM_YARN_AUDIT_REGISTRY_VERSION = '99.99.99';

function getYarnVersion() {
const version = childProcess
Expand All @@ -22,20 +28,25 @@ function yarnSupportsAudit(yarnVersion) {
return semver.gte(yarnVersion, MINIMUM_YARN_VERSION);
}

function yarnAuditSupportsRegistry(yarnVersion) {
return semver.gte(yarnVersion, MINIMUM_YARN_AUDIT_REGISTRY_VERSION);
}

/**
* Audit your Yarn project!
*
* @param {{directory: string, report: { full?: boolean, summary?: boolean }, whitelist: string[], advisories: string[], levels: { low: boolean, moderate: boolean, high: boolean, critical: boolean }}} config
* @param {{directory: string, report: { full?: boolean, summary?: boolean }, whitelist: string[], advisories: string[], registry: string, levels: { low: boolean, moderate: boolean, high: boolean, critical: boolean }}} config
* `directory`: the directory containing the package.json to audit.
* `report`: report level: `full` for full report, `summary` for summary
* `whitelist`: a list of packages that should not break the build if their vulnerability is found.
* `advisories`: a list of advisory ids that should not break the build if found.
* `registry`: the registry to resolve packages by name and version.
* `levels`: the vulnerability levels to fail on, if `moderate` is set `true`, `high` and `critical` should be as well.
* @returns {Promise<any>} Returns the audit report summary on resolve, `Error` on rejection.
*/
function audit(config, reporter = reportAudit) {
return Promise.resolve().then(() => {
const { report, whitelist } = config;
const { registry, report, whitelist } = config;
let missingLockFile = false;
const model = new Model(config);

Expand Down Expand Up @@ -90,6 +101,17 @@ function audit(config, reporter = reportAudit) {
}
const options = { cwd: config.directory };
const args = ['audit', '--json'];
if (registry) {
const auditRegistrySupported = yarnAuditSupportsRegistry(yarnVersion);
if (auditRegistrySupported) {
args.push('--registry', registry);
} else {
console.warn(
'\x1b[33m%s\x1b[0m',
'Yarn audit does not support the registry flag yet.'
);
}
}
return runProgram('yarn', args, options, outListener, errListener).then(
() => {
if (missingLockFile) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
],
"scripts": {
"lint": "eslint -c ./.eslintrc.json lib/*",
"test": "mocha --exit --timeout 60000 --recursive --reporter spec test/*.js"
"test": "mocha --exit --timeout 40000 --recursive --reporter spec test/*.js"
},
"dependencies": {
"byline": "^5.0.0",
Expand Down
2 changes: 1 addition & 1 deletion test/Model.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ describe('Model', () => {
});
});

it('ignores whitelisted advisotry IDs', () => {
it('ignores whitelisted advisory IDs', () => {
const model = new Model({
levels: { critical: true, low: true, high: true, moderate: true },
whitelist: [],
Expand Down
54 changes: 43 additions & 11 deletions test/npm-auditer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,36 @@ const path = require('path');
const { audit } = require('../lib/npm-auditer');

function config(additions) {
return Object.assign({}, { whitelist: [], advisories: [] }, additions);
const defaultConfig = {
levels: {
low: false,
moderate: false,
high: false,
critical: false,
},
report: {},
advisories: [],
whitelist: [],
directory: './',
registry: undefined,
};
return Object.assign({}, defaultConfig, additions);
}

function testDir(s) {
return path.resolve(__dirname, s);
}

describe('npm-auditer', () => {
// To modify what slow times are, need to use
// function() {} instead of () => {}
// eslint-disable-next-line func-names
describe('npm-auditer', function() {
this.slow(6000);
it('reports critical severity', () => {
return audit(
config({
directory: testDir('npm-critical'),
levels: { critical: true },
report: {},
}),
summary => summary
).then(summary => {
Expand All @@ -38,7 +54,6 @@ describe('npm-auditer', () => {
config({
directory: testDir('npm-critical'),
levels: { critical: false },
report: {},
}),
summary => summary
).then(summary => {
Expand All @@ -55,7 +70,6 @@ describe('npm-auditer', () => {
config({
directory: testDir('npm-high'),
levels: { high: true },
report: {},
}),
summary => summary
).then(summary => {
Expand All @@ -72,7 +86,6 @@ describe('npm-auditer', () => {
config({
directory: testDir('npm-moderate'),
levels: { moderate: true },
report: {},
}),
summary => summary
).then(summary => {
Expand All @@ -89,7 +102,6 @@ describe('npm-auditer', () => {
config({
directory: testDir('npm-moderate'),
levels: { moderate: false },
report: {},
}),
summary => summary
).then(summary => {
Expand All @@ -107,7 +119,6 @@ describe('npm-auditer', () => {
directory: testDir('npm-moderate'),
levels: { moderate: true },
advisories: [658],
report: {},
}),
summary => summary
).then(summary => {
Expand All @@ -125,7 +136,6 @@ describe('npm-auditer', () => {
directory: testDir('npm-moderate'),
levels: { moderate: true },
advisories: [659],
report: {},
}),
summary => summary
).then(summary => {
Expand All @@ -142,7 +152,6 @@ describe('npm-auditer', () => {
config({
directory: testDir('npm-low'),
levels: { low: true },
report: {},
}),
summary => summary
).then(summary => {
Expand All @@ -159,7 +168,6 @@ describe('npm-auditer', () => {
config({
directory: testDir('npm-none'),
levels: { low: true },
report: {},
}),
summary => summary
).then(summary => {
Expand All @@ -171,4 +179,28 @@ describe('npm-auditer', () => {
});
});
});
it('fails with error code ENOTFOUND on a non-existent site', done => {
audit(
config({
directory: testDir('npm-low'),
levels: { low: true },
registry: 'https://registry.nonexistentdomain0000000000.com',
})
).catch(err => {
expect(err.message).to.include('code ENOTFOUND');
done();
});
});
it('fails errors with code ENOAUDIT on a valid site with no audit', done => {
audit(
config({
directory: testDir('npm-low'),
levels: { low: true },
registry: 'https://example.com',
})
).catch(err => {
expect(err.message).to.include('code ENOAUDIT');
done();
});
});
});
Loading