Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

gh-lint

Package Overview
Dependencies
Maintainers
2
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

gh-lint - npm Package Compare versions

Comparing version 0.9.0 to 0.10.0

spec/fixtures/branch-protection_expected_results.json

22

lib/cli/commands/check.js

@@ -11,3 +11,3 @@ 'use strict';

execute (argv) {
const config = loadConfig(argv.c || argv.config);
const config = loadConfig(argv.config);
const opts = options.get(argv);

@@ -18,15 +18,7 @@ return execute.checkRules(config, opts).then(logErrors(opts), console.error);

type: 'object',
oneOf: [
{ required: ['c'] },
{ required: ['config'] }
],
required: ['config'],
properties: {
c: { type: 'string' },
config: { type: 'string' },
u: { type: 'string' },
p: { type: 'string' },
user: { type: 'string' },
pass: { type: 'string' },
a: { $ref: '#/definitions/dateTimeOrInt' },
b: { $ref: '#/definitions/dateTimeOrInt' },
after: { $ref: '#/definitions/dateTimeOrInt' },

@@ -36,3 +28,11 @@ before: { $ref: '#/definitions/dateTimeOrInt' },

until: { $ref: '#/definitions/dateTimeOrInt' },
tap: { type: 'boolean' }
tap: { type: 'boolean' },
teamAccess: {
type: 'string',
enum: ['admin', 'write', 'read'],
default: 'admin'
}
},
patternProperties: {
'^(c|u|p|a|b|team-access)$': true
}

@@ -39,0 +39,0 @@ }

@@ -7,3 +7,4 @@ 'use strict';

coerceTypes: true,
jsonPointers: true
jsonPointers: true,
useDefaults: true
});

@@ -52,18 +53,8 @@

get (argv) {
const opts = {};
const user = argv.u || argv.user;
const pass = argv.p || argv.pass;
if (user || pass) opts.auth = { user, pass };
const after = argv.a || argv.after;
const before = argv.b || argv.before;
if (after) opts.after = getRangeDate(after);
if (before) opts.before = getRangeDate(before);
opts.commits = {since: getRangeDate(argv.since || 30)};
if (argv.until) opts.commits.until = getRangeDate(argv.until);
opts.tap = argv.tap;
get ({user, pass, after, before, since, until, tap, teamAccess}) {
const opts = {
after, before, tap, teamAccess,
commits: {since, until}
};
if (user || pass) opts.auth = {user, pass};
return opts;

@@ -77,6 +68,1 @@ }

}
function getRangeDate(param) {
return new Date(typeof param == 'string' ? param
: Date.now() - param * 86400000); // days
}

@@ -8,3 +8,12 @@ 'use strict';

module.exports = (_argv, doExit) => {
const argv = minimist(_argv);
const argv = minimist(_argv, {
alias: {
config: 'c',
user: 'u',
pass: 'p',
after: 'a',
before: 'b',
teamAccess: 'team-access'
}
});
const command = argv._[0] || 'check';

@@ -11,0 +20,0 @@ const cmd = commands[command];

@@ -8,2 +8,7 @@ 'use strict';

let options = {};
const teamPermissions = {
admin: 'admin',
write: 'push',
read: 'pull'
};

@@ -69,3 +74,3 @@ module.exports = github;

},
async team (org, teamName) {
async team (org, teamName, teamAccess='admin') {
const teams = await github.getTeams(org);

@@ -75,3 +80,4 @@ const team = teams.find(t => t.name == teamName);

const repos = await allPages(`/teams/${team.id}/repos`);
return repos.filter((r) => !r.fork);
const perm = teamPermissions[teamAccess];
return repos.filter((r) => !r.fork && r.permissions[perm]);
}

@@ -78,0 +84,0 @@ },

@@ -22,2 +22,3 @@ 'use strict';

let CORE_LOADED = false;
const MS_PER_DAY = 86400000;

@@ -27,5 +28,4 @@

async checkRules (config, options={}) {
if (!CORE_LOADED) loadCoreRules();
if (config.plugins) loadPlugins(config);
config = execute.prepareConfig(config);
options = prepareOptions(options);
github.setOptions(options);

@@ -89,3 +89,3 @@ const repoSourceRules = await execute.prepareRepoRules(config, options);

}
const repos = await github.getRepos.team(orgName, shortTeamName);
const repos = await github.getRepos.team(orgName, shortTeamName, options.teamAccess);
addRepos(repos, teams[teamName].rules);

@@ -172,2 +172,4 @@ }

prepareConfig(config) {
if (!CORE_LOADED) loadCoreRules();
if (config.plugins) loadPlugins(config);
config = JSON.parse(JSON.stringify(config));

@@ -218,2 +220,7 @@ callValidate(ajv.getSchema('config'), config, 'config');

}
},
dateDaysAgo(days) {
let ts = Date.now() - days * MS_PER_DAY;
return new Date(ts - ts % MS_PER_DAY);
}

@@ -250,2 +257,33 @@ };

function prepareOptions(options) {
options = copy(options);
const {after, before, teamAccess} = options;
if (after) options.after = getRangeDate(after, 'after');
if (before) options.before = getRangeDate(before, 'before');
if (options.commits === undefined) {
options.commits = {since: execute.dateDaysAgo(30)};
} else {
options.commits = copy(options.commits);
const {since, until} = options.commits;
if (since) options.commits.since = getRangeDate(since, 'commits.since');
if (until) options.commits.until = getRangeDate(until, 'commits.until');
}
if (!teamAccess) options.teamAccess = 'admin';
return options;
}
function getRangeDate(param, name) {
switch (typeof param) {
case 'string':
return param;
case 'number':
return execute.dateDaysAgo(param);
default:
if (param instanceof Date) return param;
throw new Error(`incorrect "${name}" option type: ${param.toString()}`);
}
}
function callValidate(validate, data, title) {

@@ -252,0 +290,0 @@ if (!validate(data))

@@ -11,27 +11,46 @@ 'use strict';

schema: {},
schema: {
type: 'object',
properties: {
branches: {
type: 'array',
items: {type: 'string'},
uniqueItems: true,
default: ['master']
}
}
},
source: 'branches',
check: {
type: 'array',
contains: {
type: 'object',
required: ['name', 'protected'],
properties: {
name: {const: 'master'},
protected: {const: true}
}
async check(cfg, repoBranches, orgRepo, github) {
const brs = [];
for (const branch of cfg.branches) {
if (repoBranches.findIndex(b => b.name == branch) == -1) continue;
const branchUrl = `/repos/${orgRepo}/branches/${branch}`;
const branchMeta = await github.get(branchUrl);
if (!branchMeta.protected) brs.push(branchMeta);
}
if (brs.length == 0) return {valid: true};
const allBranches = brs.map(b => b.name).join(', ');
return {
valid: false,
message: `Unprotected branch${plural(brs)}: ${allBranches}`,
messages: brs.map(b => `Unprotected branch ${b.name}`)
};
function plural(arr) {
return arr.length > 1 ? 'es' : '';
}
},
issue: {
title: 'Master branch is not protected',
title: 'Branches are not protected',
comments: {
create: 'Please enable master protection',
update: 'Reminder: please enable master protection',
close: 'Master protection enabled, closing',
reopen: 'Please enable master protection'
create: 'Please enable branch protection',
close: 'Branch protection enabled, closing',
reopen: 'Branches are not protected, probably removed. Please fix it'
}
}
};
'use strict';
const PERMISSIONS = {
admin: 2,
write: 1,
read: 0
};
module.exports = {

@@ -19,2 +25,7 @@ meta: {

items: {type: 'string'}
},
minPermission: {
type: 'string',
enum: ['admin', 'write', 'read'],
default: 'admin'
}

@@ -27,4 +38,7 @@ }

check(cfg, repoTeams) {
for (const team of repoTeams)
if (cfg.teams.indexOf(team.name) >= 0) return {valid: true};
for (const team of repoTeams) {
const assigned = PERMISSIONS[team.permission] >= PERMISSIONS[cfg.minPermission]
&& cfg.teams.indexOf(team.name) >= 0;
if (assigned) return {valid: true};
}

@@ -31,0 +45,0 @@ return {

{
"name": "gh-lint",
"version": "0.9.0",
"version": "0.10.0",
"description": "Rule-based command-line tool for auditing GitHub repositories",

@@ -5,0 +5,0 @@ "main": "lib/execute/index.js",

@@ -92,2 +92,3 @@ # gh-lint

- `--tap` - output results in TAP format
- `--team-access` - team access level required for repo to be associated with the team (for team-specific rules). The default is "admin". Other values are "write" (includes admin access) and "read" (repo will be associated with the team that has any access level).

@@ -94,0 +95,0 @@

@@ -89,6 +89,6 @@ 'use strict';

it('should execute rules for all repos of a team', () => {
it('should execute rules for all repos assigned to a team with admin permission only', () => {
githubMock.teams();
githubMock.repos.team.mol_fe.list();
githubMock.repos.team.mol_fe.meta();
githubMock.repos.team.mol_fe.meta('admin');

@@ -103,2 +103,17 @@ const config = require('../fixtures/config-teams.json');

});
it('should execute rules for all repos assigned to a team with any permission level', () => {
githubMock.teams();
githubMock.repos.team.mol_fe.list();
githubMock.repos.team.mol_fe.meta();
const config = require('../fixtures/config-teams.json');
return execute.checkRules(config, {teamAccess: 'read'})
.then((results) => {
assert.deepStrictEqual(results, require('../fixtures/config-teams_read-access_expected_results.json'));
assert(nock.isDone());
});
});
});

@@ -36,2 +36,8 @@ 'use strict';

.forEach(addRepoMock('milojs', repos));
},
branches() {
glob.sync('../fixtures/milojs_repo_branches/*.list.json', { cwd: __dirname })
.forEach(addBranchesMock('milojs'));
glob.sync('../fixtures/milojs_repo_branches/*.branch.json', { cwd: __dirname })
.forEach(addBranchMock('milojs'));
}

@@ -45,6 +51,8 @@ }

},
meta() {
meta(teamPermission) { // 'pull', 'push', 'admin'
const repos = require('../fixtures/molfe_repos.json');
for (const repo of repos)
mock(`/repos/MailOnline/${repo.name}`, path.join(__dirname, `../fixtures/mailonline_repos/${repo.name}.json`));
for (const repo of repos) {
if (!teamPermission || repo.permissions[teamPermission])
mock(`/repos/MailOnline/${repo.name}`, path.join(__dirname, `../fixtures/mailonline_repos/${repo.name}.json`));
}
}

@@ -92,1 +100,17 @@ }

}
function addBranchesMock(org) {
return function (file) {
const repoName = path.basename(file, '.list.json');
mock(`/repos/${org}/${repoName}/branches?per_page=30&page=1`, file);
};
}
function addBranchMock(org) {
return function (file) {
const [repoName, branchName] = path.basename(file, '.branch.json').split('_');
mock(`/repos/${org}/${repoName}/branches/${branchName}`, file);
};
}

@@ -58,3 +58,3 @@ 'use strict';

describe('.team', () => {
it('should load organization repos', () => {
it('should load team repos only with admin team access', () => {
githubMock.teams();

@@ -65,2 +65,15 @@ githubMock.repos.team.mol_fe.list();

.then(result => {
const expectedRepos = require('../fixtures/molfe_repos.json')
.filter(r => r.permissions.admin);
assert.deepStrictEqual(result, expectedRepos);
assert(nock.isDone());
});
});
it('should load team repos with any access', () => {
githubMock.teams();
githubMock.repos.team.mol_fe.list();
return github.getRepos.team('MailOnline', '#mol-fe', 'read')
.then(result => {
assert.deepStrictEqual(result, require('../fixtures/molfe_repos.json'));

@@ -67,0 +80,0 @@ assert(nock.isDone());

@@ -151,3 +151,3 @@ 'use strict';

describe('teams scope', () => {
it('should collect rules for repos for team', () => {
it('should collect rules for repos for team with agmin access only', () => {
githubMock.teams();

@@ -173,2 +173,46 @@ githubMock.repos.team.mol_fe.list();

},
'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());
});
});
it('should collect rules for repos for team with any access', () => {
githubMock.teams();
githubMock.repos.team.mol_fe.list();
const config = execute.prepareConfig(require('../fixtures/config-teams.json'));
return execute.prepareRepoRules(config, {teamAccess: 'read'})
.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': {

@@ -175,0 +219,0 @@ meta: {

@@ -24,13 +24,2 @@ {

},
"MailOnline/stylelint-config-mailonline": {
"repo-description": {
"valid": true
},
"repo-homepage": {
"valid": false,
"message": "not satisfied",
"errors": "data.homepage should NOT be shorter than 10 characters",
"mode": 1
}
},
"MailOnline/videojs-vast-vpaid": {

@@ -37,0 +26,0 @@ "repo-description": {

@@ -54,3 +54,8 @@ {

"repo-team": {
"valid": true
"message": "Repo not assigned to one of specified teams",
"messages": [
"Specified teams: #ads, #cc, #clj, #ios-ny, #ml-nlp, #mol-fe, #rc, #rta, #support, #systems, Metro, IOS"
],
"mode": 2,
"valid": false
}

@@ -210,3 +215,8 @@ },

"repo-team": {
"valid": true
"message": "Repo not assigned to one of specified teams",
"messages": [
"Specified teams: #ads, #cc, #clj, #ios-ny, #ml-nlp, #mol-fe, #rc, #rta, #support, #systems, Metro, IOS"
],
"mode": 2,
"valid": false
}

@@ -213,0 +223,0 @@ },

@@ -79,2 +79,31 @@ 'use strict';

it('should use default since option', () => {
const defaultSinceDate = encodeURIComponent(execute.dateDaysAgo(30).toISOString());
const apiPath = `/repos/milojs/milo/commits?since=${defaultSinceDate}&per_page=30&page=1`;
nock('https://api.github.com').get(apiPath).reply(200, []);
const config = {
org: 'MailOnline',
repositories: {
'milojs/milo': {
rules: {
'commit-name': 2
}
}
}
};
return execute.checkRules(config)
.then((results) => {
assert.deepStrictEqual(results, {
'milojs/milo': {
'commit-name': {
valid: true
}
}
});
assert(nock.isDone());
});
});
it('should pass if longer message length allowed', () => {

@@ -81,0 +110,0 @@ githubMock.mock('/repos/milojs/milo/commits?since=2017-04-23&per_page=30&page=1', '../fixtures/milojs_milo_commits');

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc