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

node-core-utils

Package Overview
Dependencies
Maintainers
1
Versions
70
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

node-core-utils - npm Package Compare versions

Comparing version 1.3.0 to 1.4.0

lib/args.js

88

bin/metadata.js
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
const { EOL } = require('os');
const Request = require('../lib/request');
const auth = require('../lib/auth');
function loadQuery(file) {
const filePath = path.resolve(__dirname, '..', 'queries', `${file}.gql`);
return fs.readFileSync(filePath, 'utf8');
}
const PR_QUERY = loadQuery('PR');
const REVIEWS_QUERY = loadQuery('Reviews');
const COMMENTS_QUERY = loadQuery('PRComments');
// const COMMITS_QUERY = loadQuery('PRCommits');
const { request, requestAll } = require('../lib/request');
const { getCollaborators } = require('../lib/collaborators');
const logger = require('../lib/logger');
const ReviewAnalyzer = require('../lib/reviews');
const loggerFactory = require('../lib/logger');
const PRData = require('../lib/pr_data');
const PRChecker = require('../lib/pr_checker');
const MetadataGenerator = require('../lib/metadata_gen');
const argv = require('../lib/args')();
// const REFERENCE_RE = /referenced this pull request in/
const OWNER = argv.owner;
const REPO = argv.repo;
const PR_ID = argv.id;
const OWNER = 'nodejs';
const REPO = 'node';
async function main(prid, owner, repo, logger) {
const credentials = await auth();
const request = new Request(credentials);
const PR_ID = parsePRId(process.argv[2]);
const data = new PRData(prid, owner, repo, logger, request);
await data.getAll();
async function main(prid, owner, repo) {
logger.trace(`Getting collaborator contacts from README of ${owner}/${repo}`);
const collaborators = await getCollaborators(owner, repo);
const metadata = new MetadataGenerator(data).getMetadata();
const [SCISSOR_LEFT, SCISSOR_RIGHT] = MetadataGenerator.SCISSORS;
logger.info({
raw: [SCISSOR_LEFT, metadata, SCISSOR_RIGHT].join(EOL)
}, 'Generated metadata:');
logger.trace(`Getting PR from ${owner}/${repo}/pull/${prid}`);
const prData = await request(PR_QUERY, { prid, owner, repo });
const pr = prData.repository.pullRequest;
const vars = { prid, owner, repo };
logger.trace(`Getting reviews from ${owner}/${repo}/pull/${prid}`);
const reviews = await requestAll(REVIEWS_QUERY, vars, [
'repository', 'pullRequest', 'reviews'
]);
logger.trace(`Getting comments from ${owner}/${repo}/pull/${prid}`);
const comments = await requestAll(COMMENTS_QUERY, vars, [
'repository', 'pullRequest', 'comments'
]);
// logger.trace(`Getting commits from ${owner}/${repo}/pull/${prid}`);
// const commits = await requestAll(COMMITS_QUERY, vars, [
// 'repository', 'pullRequest', 'commits'
// ]);
const analyzer = new ReviewAnalyzer(reviews, comments, collaborators);
const reviewers = analyzer.getReviewers();
const metadata = new MetadataGenerator(repo, pr, reviewers).getMetadata();
logger.info({ raw: metadata }, 'Generated metadata:');
const checker = new PRChecker(pr, reviewers, comments);
checker.checkReviewers();
checker.checkReviews();
checker.checkPRWait();
checker.checkCI();
// TODO: check committers against authors
// TODO: maybe invalidate review after new commits?
if (!process.stdout.isTTY) {
process.stdout.write(`${metadata}${EOL}`);
}
const checker = new PRChecker(logger, data);
checker.checkAll();
}
main(PR_ID, OWNER, REPO).catch((err) => {
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
const logger = loggerFactory(logStream);
main(PR_ID, OWNER, REPO, logger).catch((err) => {
logger.error(err);
process.exit(-1);
});
function parsePRId(id) {
// Fast path: numeric string
if (`${+id}` === id) { return +id; }
const match = id.match(/^https:.*\/pull\/([0-9]+)(?:\/(?:files)?)?$/);
if (match !== null) { return +match[1]; }
throw new Error(`Could not understand PR id format: ${id}`);
}

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

const V8 = 'V8';
const LINTER = 'LINTER';

@@ -24,19 +25,24 @@ const CI_TYPES = new Map([

[NOINTL, { name: 'No Intl', re: /nointl/ }],
[V8, { name: 'V8', re: /node-test-commit-v8/ }]
[V8, { name: 'V8', re: /node-test-commit-v8/ }],
[LINTER, { name: 'Linter', re: /node-test-linter/ }]
]);
class CIParser {
constructor(comments) {
this.comments = comments;
/**
* @param {{bodyText: string, publishedAt: string}[]} thread
*/
constructor(thread) {
this.thread = thread;
}
/**
* @returns {Map<string, {link: string, date: string}>}
*/
parse() {
const comments = this.comments;
/**
* @type {Map<string, {link: string, date: string}>}
*/
const thread = this.thread;
const result = new Map();
for (const c of comments) {
if (!c.bodyText.includes(CI_DOMAIN)) continue;
const cis = this.parseText(c.bodyText);
for (const c of thread) {
const text = c.bodyText;
if (!text.includes(CI_DOMAIN)) continue;
const cis = this.parseText(text);
for (const ci of cis) {

@@ -53,3 +59,3 @@ const entry = result.get(ci.type);

/**
* @param {string} text
* @param {string} text
*/

@@ -78,5 +84,5 @@ parseText(text) {

CIParser.constants = {
CITGM, FULL, BENCHMARK, LIBUV, V8, NOINTL
CITGM, FULL, BENCHMARK, LIBUV, V8, NOINTL, LINTER
};
module.exports = CIParser;
'use strict';
const rp = require('request-promise-native');
const TSC_TITLE = '### TSC (Technical Steering Committee)';

@@ -14,4 +12,2 @@ const TSCE_TITLE = '### TSC Emeriti';

const logger = require('./logger');
class Collaborator {

@@ -49,9 +45,5 @@ constructor(login, name, email, type) {

async function getCollaborators(owner, repo) {
async function getCollaborators(readme, logger, owner, repo) {
// This is more or less taken from
// https://github.com/rvagg/iojs-tools/blob/master/pr-metadata/pr-metadata.js
const url = `https://raw.githubusercontent.com/${owner}/${repo}/master/README.md`;
const readme = await rp({ url });
const members = new Map();

@@ -110,5 +102,15 @@ let m;

/**
* @param {Map<string, Collaborator>} collaborators
* @param {{login?: string}} user
*/
function isCollaborator(collaborators, user) {
return (user && user.login && // could be a ghost
collaborators.get(user.login.toLowerCase()));
}
module.exports = {
getCollaborators,
Collaborator
Collaborator,
isCollaborator
};

@@ -5,14 +5,17 @@ 'use strict';

const chalk = require('chalk');
const { EOL } = require('os');
function paint(level) {
switch (level) {
function paint(label) {
switch (label) {
case 'ERROR':
case 'STACK':
case 'DATA':
case 'FATAL':
return chalk.red(`[${level}]`);
return chalk.red(`[${label}]`);
case 'WARN':
return chalk.yellow(`[${level}]`);
return chalk.yellow(`[${label}]`);
case 'INFO':
return chalk.blue(`[${level}]`);
return chalk.blue(`[${label}]`);
default:
return chalk.green(`[${level}]`);
return chalk.green(`[${label}]`);
}

@@ -23,3 +26,4 @@ }

.map((key) => [pino.levels.values[key], key.toUpperCase()]));
const pretty = pino.pretty({
const prettyOptions = {
forceColor: true,

@@ -33,7 +37,12 @@ formatter(obj) {

if (level === 'ERROR') {
return `${paint(level)} ${timestamp}${obj.type} ${obj.msg}\n` +
`[STACK] ${obj.stack}\n` +
`[DATA] ${JSON.stringify(obj.data, null, 2)}\n`;
let data;
if (obj.data !== undefined) {
data = JSON.stringify(obj.data, null, 2).replace(/\n/g, EOL);
}
return `${paint(level)} ${timestamp}${obj.type ? obj.type + ' ' : ''}` +
`${obj.msg}${EOL}` +
`${paint('STACK')} ${obj.stack || ''}${EOL}` +
`${paint('DATA')} ${data || ''}${EOL}`;
} else if (level === 'INFO' && obj.raw) {
return `${paint(level)} ${timestamp}${obj.msg || ''}\n${obj.raw}`;
return `${paint(level)} ${timestamp}${obj.msg || ''}${EOL}${obj.raw}`;
} else {

@@ -43,11 +52,13 @@ return `${paint(level)} ${timestamp}${obj.msg}`;

}
});
};
pretty.pipe(process.stdout);
const logger = pino({
name: 'node-core-utils',
safe: true,
level: 'trace'
}, pretty);
module.exports = logger;
module.exports = function loggerFactory(stream) {
const pretty = pino.pretty(prettyOptions);
pretty.pipe(stream);
const logger = pino({
name: 'node-core-utils',
safe: true,
level: 'trace'
}, pretty);
return logger;
};
'use strict';
const LinkParser = require('./links');
const { EOL } = require('os');
/**
* @typedef {{reviewer: Collaborator}} Reviewer
*/
class MetadataGenerator {
constructor(repo, pr, reviewers) {
/**
* @param {PRData} data
*/
constructor(data) {
const { repo, pr, reviewers } = data;
this.repo = repo;

@@ -12,8 +19,13 @@ this.pr = pr;

/**
* @returns {string}
*/
getMetadata() {
const { reviewers, repo, pr } = this;
const {
reviewers: { approved: reviewedBy },
pr: { url: prUrl, bodyHTML: op },
repo
} = this;
const prUrl = pr.url;
const reviewedBy = reviewers.approved;
const parser = new LinkParser(repo, pr.bodyHTML);
const parser = new LinkParser(repo, op);
const fixes = parser.getFixes();

@@ -27,18 +39,19 @@ const refs = parser.getRefs();

let meta = [
'-------------------------------- >8 --------------------------------',
`PR-URL: ${output.prUrl}`
];
meta = meta.concat(output.fixes.map((fix) => `Fixes: ${fix}`));
meta = meta.concat(output.refs.map((ref) => `Refs: ${ref}`));
meta = meta.concat(output.reviewedBy.map((r) => {
return `Reviewed-By: ${r.reviewer.getContact()}`;
}));
meta = meta.concat(output.fixes.map((fix) => `Fixes: ${fix}`));
meta = meta.concat(output.refs.map((ref) => `Refs: ${ref}`));
meta.push(
'-------------------------------- 8< --------------------------------'
);
return meta.join('\n');
return meta.join(EOL);
}
}
MetadataGenerator.SCISSORS = [
'-------------------------------- >8 --------------------------------',
'-------------------------------- 8< --------------------------------'
];
module.exports = MetadataGenerator;

@@ -13,4 +13,9 @@ 'use strict';

const logger = require('./logger');
const ReviewAnalyzer = require('./reviews');
const {
REVIEW_SOURCES: { FROM_COMMENT }
} = require('./reviews');
const {
FIRST_TIME_CONTRIBUTOR, FIRST_TIMER
} = require('./user_status');
const CIParser = require('./ci');

@@ -21,24 +26,32 @@ const CI_TYPES = CIParser.TYPES;

class PRChecker {
constructor(pr, reviewers, comments) {
/**
* @param {{}} logger
* @param {PRData} data
*/
constructor(logger, data) {
this.logger = logger;
const {
pr, reviewers, comments, reviews, commits, collaborators
} = data;
this.reviewers = reviewers;
this.pr = pr;
this.comments = comments;
this.reviews = reviews;
this.commits = commits;
this.collaboratorEmails = new Set(
Array.from(collaborators).map((c) => c[1].email)
);
}
checkReviews() {
const { rejected, approved } = this.reviewers;
if (rejected.length > 0) {
for (const { reviewer, review } of rejected) {
logger.warn(`${reviewer.getName()}) rejected in ${review.ref}`);
}
checkAll() {
this.checkReviews();
this.checkPRWait(new Date());
this.checkCI();
if (this.authorIsNew()) {
this.checkAuthor();
}
if (approved.length === 0) {
logger.warn('This PR has not been approved yet');
} else {
for (const { reviewer, review } of approved) {
if (review.source === ReviewAnalyzer.SOURCES.FROM_COMMENT) {
logger.info(`${reviewer.getName()}) approved in via LGTM in comments`);
}
}
}
// TODO: maybe invalidate review after new commits?
// TODO: check for pre-backport, Github API v4
// does not support reading files changed
}

@@ -58,5 +71,6 @@

checkReviewers() {
const { rejected, approved } = this.reviewers;
const pr = this.pr;
checkReviews() {
const {
pr, logger, reviewers: { rejected, approved }
} = this;

@@ -68,2 +82,5 @@ if (rejected.length === 0) {

logger.warn(`Rejections: ${rejected.length}${hint}`);
for (const { reviewer, review } of rejected) {
logger.warn(`${reviewer.getName()}) rejected in ${review.ref}`);
}
}

@@ -75,2 +92,9 @@ if (approved.length === 0) {

logger.info(`Approvals: ${approved.length}${hint}`);
for (const { reviewer, review } of approved) {
if (review.source === FROM_COMMENT) {
logger.info(`${reviewer.getName()}) approved in via LGTM in comments`);
}
}
const labels = pr.labels.nodes.map((l) => l.name);

@@ -86,5 +110,7 @@ if (labels.includes('semver-major')) {

getWait() {
/**
* @param {Date} now
*/
getWait(now) {
const createTime = new Date(this.pr.createdAt);
const now = new Date();
const utcDay = now.getUTCDay();

@@ -105,6 +131,11 @@ // TODO: do we need to lose this a bit considering timezones?

// TODO: skip some PRs...we might need a label for that
checkPRWait() {
const wait = this.getWait(this.pr);
/**
* @param {Date} now
*/
checkPRWait(now) {
const { pr } = this;
const { logger } = this;
const wait = this.getWait(now);
if (wait.timeLeft > 0) {
const dateStr = new Date(this.pr.createdAt).toDateString();
const dateStr = new Date(pr.createdAt).toDateString();
const type = wait.isWeekend ? 'weekend' : 'weekday';

@@ -119,8 +150,9 @@ logger.info(`This PR was created on ${dateStr} (${type} in UTC)`);

checkCI() {
const comments = this.comments;
const { pr, logger, comments, reviews } = this;
const prNode = {
publishedAt: this.pr.createdAt,
bodyText: this.pr.bodyText
publishedAt: pr.createdAt,
bodyText: pr.bodyText
};
const ciMap = new CIParser(comments.concat([prNode])).parse();
const thread = comments.concat([prNode]).concat(reviews);
const ciMap = new CIParser(thread).parse();
if (!ciMap.size) {

@@ -137,4 +169,57 @@ logger.warn('No CI runs detected');

}
authorIsNew() {
const assoc = this.pr.authorAssociation;
return assoc === FIRST_TIME_CONTRIBUTOR || assoc === FIRST_TIMER;
}
checkAuthor() {
const { logger, commits, pr } = this;
const oddCommits = this.filterOddCommits(commits);
if (!oddCommits.length) {
return;
}
const prAuthor = pr.author.login;
logger.warn(`PR is opened by @${prAuthor}`);
for (const c of oddCommits) {
const { oid, author } = c.commit;
const hash = oid.slice(0, 7);
logger.warn(`Author ${author.email} of commit ${hash} ` +
`does not match committer or PR author`);
}
}
filterOddCommits(commits) {
return commits.filter((c) => this.isOddAuthor(c.commit));
}
isOddAuthor(commit) {
const { pr, collaboratorEmails } = this;
// If they have added the alternative email to their account,
// commit.authoredByCommitter should be set to true by Github
if (commit.authoredByCommitter) {
return false;
}
// The commit author is one of the collaborators, they should know
// what they are doing anyway
if (collaboratorEmails.has(commit.author.email)) {
return false;
}
if (commit.author.email === pr.author.email) {
return false;
}
// At this point, the commit:
// 1. is not authored by the commiter i.e. author email is not in the
// committer's Github account
// 2. is not authored by a collaborator
// 3. is not authored by the people opening the PR
return true;
}
}
module.exports = PRChecker;
'use strict';
const rp = require('request-promise-native');
const auth = require('./auth');
const fs = require('fs');
const path = require('path');
async function request(query, variables) {
const options = {
uri: 'https://api.github.com/graphql',
method: 'POST',
headers: {
'Authorization': `Basic ${await auth()}`,
'User-Agent': 'node-check-pr'
},
json: true,
gzip: true,
body: {
query: query,
variables: variables
}
};
// console.log(options);
const result = await rp(options);
if (result.errors) {
const err = new Error('GraphQL request Error');
err.data = {
// query: query,
variables: variables,
errors: result.errors
};
throw err;
class Request {
constructor(credentials) {
this.credentials = credentials;
}
return result.data;
}
async function requestAll(query, variables, path) {
let after = null;
let all = [];
// first page
do {
const varWithPage = Object.assign({
after
}, variables);
const data = await request(query, varWithPage);
let current = data;
for (const step of path) {
current = current[step];
}
// current should have:
// totalCount
// pageInfo { hasNextPage, endCursor }
// nodes
all = all.concat(current.nodes);
if (current.pageInfo.hasNextPage) {
after = current.pageInfo.endCursor;
loadQuery(file) {
const filePath = path.resolve(__dirname, '..', 'queries', `${file}.gql`);
return fs.readFileSync(filePath, 'utf8');
}
async promise() {
return rp(...arguments);
}
async gql(name, variables, path) {
const query = this.loadQuery(name);
if (path) {
const result = await this.requestAll(query, variables, path);
return result;
} else {
after = null;
const result = await this.request(query, variables);
return result;
}
} while (after !== null);
}
return all;
async request(query, variables) {
const options = {
uri: 'https://api.github.com/graphql',
method: 'POST',
headers: {
'Authorization': `Basic ${this.credentials}`,
'User-Agent': 'node-core-utils'
},
json: true,
gzip: true,
body: {
query: query,
variables: variables
}
};
// console.log(options);
const result = await rp(options);
if (result.errors) {
const err = new Error('GraphQL request Error');
err.data = {
// query: query,
variables: variables,
errors: result.errors
};
throw err;
}
return result.data;
}
async requestAll(query, variables, path) {
let after = null;
let all = [];
// first page
do {
const varWithPage = Object.assign({
after
}, variables);
const data = await this.request(query, varWithPage);
let current = data;
for (const step of path) {
current = current[step];
}
// current should have:
// totalCount
// pageInfo { hasNextPage, endCursor }
// nodes
all = all.concat(current.nodes);
if (current.pageInfo.hasNextPage) {
after = current.pageInfo.endCursor;
} else {
after = null;
}
} while (after !== null);
return all;
}
}
module.exports = {
request,
requestAll
};
module.exports = Request;
'use strict';
const {
PENDING, COMMENTED, APPROVED, CHANGES_REQUESTED, DISMISSED
} = require('./review_state');
const { isCollaborator } = require('./collaborators');
const { ascending } = require('./comp');
const LGTM_RE = /(\W|^)lgtm(\W|$)/i;

@@ -14,7 +13,6 @@ const FROM_REVIEW = 'review';

/**
*
* @param {string} state
* @param {string} state
* @param {string} date // ISO date string
* @param {string} ref
* @param {string} source
* @param {string} ref
* @param {string} source
*/

@@ -29,9 +27,22 @@ constructor(state, date, ref, source) {

/**
* @typedef {Object} GHReview
* @property {string} bodyText
* @property {string} state
* @property {{login: string}} author
* @property {string} url
* @property {string} publishedAt
*
* @typedef {Object} GHComment
* @property {string} bodyText
* @property {{login: string}} author
* @property {string} publishedAt
*
*/
class ReviewAnalyzer {
/**
* @param {{}[]} reviewes
* @param {{}[]} comments
* @param {Map<string, Collaborator>} collaborators
* @param {PRData} data
*/
constructor(reviews, comments, collaborators) {
constructor(data) {
const { reviews, comments, collaborators } = data;
this.reviews = reviews;

@@ -42,2 +53,5 @@ this.comments = comments;

/**
* @returns {Map<string, Review>}
*/
mapByGithubReviews() {

@@ -49,4 +63,3 @@ const map = new Map();

.filter((r) => {
return (r.author && r.author.login && // could be a ghost
collaborators.get(r.author.login.toLowerCase()));
return (isCollaborator(collaborators, r.author));
}).sort((a, b) => {

@@ -85,3 +98,3 @@ return ascending(a.publishedAt, b.publishedAt);

/**
* @param {Map<string, Review>} oldMap
* @param {Map<string, Review>} oldMap
* @returns {Map<string, Review>}

@@ -94,4 +107,3 @@ */

.filter((c) => {
return (c.author && c.author.login && // could be a ghost
collaborators.get(c.author.login.toLowerCase()));
return (isCollaborator(collaborators, c.author));
}).sort((a, b) => {

@@ -115,2 +127,6 @@ return ascending(a.publishedAt, b.publishedAt);

/**
* @typedef {{reviwewer: Collaborator, review: Review}[]} ReviewerList
* @returns {{approved: ReviewerList, rejected: ReviewerList}}
*/
getReviewers() {

@@ -136,6 +152,10 @@ const ghReviews = this.mapByGithubReviews();

ReviewAnalyzer.SOURCES = {
const REVIEW_SOURCES = {
FROM_COMMENT, FROM_REVIEW
};
module.exports = ReviewAnalyzer;
module.exports = {
ReviewAnalyzer,
Review,
REVIEW_SOURCES
};
{
"name": "node-core-utils",
"version": "1.3.0",
"description": "",
"version": "1.4.0",
"description": "Utilities for Node.js core collaborators",
"main": "./bin/metadata.js",

@@ -15,5 +15,15 @@ "bin": {

"coverage-all": "nyc npm run test-all",
"lint": "eslint ."
"lint": "eslint .",
"report-coverage": "nyc report --reporter=lcov > coverage.lcov && codecov"
},
"author": "Joyee Cheung <joyeec9h3@gmail.com>",
"repository": {
"type": "git",
"url": "git+ssh://git@github.com:joyeecheung/node-core-utils.git"
},
"files": [
"lib/",
"bin/",
"queries/"
],
"license": "MIT",

@@ -25,5 +35,7 @@ "dependencies": {

"request": "^2.83.0",
"request-promise-native": "^1.0.5"
"request-promise-native": "^1.0.5",
"yargs": "^10.0.3"
},
"devDependencies": {
"codecov": "^3.0.0",
"eslint": "^4.9.0",

@@ -36,8 +48,9 @@ "eslint-config-standard": "^10.2.1",

"intelli-espower-loader": "^1.0.1",
"mkdirp": "^0.5.1",
"mocha": "^4.0.1",
"nyc": "^11.2.1",
"power-assert": "^1.4.4",
"mkdirp": "^0.5.1",
"rimraf": "^2.6.2"
"rimraf": "^2.6.2",
"sinon": "^4.0.2"
}
}
# Node.js Core Utilities
[![npm](https://img.shields.io/npm/v/node-core-utils.svg?style=flat-square)](https://npmjs.org/package/node-core-utils)
[![Build Status](https://travis-ci.org/joyeecheung/node-core-utils.svg?branch=master)](https://travis-ci.org/joyeecheung/node-core-utils)
[![codecov](https://codecov.io/gh/joyeecheung/node-core-utils/branch/master/graph/badge.svg)](https://codecov.io/gh/joyeecheung/node-core-utils)
[![Known Vulnerabilities](https://snyk.io/test/github/joyeecheung/node-core-utils/badge.svg)](https://snyk.io/test/github/joyeecheung/node-core-utils)

@@ -12,3 +14,5 @@ CLI tools for Node.js Core collaborators

Note: You don't need to check any boxes, these tools only require public access(for now).
Note: We need to read the email of the PR author in order to check if it matches
the email of the commit author. This requires checking the box `user:email` when
you create the personal access token (you can edit the permission later as well).

@@ -40,2 +44,36 @@ Then create a file named `.ncurc` under your `$HOME` directory (`~/.ncurc`);

```
get-metadata <identifier> [owner] [repo]
Retrieves metadata for a PR and validates them against nodejs/node PR rules
Options:
--version Show version number [boolean]
-o, --owner GitHub owner of the PR repository [string]
-r, --repo GitHub repository of the PR [string]
-h, --help Show help [boolean]
```
Examples:
```bash
PRID=12345
# fetch metadata and run checks on nodejs/node/pull/$PRID
$ get-metadata $PRID
# is equivalent to
$ get-metadata https://github.com/nodejs/node/pull/$PRID
# is equivalent to
$ get-metadata $PRID -o nodejs -r node
# Or, redirect the metadata to a file while see the checks in stderr
$ get-metadata $PRID > msg.txt
# Using it to amend commit messages:
$ git show -s --format=%B > msg.txt
$ echo "" >> msg.txt
$ get-metadata $PRID >> msg.txt
$ git commit --amend -F msg.txt
```
### TODO

@@ -48,4 +86,3 @@

- [x] Check for CI runs
- [ ] Check if commiters match authors
- Only when `"authorAssociation": "FIRST_TIME_CONTRIBUTOR"`
- [x] Check if commiters match authors
- [x] Check 48-hour wait

@@ -52,0 +89,0 @@ - [x] Check two TSC approval for semver-major

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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