add
This commit is contained in:
		
							
								
								
									
										131
									
								
								pkg/axios/bin/GithubAPI.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								pkg/axios/bin/GithubAPI.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,131 @@
 | 
			
		||||
import util from "util";
 | 
			
		||||
import cp from "child_process";
 | 
			
		||||
import {parseVersion} from "./helpers/parser.js";
 | 
			
		||||
import githubAxios from "./githubAxios.js";
 | 
			
		||||
import memoize from 'memoizee';
 | 
			
		||||
 | 
			
		||||
const exec = util.promisify(cp.exec);
 | 
			
		||||
 | 
			
		||||
export default class GithubAPI {
 | 
			
		||||
  constructor(owner, repo) {
 | 
			
		||||
    if (!owner) {
 | 
			
		||||
      throw new Error('repo owner must be specified');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!repo) {
 | 
			
		||||
      throw new Error('repo must be specified');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.repo = repo;
 | 
			
		||||
    this.owner = owner;
 | 
			
		||||
    this.axios = githubAxios.create({
 | 
			
		||||
      baseURL: `https://api.github.com/repos/${this.owner}/${this.repo}/`,
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async createComment(issue, body) {
 | 
			
		||||
    return (await this.axios.post(`/issues/${issue}/comments`, {body})).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getComments(issue, {desc = false, per_page= 100, page = 1} = {}) {
 | 
			
		||||
    return (await this.axios.get(`/issues/${issue}/comments`, {params: {direction: desc ? 'desc' : 'asc', per_page, page}})).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getComment(id) {
 | 
			
		||||
    return (await this.axios.get(`/issues/comments/${id}`)).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async updateComment(id, body) {
 | 
			
		||||
    return (await this.axios.patch(`/issues/comments/${id}`, {body})).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async appendLabels(issue, labels) {
 | 
			
		||||
    return (await this.axios.post(`/issues/${issue}/labels`, {labels})).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getUser(user) {
 | 
			
		||||
    return (await githubAxios.get(`/users/${user}`)).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async isCollaborator(user) {
 | 
			
		||||
    try {
 | 
			
		||||
      return (await this.axios.get(`/collaborators/${user}`)).status === 204;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async deleteLabel(issue, label) {
 | 
			
		||||
    return (await this.axios.delete(`/issues/${issue}/labels/${label}`)).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getIssue(issue) {
 | 
			
		||||
    return (await this.axios.get(`/issues/${issue}`)).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getPR(issue) {
 | 
			
		||||
    return (await this.axios.get(`/pulls/${issue}`)).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getIssues({state= 'open', labels, sort = 'created', desc = false, per_page = 100, page = 1}) {
 | 
			
		||||
    return (await this.axios.get(`/issues`, {params: {state, labels, sort, direction: desc ? 'desc' : 'asc', per_page, page}})).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async updateIssue(issue, data) {
 | 
			
		||||
    return (await this.axios.patch(`/issues/${issue}`, data)).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async closeIssue(issue) {
 | 
			
		||||
    return this.updateIssue(issue, {
 | 
			
		||||
      state: "closed"
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getReleases({per_page = 30, page= 1} = {}) {
 | 
			
		||||
    return (await this.axios.get(`/releases`, {params: {per_page, page}})).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getRelease(release = 'latest') {
 | 
			
		||||
    return (await this.axios.get(parseVersion(release) ? `/releases/tags/${release}` : `/releases/${release}`)).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getTags({per_page = 30, page= 1} = {}) {
 | 
			
		||||
    return (await this.axios.get(`/tags`, {params: {per_page, page}})).data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async reopenIssue(issue) {
 | 
			
		||||
    return this.updateIssue(issue, {
 | 
			
		||||
      state: "open"
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static async getTagRef(tag) {
 | 
			
		||||
    try {
 | 
			
		||||
      return (await exec(`git show-ref --tags "refs/tags/${tag}"`)).stdout.split(' ')[0];
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static async getLatestTag() {
 | 
			
		||||
    try{
 | 
			
		||||
      const {stdout} = await exec(`git for-each-ref refs/tags --sort=-taggerdate --format='%(refname)' --count=1`);
 | 
			
		||||
 | 
			
		||||
      return stdout.split('/').pop();
 | 
			
		||||
    } catch (e) {}
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static normalizeTag(tag){
 | 
			
		||||
    return tag ? 'v' + tag.replace(/^v/, '') : '';
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const {prototype} = GithubAPI;
 | 
			
		||||
 | 
			
		||||
['getUser', 'isCollaborator'].forEach(methodName => {
 | 
			
		||||
  prototype[methodName] = memoize(prototype[methodName], { promise: true })
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
['get', 'post', 'put', 'delete', 'isAxiosError'].forEach((method) => prototype[method] = function(...args){
 | 
			
		||||
  return this.axios[method](...args);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										128
									
								
								pkg/axios/bin/RepoBot.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								pkg/axios/bin/RepoBot.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
			
		||||
import GithubAPI from "./GithubAPI.js";
 | 
			
		||||
import api from './api.js';
 | 
			
		||||
import Handlebars from "handlebars";
 | 
			
		||||
import fs from "fs/promises";
 | 
			
		||||
import {colorize} from "./helpers/colorize.js";
 | 
			
		||||
import {getReleaseInfo} from "./contributors.js";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import {fileURLToPath} from "url";
 | 
			
		||||
 | 
			
		||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
 | 
			
		||||
 | 
			
		||||
const NOTIFY_PR_TEMPLATE = path.resolve(__dirname, '../templates/pr_published.hbs');
 | 
			
		||||
 | 
			
		||||
const normalizeTag = (tag) => tag ? 'v' + tag.replace(/^v/, '') : '';
 | 
			
		||||
 | 
			
		||||
const GITHUB_BOT_LOGIN = 'github-actions[bot]';
 | 
			
		||||
 | 
			
		||||
const skipCollaboratorPRs = true;
 | 
			
		||||
 | 
			
		||||
class RepoBot {
 | 
			
		||||
  constructor(options) {
 | 
			
		||||
    const {
 | 
			
		||||
      owner, repo,
 | 
			
		||||
      templates
 | 
			
		||||
    } = options || {};
 | 
			
		||||
 | 
			
		||||
    this.templates = Object.assign({
 | 
			
		||||
      published: NOTIFY_PR_TEMPLATE
 | 
			
		||||
    }, templates);
 | 
			
		||||
 | 
			
		||||
    this.github = api || new GithubAPI(owner, repo);
 | 
			
		||||
 | 
			
		||||
    this.owner = this.github.owner;
 | 
			
		||||
    this.repo = this.github.repo;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async addComment(targetId, message) {
 | 
			
		||||
    return this.github.createComment(targetId, message);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async notifyPRPublished(id, tag) {
 | 
			
		||||
    let pr;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      pr = await this.github.getPR(id);
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      if(err.response?.status === 404) {
 | 
			
		||||
        throw new Error(`PR #${id} not found (404)`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      throw err;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    tag = normalizeTag(tag);
 | 
			
		||||
 | 
			
		||||
    const {merged, labels, user: {login, type}} = pr;
 | 
			
		||||
 | 
			
		||||
    const isBot = type === 'Bot';
 | 
			
		||||
 | 
			
		||||
    if (!merged) {
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await this.github.appendLabels(id, [tag]);
 | 
			
		||||
 | 
			
		||||
    if (isBot || labels.find(({name}) => name === 'automated pr') || (skipCollaboratorPRs && await this.github.isCollaborator(login))) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const comments = await this.github.getComments(id, {desc: true});
 | 
			
		||||
 | 
			
		||||
    const comment = comments.find(
 | 
			
		||||
      ({body, user}) => user.login === GITHUB_BOT_LOGIN && body.indexOf('published in') >= 0
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if (comment) {
 | 
			
		||||
      console.log(colorize()`Release comment [${comment.html_url}] already exists in #${pr.id}`);
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const author = await this.github.getUser(login);
 | 
			
		||||
 | 
			
		||||
    author.isBot = isBot;
 | 
			
		||||
 | 
			
		||||
    const message = await this.constructor.renderTemplate(this.templates.published, {
 | 
			
		||||
      id,
 | 
			
		||||
      author,
 | 
			
		||||
      release: {
 | 
			
		||||
        tag,
 | 
			
		||||
        url: `https://github.com/${this.owner}/${this.repo}/releases/tag/${tag}`
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return await this.addComment(id, message);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async notifyPublishedPRs(tag) {
 | 
			
		||||
    tag = normalizeTag(tag);
 | 
			
		||||
 | 
			
		||||
    const release = await getReleaseInfo(tag);
 | 
			
		||||
 | 
			
		||||
    if (!release) {
 | 
			
		||||
      throw Error(colorize()`Can't get release info for ${tag}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const {merges} = release;
 | 
			
		||||
 | 
			
		||||
    console.log(colorize()`Found ${merges.length} PRs in ${tag}:`);
 | 
			
		||||
 | 
			
		||||
    let i = 0;
 | 
			
		||||
 | 
			
		||||
    for (const pr of merges) {
 | 
			
		||||
      try {
 | 
			
		||||
        console.log(colorize()`${i++}) Notify PR #${pr.id}`)
 | 
			
		||||
        const result = await this.notifyPRPublished(pr.id, tag);
 | 
			
		||||
        console.log('✔️', result ? 'Label, comment' : 'Label');
 | 
			
		||||
      } catch (err) {
 | 
			
		||||
        console.warn(colorize('green', 'red')`❌ Failed notify PR ${pr.id}: ${err.message}`);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static async renderTemplate(template, data) {
 | 
			
		||||
    return Handlebars.compile(String(await fs.readFile(template)))(data);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default RepoBot;
 | 
			
		||||
							
								
								
									
										28
									
								
								pkg/axios/bin/actions/notify_published.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								pkg/axios/bin/actions/notify_published.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
import minimist from "minimist";
 | 
			
		||||
import RepoBot from '../RepoBot.js';
 | 
			
		||||
import fs from 'fs/promises';
 | 
			
		||||
 | 
			
		||||
const argv = minimist(process.argv.slice(2));
 | 
			
		||||
console.log(argv);
 | 
			
		||||
 | 
			
		||||
let {tag} = argv;
 | 
			
		||||
 | 
			
		||||
(async() => {
 | 
			
		||||
  if (!tag || tag === true) {
 | 
			
		||||
    const {version} = JSON.parse((await fs.readFile('./package.json')).toString());
 | 
			
		||||
 | 
			
		||||
    tag = 'v' + version;
 | 
			
		||||
  } else if (typeof tag !== 'string') {
 | 
			
		||||
 | 
			
		||||
    throw new Error('tag must be a string');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const bot = new RepoBot();
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    await bot.notifyPublishedPRs(tag);
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    console.warn('Error:', err.message);
 | 
			
		||||
  }
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								pkg/axios/bin/api.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								pkg/axios/bin/api.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
import GithubAPI from "./GithubAPI.js";
 | 
			
		||||
 | 
			
		||||
export default new GithubAPI('axios', 'axios');
 | 
			
		||||
							
								
								
									
										29
									
								
								pkg/axios/bin/check-build-version.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								pkg/axios/bin/check-build-version.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
import fs from 'fs';
 | 
			
		||||
import assert from 'assert';
 | 
			
		||||
import axios from '../index.js';
 | 
			
		||||
import axiosBuild from '../dist/node/axios.cjs';
 | 
			
		||||
 | 
			
		||||
const {version} = JSON.parse(fs.readFileSync('./package.json'));
 | 
			
		||||
 | 
			
		||||
console.log('Checking versions...\n----------------------------')
 | 
			
		||||
 | 
			
		||||
console.log(`Package version: v${version}`);
 | 
			
		||||
console.log(`Axios version: v${axios.VERSION}`);
 | 
			
		||||
console.log(`Axios build version: v${axiosBuild.VERSION}`);
 | 
			
		||||
console.log(`----------------------------`);
 | 
			
		||||
 | 
			
		||||
assert.strictEqual(
 | 
			
		||||
  version,
 | 
			
		||||
  axios.VERSION,
 | 
			
		||||
  `Version mismatch between package and Axios ${version} != ${axios.VERSION}`
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
assert.strictEqual(
 | 
			
		||||
  version,
 | 
			
		||||
  axiosBuild.VERSION,
 | 
			
		||||
  `Version mismatch between package and build ${version} != ${axiosBuild.VERSION}`
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
console.log('✔️ PASSED\n');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										241
									
								
								pkg/axios/bin/contributors.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								pkg/axios/bin/contributors.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,241 @@
 | 
			
		||||
import axios from "./githubAxios.js";
 | 
			
		||||
import util from "util";
 | 
			
		||||
import cp from "child_process";
 | 
			
		||||
import Handlebars from "handlebars";
 | 
			
		||||
import fs from "fs/promises";
 | 
			
		||||
import {colorize} from "./helpers/colorize.js";
 | 
			
		||||
 | 
			
		||||
const exec = util.promisify(cp.exec);
 | 
			
		||||
 | 
			
		||||
const ONE_MB = 1024 * 1024;
 | 
			
		||||
 | 
			
		||||
const removeExtraLineBreaks = (str) => str.replace(/(?:\r\n|\r|\n){3,}/gm, '\r\n\r\n');
 | 
			
		||||
 | 
			
		||||
const cleanTemplate = template => template
 | 
			
		||||
  .replace(/\n +/g, '\n')
 | 
			
		||||
  .replace(/^ +/, '')
 | 
			
		||||
  .replace(/\n\n\n+/g, '\n\n')
 | 
			
		||||
  .replace(/\n\n$/, '\n');
 | 
			
		||||
 | 
			
		||||
const getUserFromCommit = ((commitCache) => async (sha) => {
 | 
			
		||||
  try {
 | 
			
		||||
    if(commitCache[sha] !== undefined) {
 | 
			
		||||
      return commitCache[sha];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    console.log(colorize()`fetch github commit info (${sha})`);
 | 
			
		||||
 | 
			
		||||
    const {data} = await axios.get(`https://api.github.com/repos/axios/axios/commits/${sha}`);
 | 
			
		||||
 | 
			
		||||
    return commitCache[sha] = {
 | 
			
		||||
      ...data.commit.author,
 | 
			
		||||
      ...data.author,
 | 
			
		||||
      avatar_url_sm: data.author.avatar_url ? data.author.avatar_url + '&s=18' : '',
 | 
			
		||||
    };
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    return commitCache[sha] = null;
 | 
			
		||||
  }
 | 
			
		||||
})({});
 | 
			
		||||
 | 
			
		||||
const getIssueById = ((cache) => async (id) => {
 | 
			
		||||
  if(cache[id] !== undefined) {
 | 
			
		||||
    return cache[id];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    const {data} = await axios.get(`https://api.github.com/repos/axios/axios/issues/${id}`);
 | 
			
		||||
 | 
			
		||||
    return cache[id] = data;
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
})({});
 | 
			
		||||
 | 
			
		||||
const getUserInfo = ((userCache) => async (userEntry) => {
 | 
			
		||||
  const {email, commits} = userEntry;
 | 
			
		||||
 | 
			
		||||
  if (userCache[email] !== undefined) {
 | 
			
		||||
    return userCache[email];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  console.log(colorize()`fetch github user info [${userEntry.name}]`);
 | 
			
		||||
 | 
			
		||||
  return userCache[email] = {
 | 
			
		||||
    ...userEntry,
 | 
			
		||||
    ...await getUserFromCommit(commits[0].hash)
 | 
			
		||||
  }
 | 
			
		||||
})({});
 | 
			
		||||
 | 
			
		||||
const deduplicate = (authors) => {
 | 
			
		||||
  const loginsMap = {};
 | 
			
		||||
  const combined= {};
 | 
			
		||||
 | 
			
		||||
  const assign = (a, b) => {
 | 
			
		||||
    const {insertions, deletions, points, ...rest} = b;
 | 
			
		||||
 | 
			
		||||
    Object.assign(a, rest);
 | 
			
		||||
 | 
			
		||||
    a.insertions += insertions;
 | 
			
		||||
    a.deletions += insertions;
 | 
			
		||||
    a.insertions += insertions;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for(const [email, user] of Object.entries(authors)) {
 | 
			
		||||
    const {login} = user;
 | 
			
		||||
    let entry;
 | 
			
		||||
 | 
			
		||||
    if(login && (entry = loginsMap[login])) {
 | 
			
		||||
       assign(entry, user);
 | 
			
		||||
    } else {
 | 
			
		||||
      login && (loginsMap[login] = user);
 | 
			
		||||
      combined[email] = user;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return combined;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getReleaseInfo = ((releaseCache) => async (tag) => {
 | 
			
		||||
  if(releaseCache[tag] !== undefined) {
 | 
			
		||||
    return releaseCache[tag];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const isUnreleasedTag = !tag;
 | 
			
		||||
 | 
			
		||||
  const version = 'v' + tag.replace(/^v/, '');
 | 
			
		||||
 | 
			
		||||
  const command = isUnreleasedTag ?
 | 
			
		||||
    `npx auto-changelog --unreleased-only --stdout --commit-limit false --template json` :
 | 
			
		||||
    `npx auto-changelog ${
 | 
			
		||||
      version ? '--starting-version ' + version + ' --ending-version ' + version : ''
 | 
			
		||||
    } --stdout --commit-limit false --template json`;
 | 
			
		||||
 | 
			
		||||
  console.log(command);
 | 
			
		||||
 | 
			
		||||
  const {stdout} = await exec(command, {maxBuffer: 10 * ONE_MB});
 | 
			
		||||
 | 
			
		||||
  const release = JSON.parse(stdout)[0];
 | 
			
		||||
 | 
			
		||||
  if(release) {
 | 
			
		||||
    const authors = {};
 | 
			
		||||
 | 
			
		||||
    const commits = [
 | 
			
		||||
      ...release.commits,
 | 
			
		||||
      ...release.fixes.map(fix => fix.commit),
 | 
			
		||||
      ...release.merges.map(fix => fix.commit)
 | 
			
		||||
    ].filter(Boolean);
 | 
			
		||||
 | 
			
		||||
    const commitMergeMap = {};
 | 
			
		||||
 | 
			
		||||
    for(const merge of release.merges) {
 | 
			
		||||
      commitMergeMap[merge.commit.hash] = merge.id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const {hash, author, email, insertions, deletions} of commits) {
 | 
			
		||||
      const entry = authors[email] = (authors[email] || {
 | 
			
		||||
        name: author,
 | 
			
		||||
        prs: [],
 | 
			
		||||
        email,
 | 
			
		||||
        commits: [],
 | 
			
		||||
        insertions: 0, deletions: 0
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      entry.commits.push({hash});
 | 
			
		||||
 | 
			
		||||
      let pr;
 | 
			
		||||
 | 
			
		||||
      if((pr = commitMergeMap[hash])) {
 | 
			
		||||
        entry.prs.push(pr);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      console.log(colorize()`Found commit [${hash}]`);
 | 
			
		||||
 | 
			
		||||
      entry.displayName = entry.name || author || entry.login;
 | 
			
		||||
 | 
			
		||||
      entry.github = entry.login ? `https://github.com/${encodeURIComponent(entry.login)}` : '';
 | 
			
		||||
 | 
			
		||||
      entry.insertions += insertions;
 | 
			
		||||
      entry.deletions += deletions;
 | 
			
		||||
      entry.points = entry.insertions + entry.deletions;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const [email, author] of Object.entries(authors)) {
 | 
			
		||||
      const entry = authors[email] = await getUserInfo(author);
 | 
			
		||||
 | 
			
		||||
      entry.isBot = entry.type === "Bot";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    release.authors = Object.values(deduplicate(authors))
 | 
			
		||||
      .sort((a, b) => b.points - a.points);
 | 
			
		||||
 | 
			
		||||
    release.allCommits = commits;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  releaseCache[tag] = release;
 | 
			
		||||
 | 
			
		||||
  return release;
 | 
			
		||||
})({});
 | 
			
		||||
 | 
			
		||||
const renderContributorsList = async (tag, template) => {
 | 
			
		||||
  const release = await getReleaseInfo(tag);
 | 
			
		||||
 | 
			
		||||
  const compile = Handlebars.compile(String(await fs.readFile(template)))
 | 
			
		||||
 | 
			
		||||
  const content = compile(release);
 | 
			
		||||
 | 
			
		||||
  return removeExtraLineBreaks(cleanTemplate(content));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const renderPRsList = async (tag, template, {comments_threshold= 5, awesome_threshold= 5, label = 'add_to_changelog'} = {}) => {
 | 
			
		||||
  const release = await getReleaseInfo(tag);
 | 
			
		||||
 | 
			
		||||
  const prs = {};
 | 
			
		||||
 | 
			
		||||
  for(const merge of release.merges) {
 | 
			
		||||
    const pr = await getIssueById(merge.id);
 | 
			
		||||
 | 
			
		||||
    if (pr && pr.labels.find(({name})=> name === label)) {
 | 
			
		||||
      const {reactions, body} = pr;
 | 
			
		||||
      prs[pr.number] = pr;
 | 
			
		||||
      pr.isHot = pr.comments > comments_threshold;
 | 
			
		||||
      const points = reactions['+1'] +
 | 
			
		||||
        reactions['hooray'] + reactions['rocket'] + reactions['heart'] + reactions['laugh'] - reactions['-1'];
 | 
			
		||||
 | 
			
		||||
      pr.isAwesome = points > awesome_threshold;
 | 
			
		||||
 | 
			
		||||
      let match;
 | 
			
		||||
 | 
			
		||||
      pr.messages = [];
 | 
			
		||||
 | 
			
		||||
      if (body) {
 | 
			
		||||
        const reg = /```+changelog\n*(.+?)?\n*```/gms;
 | 
			
		||||
 | 
			
		||||
        while((match = reg.exec(body))) {
 | 
			
		||||
          match[1] && pr.messages.push(match[1]);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  release.prs = Object.values(prs);
 | 
			
		||||
 | 
			
		||||
  const compile = Handlebars.compile(String(await fs.readFile(template)))
 | 
			
		||||
 | 
			
		||||
  const content = compile(release);
 | 
			
		||||
 | 
			
		||||
  return removeExtraLineBreaks(cleanTemplate(content));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getTagRef = async (tag) => {
 | 
			
		||||
  try {
 | 
			
		||||
    return (await exec(`git show-ref --tags "refs/tags/${tag}"`)).stdout.split(' ')[0];
 | 
			
		||||
  } catch(e) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  renderContributorsList,
 | 
			
		||||
  getReleaseInfo,
 | 
			
		||||
  renderPRsList,
 | 
			
		||||
  getTagRef
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								pkg/axios/bin/githubAxios.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								pkg/axios/bin/githubAxios.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
import axios from '../index.js';
 | 
			
		||||
import {colorize} from "./helpers/colorize.js";
 | 
			
		||||
 | 
			
		||||
const {GITHUB_TOKEN} = process.env;
 | 
			
		||||
 | 
			
		||||
GITHUB_TOKEN ? console.log(`[GITHUB_TOKEN OK]`) : console.warn(`[GITHUB_TOKEN is not defined]`);
 | 
			
		||||
 | 
			
		||||
const defaultTransform = axios.defaults.transformRequest;
 | 
			
		||||
 | 
			
		||||
export default axios.create({
 | 
			
		||||
  transformRequest: [defaultTransform[0], function (data) {
 | 
			
		||||
    console.log(colorize()`[${this.method.toUpperCase()}] Request [${new URL(axios.getUri(this)).pathname}]`);
 | 
			
		||||
    return data;
 | 
			
		||||
  }],
 | 
			
		||||
  baseURL: 'https://api.github.com/',
 | 
			
		||||
  headers: {
 | 
			
		||||
    Authorization: GITHUB_TOKEN ? `token ${GITHUB_TOKEN}` : null
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										14
									
								
								pkg/axios/bin/helpers/colorize.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								pkg/axios/bin/helpers/colorize.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
import chalk from 'chalk';
 | 
			
		||||
 | 
			
		||||
export const colorize = (...colors)=> {
 | 
			
		||||
  if(!colors.length) {
 | 
			
		||||
    colors = ['green', 'cyan', 'magenta', 'blue', 'yellow', 'red'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const colorsCount = colors.length;
 | 
			
		||||
 | 
			
		||||
  return (strings, ...values) => {
 | 
			
		||||
    const {length} = values;
 | 
			
		||||
    return strings.map((str, i) => i < length ? str + chalk[colors[i%colorsCount]].bold(values[i]) : str).join('');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								pkg/axios/bin/helpers/parser.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								pkg/axios/bin/helpers/parser.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
export const matchAll = (text, regexp, cb) => {
 | 
			
		||||
  let match;
 | 
			
		||||
  while((match = regexp.exec(text))) {
 | 
			
		||||
    cb(match);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const parseSection = (body, name, cb) => {
 | 
			
		||||
  matchAll(body, new RegExp(`^(#+)\\s+${name}?(.*?)^\\1\\s+\\w+`, 'gims'), cb);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const parseVersion = (rawVersion) => /^v?(\d+).(\d+).(\d+)/.exec(rawVersion);
 | 
			
		||||
							
								
								
									
										78
									
								
								pkg/axios/bin/injectContributorsList.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								pkg/axios/bin/injectContributorsList.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
import fs from 'fs/promises';
 | 
			
		||||
import path from 'path';
 | 
			
		||||
import {renderContributorsList, getTagRef, renderPRsList} from './contributors.js';
 | 
			
		||||
import asyncReplace from 'string-replace-async';
 | 
			
		||||
import {fileURLToPath} from "url";
 | 
			
		||||
import {colorize} from "./helpers/colorize.js";
 | 
			
		||||
 | 
			
		||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
 | 
			
		||||
 | 
			
		||||
const CONTRIBUTORS_TEMPLATE = path.resolve(__dirname, '../templates/contributors.hbs');
 | 
			
		||||
const PRS_TEMPLATE = path.resolve(__dirname, '../templates/prs.hbs');
 | 
			
		||||
 | 
			
		||||
const injectSection = async (name, contributorsRE, injector, infile = '../CHANGELOG.md') => {
 | 
			
		||||
  console.log(colorize()`Checking ${name} sections in ${infile}`);
 | 
			
		||||
 | 
			
		||||
  infile = path.resolve(__dirname, infile);
 | 
			
		||||
 | 
			
		||||
  const content = String(await fs.readFile(infile));
 | 
			
		||||
  const headerRE = /^#+\s+\[([-_\d.\w]+)].+?$/mig;
 | 
			
		||||
 | 
			
		||||
  let tag;
 | 
			
		||||
  let index = 0;
 | 
			
		||||
  let isFirstTag = true;
 | 
			
		||||
 | 
			
		||||
  const newContent = await asyncReplace(content, headerRE, async (match, nextTag, offset) => {
 | 
			
		||||
    const releaseContent = content.slice(index, offset);
 | 
			
		||||
 | 
			
		||||
    const hasSection = contributorsRE.test(releaseContent);
 | 
			
		||||
 | 
			
		||||
    const currentTag = tag;
 | 
			
		||||
 | 
			
		||||
    tag = nextTag;
 | 
			
		||||
    index = offset + match.length;
 | 
			
		||||
 | 
			
		||||
    if(currentTag) {
 | 
			
		||||
      if (hasSection) {
 | 
			
		||||
        console.log(colorize()`[${currentTag}]: ✓ OK`);
 | 
			
		||||
      } else {
 | 
			
		||||
        const target = isFirstTag && (!await getTagRef(currentTag)) ? '' : currentTag;
 | 
			
		||||
 | 
			
		||||
        console.log(colorize()`[${currentTag}]: ❌ MISSED` + (!target ? ' (UNRELEASED)' : ''));
 | 
			
		||||
 | 
			
		||||
        isFirstTag = false;
 | 
			
		||||
 | 
			
		||||
        console.log(`Generating section...`);
 | 
			
		||||
 | 
			
		||||
        const section = await injector(target);
 | 
			
		||||
 | 
			
		||||
        if (!section) {
 | 
			
		||||
          return match;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        console.log(colorize()`\nRENDERED SECTION [${name}] for [${currentTag}]:`);
 | 
			
		||||
        console.log('-------------BEGIN--------------\n');
 | 
			
		||||
        console.log(section);
 | 
			
		||||
        console.log('--------------END---------------\n');
 | 
			
		||||
 | 
			
		||||
        return section + '\n' + match;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return match;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  await fs.writeFile(infile, newContent);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
await injectSection(
 | 
			
		||||
  'PRs',
 | 
			
		||||
  /^\s*### PRs/mi,
 | 
			
		||||
  (tag) => tag ? '' : renderPRsList(tag, PRS_TEMPLATE, {awesome_threshold: 5, comments_threshold: 7}),
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
await injectSection(
 | 
			
		||||
  'contributors',
 | 
			
		||||
  /^\s*### Contributors/mi,
 | 
			
		||||
  (tag) => renderContributorsList(tag, CONTRIBUTORS_TEMPLATE)
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										75
									
								
								pkg/axios/bin/pr.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								pkg/axios/bin/pr.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
			
		||||
import util from "util";
 | 
			
		||||
import cp from "child_process";
 | 
			
		||||
import Handlebars from "handlebars";
 | 
			
		||||
import fs from "fs/promises";
 | 
			
		||||
import prettyBytes from 'pretty-bytes';
 | 
			
		||||
import {gzipSize} from 'gzip-size';
 | 
			
		||||
 | 
			
		||||
const exec = util.promisify(cp.exec);
 | 
			
		||||
 | 
			
		||||
const getBlobHistory = async (filepath, maxCount= 5) => {
 | 
			
		||||
  const log = (await exec(
 | 
			
		||||
    `git log --max-count=${maxCount} --no-walk --tags=v* --oneline --format=%H%d -- ${filepath}`
 | 
			
		||||
  )).stdout;
 | 
			
		||||
 | 
			
		||||
  const commits = [];
 | 
			
		||||
 | 
			
		||||
  let match;
 | 
			
		||||
 | 
			
		||||
  const regexp = /^(\w+) \(tag: (v?[.\d]+)\)$/gm;
 | 
			
		||||
 | 
			
		||||
  while((match = regexp.exec(log))) {
 | 
			
		||||
    commits.push({
 | 
			
		||||
      sha: match[1],
 | 
			
		||||
      tag: match[2],
 | 
			
		||||
      size: await getBlobSize(filepath, match[1])
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return commits;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getBlobSize = async (filepath, sha ='HEAD') => {
 | 
			
		||||
  const size = (await exec(
 | 
			
		||||
    `git cat-file -s ${sha}:${filepath}`
 | 
			
		||||
  )).stdout;
 | 
			
		||||
 | 
			
		||||
  return size ? +size : 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const generateFileReport = async (files) => {
 | 
			
		||||
  const stat = {};
 | 
			
		||||
 | 
			
		||||
  for(const [name, file] of Object.entries(files)) {
 | 
			
		||||
    const commits = await getBlobHistory(file);
 | 
			
		||||
 | 
			
		||||
    stat[file] = {
 | 
			
		||||
      name,
 | 
			
		||||
      size: (await fs.stat(file)).size,
 | 
			
		||||
      path: file,
 | 
			
		||||
      gzip: await gzipSize(String(await fs.readFile(file))),
 | 
			
		||||
      commits,
 | 
			
		||||
      history: commits.map(({tag, size}) => `${prettyBytes(size)} (${tag})`).join(' ← ')
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return stat;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const generateBody = async ({files, template = './templates/pr.hbs'} = {}) => {
 | 
			
		||||
  const data = {
 | 
			
		||||
    files: await generateFileReport(files)
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  Handlebars.registerHelper('filesize', (bytes)=> prettyBytes(bytes));
 | 
			
		||||
 | 
			
		||||
  return Handlebars.compile(String(await fs.readFile(template)))(data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
console.log(await generateBody({
 | 
			
		||||
  files: {
 | 
			
		||||
    'Browser build (UMD)' : './dist/axios.min.js',
 | 
			
		||||
    'Browser build (ESM)' : './dist/esm/axios.min.js',
 | 
			
		||||
  }
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										22
									
								
								pkg/axios/bin/ssl_hotfix.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								pkg/axios/bin/ssl_hotfix.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
import {spawn} from 'child_process';
 | 
			
		||||
 | 
			
		||||
const args = process.argv.slice(2);
 | 
			
		||||
 | 
			
		||||
console.log(`Running ${args.join(' ')} on ${process.version}\n`);
 | 
			
		||||
 | 
			
		||||
const match = /v(\d+)/.exec(process.version);
 | 
			
		||||
 | 
			
		||||
const isHotfixNeeded = match && match[1] > 16;
 | 
			
		||||
 | 
			
		||||
isHotfixNeeded && console.warn('Setting --openssl-legacy-provider as ssl hotfix');
 | 
			
		||||
 | 
			
		||||
const test = spawn('cross-env',
 | 
			
		||||
  isHotfixNeeded ? ['NODE_OPTIONS=--openssl-legacy-provider', ...args] : args, {
 | 
			
		||||
    shell: true,
 | 
			
		||||
    stdio: 'inherit'
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
test.on('exit', function (code) {
 | 
			
		||||
  process.exit(code)
 | 
			
		||||
})
 | 
			
		||||
		Reference in New Issue
	
	Block a user