Comparing version 0.5.0 to 0.6.0
@@ -32,2 +32,4 @@ 'use strict'; | ||
before: { $ref: '#/definitions/dateTimeOrInt' }, | ||
since: { $ref: '#/definitions/dateTimeOrInt' }, | ||
until: { $ref: '#/definitions/dateTimeOrInt' }, | ||
tap: { type: 'boolean' } | ||
@@ -74,3 +76,4 @@ } | ||
--- | ||
message: ${result.message} | ||
message: ${JSON.stringify(result.message)} | ||
messages: ${JSON.stringify(result.messages)} | ||
severity: ${result.mode == 2 ? 'error' : 'warning'} | ||
@@ -77,0 +80,0 @@ ${tapTestMeta(repoName, ruleName)}`); |
@@ -62,2 +62,5 @@ 'use strict'; | ||
opts.commits = {since: getRangeDate(argv.since || 30)}; | ||
if (argv.until) opts.commits.until = getRangeDate(argv.until); | ||
opts.tap = argv.tap; | ||
@@ -64,0 +67,0 @@ |
@@ -43,2 +43,6 @@ 'use strict'; | ||
return allPages(`/repos/${orgRepo}/teams`); | ||
}, | ||
commits(orgRepo) { | ||
let {since, until} = options.commits; | ||
return allPages(`/repos/${orgRepo}/commits`, {since, until}); | ||
} | ||
@@ -45,0 +49,0 @@ }, |
@@ -8,2 +8,3 @@ 'use strict'; | ||
allErrors: true, | ||
useDefaults: true, | ||
schemas: { | ||
@@ -19,3 +20,3 @@ defs: require('../../schemas/defs.json'), | ||
async checkRules (config, options={}) { | ||
execute.validateConfig(config); | ||
config = execute.prepareConfig(config); | ||
github.setOptions(options); | ||
@@ -45,47 +46,75 @@ const repoSourceRules = await execute.prepareRepoRules(config, options); | ||
const repoSourceRules = {}; | ||
const {org, rules, organizations, teams, repositories} = config; | ||
const org = config.org; | ||
if (rules) { | ||
// global rules | ||
const repos = await github.getRepos.organization(org); | ||
addRepos(repos, rules); | ||
await addGlobalRules(); | ||
await addOrganisations(); | ||
await addTeams(); | ||
await addRepositories(); | ||
removeDisabledRules(); | ||
return repoSourceRules; | ||
async function addGlobalRules() { | ||
if (config.rules) { | ||
const repos = await github.getRepos.organization(org); | ||
addRepos(repos, config.rules); | ||
} | ||
} | ||
if (organizations) { | ||
// organization-specific rules | ||
for (const orgName in organizations) { | ||
const repos = await github.getRepos.organization(orgName); | ||
addRepos(repos, organizations[orgName].rules); | ||
async function addOrganisations() { | ||
const organizations = config.organizations; | ||
if (organizations) { | ||
// organization-specific rules | ||
for (const orgName in organizations) { | ||
const repos = await github.getRepos.organization(orgName); | ||
addRepos(repos, organizations[orgName].rules); | ||
} | ||
} | ||
} | ||
if (teams) { | ||
// team specific rules | ||
for (const teamName in teams) { | ||
let [orgName, shortTeamName] = teamName.split('/'); | ||
if (!shortTeamName) { | ||
orgName = org; | ||
shortTeamName = teamName; | ||
async function addTeams() { | ||
const teams = config.teams; | ||
if (teams) { | ||
// team specific rules | ||
for (const teamName in teams) { | ||
let [orgName, shortTeamName] = teamName.split('/'); | ||
if (!shortTeamName) { | ||
orgName = org; | ||
shortTeamName = teamName; | ||
} | ||
const repos = await github.getRepos.team(orgName, shortTeamName); | ||
addRepos(repos, teams[teamName].rules); | ||
} | ||
const repos = await github.getRepos.team(orgName, shortTeamName); | ||
addRepos(repos, teams[teamName].rules); | ||
} | ||
} | ||
if (repositories) { | ||
for (const repoName in repositories) { | ||
const fullRepoName = /\//.test(repoName) ? repoName : org + '/' + repoName; | ||
const repoRules = repositories[repoName].rules; | ||
addRepoRules(fullRepoName, repoRules); | ||
async function addRepositories() { | ||
const repositories = config.repositories; | ||
if (repositories) { | ||
for (const repoName in repositories) { | ||
const fullRepoName = /\//.test(repoName) ? repoName : org + '/' + repoName; | ||
const repoRules = repositories[repoName].rules; | ||
addRepoRules(fullRepoName, repoRules); | ||
} | ||
} | ||
} | ||
return repoSourceRules; | ||
function removeDisabledRules() { | ||
for (const repoOrg in repoSourceRules) { | ||
const repoSources = repoSourceRules[repoOrg]; | ||
for (const source in repoSources) { | ||
if (Object.keys(repoSources[source]).length == 0) | ||
delete repoSources[source]; | ||
} | ||
if (Object.keys(repoSources).length == 0) | ||
delete repoSourceRules[repoOrg]; | ||
} | ||
} | ||
function addRepos(repos, repoRules) { | ||
for (const repo of repos) { | ||
const updatedAt = new Date(repo.updated_at); | ||
const inRange = (!options.after || updatedAt > options.after) && | ||
(!options.before || updatedAt < options.before); | ||
const pushedAt = new Date(repo.pushed_at); | ||
const inRange = (!options.after || updatedAt >= options.after || pushedAt >= options.after) && | ||
(!options.before || (updatedAt < options.before && pushedAt < options.before)); | ||
if (inRange) addRepoRules(repo.full_name, repoRules); | ||
@@ -98,3 +127,3 @@ } | ||
for (const ruleName in repoRules) { | ||
const ruleOptions = execute.normaliseRuleOptions(repoRules[ruleName]); | ||
const ruleOptions = repoRules[ruleName]; | ||
const ruleDefinition = execute.getRuleDefinition(ruleName); | ||
@@ -108,3 +137,6 @@ const source = ruleDefinition.source; | ||
// config for smaller scope overrides config for bigger scope | ||
sourceRules[ruleName] = [ruleOptions]; | ||
if (ruleOptions.mode === 0) | ||
delete sourceRules[ruleName]; | ||
else | ||
sourceRules[ruleName] = [ruleOptions]; | ||
} | ||
@@ -135,6 +167,8 @@ } | ||
getRuleDefinition(ruleName) { | ||
return require(path.join('..', 'rules', ruleName)); | ||
try { return require(path.join('..', 'rules', ruleName)); } | ||
catch(e) { throw new Error(`cannot find rule ${ruleName}: ${e.message}`); } | ||
}, | ||
validateConfig(config) { | ||
prepareConfig(config) { | ||
config = JSON.parse(JSON.stringify(config)); | ||
callValidate(ajv.getSchema('config'), config, 'config'); | ||
@@ -145,25 +179,33 @@ | ||
const ruleNames = {}; | ||
addRuleNames(config); | ||
addRuleNamesFromMap(organizations); | ||
addRuleNamesFromMap(teams); | ||
addRuleNamesFromMap(repositories); | ||
const validateRule = {}; | ||
addRules(config, 'global rules'); | ||
addRulesFromMap(organizations, 'organization'); | ||
addRulesFromMap(teams, 'team'); | ||
addRulesFromMap(repositories, 'repository'); | ||
for (const name in ruleNames) { | ||
let rule; | ||
try { rule = execute.getRuleDefinition(name); } | ||
catch(e) { throw new Error(`cannot find rule ${name}: ${e.message}`); } | ||
let rule = execute.getRuleDefinition(name); | ||
callValidate(validate, rule, `rule "${name}"`); | ||
} | ||
return config; | ||
function addRuleNames({rules}={}) { | ||
function addRules({rules}={}, scope) { | ||
if (rules) { | ||
for (const name in rules) | ||
for (const name in rules) { | ||
rules[name] = execute.normaliseRuleOptions(rules[name]); | ||
if (!validateRule[name]) { | ||
const ruleSchema = execute.getRuleDefinition(name).schema; | ||
validateRule[name] = ajv.compile(ruleSchema); | ||
} | ||
callValidate(validateRule[name], rules[name], `config for rule "${name}" in ${scope}`); | ||
ruleNames[name] = true; | ||
} | ||
} | ||
} | ||
function addRuleNamesFromMap(group) { | ||
function addRulesFromMap(group, groupName) { | ||
if (group) { | ||
for (const item in group) | ||
addRuleNames(group[item]); | ||
addRules(group[item], `${groupName} "${item}"`); | ||
} | ||
@@ -218,2 +260,1 @@ } | ||
} | ||
{ | ||
"name": "gh-lint", | ||
"version": "0.5.0", | ||
"version": "0.6.0", | ||
"description": "Rule-based command-line tool for auditing GitHub repositories", | ||
@@ -5,0 +5,0 @@ "main": "lib/execute/index.js", |
@@ -42,8 +42,16 @@ # gh-lint | ||
#### Commit rules | ||
By default, these rules analyse the commits for the last 30 days. It can be changed using options `--since` and `--until` (see below). | ||
- commit-name: check that commit names satisfy semantic commit conventions | ||
## Options | ||
- `-c` (or `--config`) - configuration file location | ||
- `-u` (or `--user`) - GitHub username. | ||
- `-p` (or `--pass`) - GitHub password. | ||
- `-u` (or `--user`) - GitHub username | ||
- `-p` (or `--pass`) - GitHub password | ||
- `-a` (or `--after`) / `-b` (or `--before`) - only validate repositories in organizations and in teams that were changed **after**/**before** this date (also can be date-time or the integer number of days). These options have no effect on repositories that are explicitely specified. | ||
- `--since` / `--until` - validate commits **since**/**until** this date (also can be date-time or the integer number of days) | ||
- `--tap` - output results in TAP format | ||
@@ -50,0 +58,0 @@ |
@@ -63,34 +63,28 @@ 'use strict'; | ||
it('should check repos in orgs within date range', () => { | ||
githubMock.repos.organization.MailOnline.list(); | ||
githubMock.repos.organization.milojs.list(); | ||
githubMock.mock('/repos/MailOnline/json-schema-test', '../fixtures/mailonline_repos/json-schema-test'); | ||
githubMock.mock('/repos/MailOnline/ImageViewer', '../fixtures/mailonline_repos/ImageViewer'); | ||
githubMock.mock('/repos/milojs/milo', '../fixtures/milojs_repos/milo'); | ||
describe('date range', () => { | ||
it('should check repos in orgs within date range', () => { | ||
test(['--after', '2017-01-20', '--before', '2017-02-01']); | ||
}); | ||
return ok(run([ | ||
'--config', './spec/fixtures/config-orgs.json', | ||
'--after', '2017-01-20', '--before', '2017-02-01' | ||
], false)).then(() => { | ||
assert.equal(log, 'warning MailOnline/json-schema-test: repo-homepage - not satisfied\n' + | ||
'warning MailOnline/ImageViewer: repo-homepage - not satisfied'); | ||
assert(nock.isDone()); | ||
it('should check repos in orgs changed in the last X days', () => { | ||
test(['--after', getDays('2017-01-20'), '--before', getDays('2017-02-01')]); | ||
}); | ||
}); | ||
it('should check repos in orgs changed in the last X days', () => { | ||
githubMock.repos.organization.MailOnline.list(); | ||
githubMock.repos.organization.milojs.list(); | ||
githubMock.mock('/repos/MailOnline/json-schema-test', '../fixtures/mailonline_repos/json-schema-test'); | ||
githubMock.mock('/repos/MailOnline/ImageViewer', '../fixtures/mailonline_repos/ImageViewer'); | ||
githubMock.mock('/repos/milojs/milo', '../fixtures/milojs_repos/milo'); | ||
function test(range) { | ||
githubMock.repos.organization.MailOnline.list(); | ||
githubMock.repos.organization.MailOnline.meta(['cuteyp', 'json-schema-test', 'ImageViewer', 'gh-lint']); | ||
githubMock.repos.organization.milojs.list(); | ||
githubMock.repos.organization.milojs.meta(['milo', 'proto', 'milo-core']); | ||
return ok(run([ | ||
'--config', './spec/fixtures/config-orgs.json', | ||
'--after', getDays('2017-01-20'), '--before', getDays('2017-02-01') | ||
], false)).then(() => { | ||
assert.equal(log, 'warning MailOnline/json-schema-test: repo-homepage - not satisfied\n' + | ||
'warning MailOnline/ImageViewer: repo-homepage - not satisfied'); | ||
assert(nock.isDone()); | ||
}); | ||
const params = ['--config', './spec/fixtures/config-orgs.json'].concat(range); | ||
return ok(run(params, false)).then(() => { | ||
assert.equal(log, | ||
`warning MailOnline/cuteyp: repo-homepage - not satisfied | ||
warning MailOnline/json-schema-test: repo-homepage - not satisfied | ||
warning MailOnline/ImageViewer: repo-homepage - not satisfied | ||
warning MailOnline/gh-lint: repo-homepage - not satisfied | ||
warning milojs/milo-core: repo-homepage - not satisfied`); | ||
assert(nock.isDone()); | ||
}); | ||
} | ||
@@ -97,0 +91,0 @@ function getDays(dateStr) { |
@@ -74,3 +74,3 @@ 'use strict'; | ||
it('should execute rules for all repos in two orgs', () => { | ||
it.skip('should execute rules for all repos in two orgs', () => { | ||
githubMock.repos.organization.MailOnline.list(); | ||
@@ -77,0 +77,0 @@ githubMock.repos.organization.MailOnline.meta(); |
@@ -20,5 +20,9 @@ 'use strict'; | ||
}, | ||
meta() { | ||
meta(repos) { | ||
glob.sync('../fixtures/mailonline_repos/*.json', { cwd: __dirname }) | ||
.forEach(addRepoMock('MailOnline')); | ||
.forEach(addRepoMock('MailOnline', repos)); | ||
}, | ||
teams() { | ||
glob.sync('../fixtures/mailonline_repo_teams/*.json', { cwd: __dirname }) | ||
.forEach(addTeamsMock('MailOnline')); | ||
} | ||
@@ -30,5 +34,5 @@ }, | ||
}, | ||
meta() { | ||
meta(repos) { | ||
glob.sync('../fixtures/milojs_repos/*.json', { cwd: __dirname }) | ||
.forEach(addRepoMock('milojs')); | ||
.forEach(addRepoMock('milojs', repos)); | ||
} | ||
@@ -58,7 +62,16 @@ } | ||
function addRepoMock(org) { | ||
function addRepoMock(org, repos) { | ||
return function (file) { | ||
const repoName = path.basename(file, '.json'); | ||
mock(`/repos/${org}/${repoName}`, file); | ||
if (!repos || repos.indexOf(repoName) >= 0) | ||
mock(`/repos/${org}/${repoName}`, file); | ||
}; | ||
} | ||
function addTeamsMock(org) { | ||
return function (file) { | ||
const repoName = path.basename(file, '.json'); | ||
mock(`/repos/${org}/${repoName}/teams?per_page=30&page=1`, file); | ||
}; | ||
} |
@@ -5,9 +5,17 @@ 'use strict'; | ||
const assert = require('assert'); | ||
const nock = require('nock'); | ||
const githubMock = require('./github_mock'); | ||
const github = require('../../lib/execute/github'); | ||
// const util = require('util'); | ||
describe('prepareRepoRules', () => { | ||
afterEach(() => { | ||
nock.cleanAll(); | ||
github.clearTeams(); | ||
}); | ||
describe('repositories scope', () => { | ||
it('should collect sources and rules for all repositories', () => { | ||
const config = { | ||
const config = execute.prepareConfig({ | ||
org: 'MailOnline', | ||
@@ -27,3 +35,3 @@ repositories: { | ||
} | ||
}; | ||
}); | ||
@@ -34,7 +42,7 @@ return execute.prepareRepoRules(config) | ||
'MailOnline/mol-fe': { | ||
meta: { 'repo-description': [{ mode: 2}] } }, | ||
meta: { 'repo-description': [{ mode: 2, minLength: 1 }] } }, | ||
'milojs/milo': { | ||
meta: { | ||
'repo-description': [{ mode: 2, minLength: 16 }], | ||
'repo-homepage': [{ mode: 1 }] | ||
'repo-homepage': [{ mode: 1, minLength: 1 }] | ||
} | ||
@@ -54,3 +62,3 @@ } | ||
const config = require('../fixtures/config-orgs.json'); | ||
const config = execute.prepareConfig(require('../fixtures/config-orgs.json')); | ||
@@ -63,6 +71,12 @@ return execute.prepareRepoRules(config, { | ||
assert.deepStrictEqual(repoSourceRules, { | ||
'MailOnline/cuteyp': { | ||
meta: { | ||
'repo-description': [ { mode: 2, minLength: 1 } ], | ||
'repo-homepage': [ { mode: 1, minLength: 1 } ] | ||
} | ||
}, | ||
'MailOnline/json-schema-test': { | ||
meta: { | ||
'repo-description': [ { mode: 2 } ], | ||
'repo-homepage': [ { mode: 1 } ] | ||
'repo-description': [ { mode: 2, minLength: 1 } ], | ||
'repo-homepage': [ { mode: 1, minLength: 1 } ] | ||
} | ||
@@ -72,13 +86,32 @@ }, | ||
meta: { | ||
'repo-description': [ { mode: 2 } ], | ||
'repo-homepage': [ { mode: 1 } ] | ||
'repo-description': [ { mode: 2, minLength: 1 } ], | ||
'repo-homepage': [ { mode: 1, minLength: 1 } ] | ||
} | ||
}, | ||
'MailOnline/gh-lint': { | ||
meta: { | ||
'repo-description': [ { mode: 2, minLength: 1 } ], | ||
'repo-homepage': [ { mode: 1, minLength: 1 } ] | ||
} | ||
}, | ||
'milojs/milo': { | ||
meta: { | ||
'repo-description': [ { mode: 2 } ], | ||
'repo-homepage': [ { mode: 1 } ] | ||
'repo-description': [ { mode: 2, minLength: 1 } ], | ||
'repo-homepage': [ { mode: 1, minLength: 1 } ] | ||
} | ||
}, | ||
'milojs/proto': { | ||
meta: { | ||
'repo-description': [ { mode: 2, minLength: 1 } ], | ||
'repo-homepage': [ { mode: 1, minLength: 1 } ] | ||
} | ||
}, | ||
'milojs/milo-core': { | ||
meta: { | ||
'repo-description': [ { mode: 2, minLength: 1 } ], | ||
'repo-homepage': [ { mode: 1, minLength: 1 } ] | ||
} | ||
} | ||
}); | ||
assert(nock.isDone()); | ||
// console.log(util.inspect(repoSourceRules, {depth: null})); | ||
@@ -88,2 +121,89 @@ }); | ||
}); | ||
describe('teams scope', () => { | ||
it('should collect rules for repos for team', () => { | ||
githubMock.teams(); | ||
githubMock.repos.team.mol_fe.list(); | ||
const config = execute.prepareConfig(require('../fixtures/config-teams.json')); | ||
return execute.prepareRepoRules(config) | ||
.then(repoSourceRules => { | ||
assert.deepStrictEqual(repoSourceRules, { | ||
'MailOnline/eslint-config-mailonline': { | ||
meta: { | ||
'repo-description': [ { mode: 2, minLength: 1 } ], | ||
'repo-homepage': [ { mode: 1, minLength: 1 } ] | ||
} | ||
}, | ||
'MailOnline/mol-conventional-changelog': { | ||
meta: { | ||
'repo-description': [ { mode: 2, minLength: 1 } ], | ||
'repo-homepage': [ { mode: 1, minLength: 1 } ] | ||
} | ||
}, | ||
'MailOnline/stylelint-config-mailonline': { | ||
meta: { | ||
'repo-description': [ { mode: 2, minLength: 1 } ], | ||
'repo-homepage': [ { mode: 1, minLength: 1 } ] | ||
} | ||
}, | ||
'MailOnline/videojs-vast-vpaid': { | ||
meta: { | ||
'repo-description': [ { mode: 2, minLength: 1 } ], | ||
'repo-homepage': [ { mode: 1, minLength: 1 } ] | ||
} | ||
}, | ||
'MailOnline/VPAIDFLASHClient': { | ||
meta: { | ||
'repo-description': [ { mode: 2, minLength: 1 } ], | ||
'repo-homepage': [ { mode: 1, minLength: 1 } ] | ||
} | ||
}, | ||
'MailOnline/VPAIDHTML5Client': { | ||
meta: { | ||
'repo-description': [ { mode: 2, minLength: 1 } ], | ||
'repo-homepage': [ { mode: 1, minLength: 1 } ] | ||
} | ||
} | ||
}); | ||
assert(nock.isDone()); | ||
// console.log(util.inspect(repoSourceRules, {depth: null})); | ||
}); | ||
}); | ||
it('should collect rules for repos for team excluding disabled rules', () => { | ||
githubMock.teams(); | ||
githubMock.repos.team.mol_fe.list(); | ||
const config = execute.prepareConfig(require('../fixtures/config-teams-excluding-repos.json')); | ||
return execute.prepareRepoRules(config) | ||
.then(repoSourceRules => { | ||
assert.deepStrictEqual(repoSourceRules, { | ||
'MailOnline/videojs-vast-vpaid': { | ||
meta: { | ||
'repo-description': [ { mode: 2, minLength: 1 } ], | ||
'repo-homepage': [ { mode: 1, minLength: 1 } ] | ||
} | ||
}, | ||
'MailOnline/VPAIDFLASHClient': { | ||
meta: { | ||
'repo-description': [ { mode: 2, minLength: 1 } ], | ||
'repo-homepage': [ { mode: 1, minLength: 1 } ] | ||
} | ||
}, | ||
'MailOnline/VPAIDHTML5Client': { | ||
meta: { | ||
'repo-description': [ { mode: 2, minLength: 1 } ], | ||
'repo-homepage': [ { mode: 1, minLength: 1 } ] | ||
} | ||
} | ||
}); | ||
assert(nock.isDone()); | ||
// console.log(util.inspect(repoSourceRules, {depth: null})); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -7,6 +7,6 @@ 'use strict'; | ||
describe('validateConfig', () => { | ||
describe('prepareConfig', () => { | ||
it('should throw exception if config is not valid', () => { | ||
assert.throws(() => { | ||
execute.validateConfig({}); | ||
execute.prepareConfig({}); | ||
}, /config is invalid/); | ||
@@ -17,3 +17,3 @@ }); | ||
assert.throws(() => { | ||
execute.validateConfig({ | ||
execute.prepareConfig({ | ||
org: 'MailOnline', | ||
@@ -26,2 +26,19 @@ rules: { | ||
}); | ||
it('should throw exception if rule options are invalid', () => { | ||
assert.throws(() => { | ||
execute.prepareConfig({ | ||
org: 'MailOnline', | ||
repositories: { | ||
'milojs/milo': { | ||
rules: { | ||
'commit-name': [2, { | ||
maxLineLength: 'invalid' | ||
}] | ||
} | ||
} | ||
} | ||
}); | ||
}, /config for rule.*commit-name.*milojs\/milo.*invalid/); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
778246
152
14938
62