Skip to content
This repository has been archived by the owner on Mar 20, 2023. It is now read-only.

Commit

Permalink
Merge 1743321 into 1af133f
Browse files Browse the repository at this point in the history
  • Loading branch information
IvanGoncharov committed May 31, 2020
2 parents 1af133f + 1743321 commit 579caef
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 133 deletions.
97 changes: 50 additions & 47 deletions resources/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,77 @@
const fs = require('fs');
const path = require('path');
const assert = require('assert');

const babel = require('@babel/core');
const {
copyFile,
writeFile,
rmdirRecursive,
mkdirRecursive,
readdirRecursive,
parseSemver,
} = require('./utils');

const { rmdirRecursive, readdirRecursive } = require('./utils');

if (require.main === module) {
rmdirRecursive('./dist');
mkdirRecursive('./dist');

copyFile('./LICENSE', './dist/LICENSE');
copyFile('./README.md', './dist/README.md');
copyFile('./types/index.d.ts', './dist/index.d.ts');
fs.mkdirSync('./dist');

const srcFiles = readdirRecursive('./src', { ignoreDir: /^__.*__$/ });
for (const filepath of srcFiles) {
const srcPath = path.join('./src', filepath);
const destPath = path.join('./dist', filepath);

fs.mkdirSync(path.dirname(destPath), { recursive: true });
if (filepath.endsWith('.js')) {
buildJSFile(filepath);
fs.copyFileSync(srcPath, destPath + '.flow');

const cjs = babelBuild(srcPath, { envName: 'cjs' });
fs.writeFileSync(destPath, cjs);
}
}

fs.copyFileSync('./types/index.d.ts', './dist/index.d.ts');
fs.copyFileSync('./LICENSE', './dist/LICENSE');
fs.copyFileSync('./README.md', './dist/README.md');

// Should be done as the last step so only valid packages can be published
const packageJSON = buildPackageJSON();
writeFile('./dist/package.json', JSON.stringify(packageJSON, null, 2));
fs.writeFileSync('./dist/package.json', JSON.stringify(packageJSON, null, 2));

showStats();
}

function babelBuild(srcPath, options) {
return babel.transformFileSync(srcPath, options).code + '\n';
}

function buildPackageJSON() {
const packageJSON = require('../package.json');
delete packageJSON.private;
delete packageJSON.scripts;
delete packageJSON.devDependencies;

const { version } = packageJSON;
const versionMatch = /^\d+\.\d+\.\d+-?(.*)?$/.exec(version);
if (!versionMatch) {
throw new Error('Version does not match semver spec: ' + version);
}

const [, preReleaseTag] = versionMatch;

if (preReleaseTag != null) {
const [tag] = preReleaseTag.split('.');
assert(['alpha', 'beta', 'rc'].includes(tag), `"${tag}" tag is supported.`);

assert(!packageJSON.publishConfig, 'Can not override "publishConfig".');
packageJSON.publishConfig = { tag: tag || 'latest' };
}

return packageJSON;
}

function showStats() {
const fileTypes = {};
let totalSize = 0;

for (const filepath of readdirRecursive('./dist')) {
const name = filepath.split(path.sep).pop();
const [base, ...splitedExt] = name.split('.');
const ext = splitedExt.join('.');
const [base, ...splitExt] = name.split('.');
const ext = splitExt.join('.');

const filetype = ext ? '*.' + ext : base;
fileTypes[filetype] = fileTypes[filetype] || { filepaths: [], size: 0 };
Expand Down Expand Up @@ -80,33 +113,3 @@ function showStats() {
'Total'.padStart(typeMaxLength) + ' | ' + totalMB.padStart(sizeMaxLength),
);
}

function babelBuild(srcPath, envName) {
return babel.transformFileSync(srcPath, { envName }).code + '\n';
}

function buildJSFile(filepath) {
const srcPath = path.join('./src', filepath);
const destPath = path.join('./dist', filepath);

copyFile(srcPath, destPath + '.flow');
writeFile(destPath, babelBuild(srcPath, 'cjs'));
}

function buildPackageJSON() {
const packageJSON = require('../package.json');
delete packageJSON.private;
delete packageJSON.scripts;
delete packageJSON.devDependencies;

const { preReleaseTag } = parseSemver(packageJSON.version);
if (preReleaseTag != null) {
const [tag] = preReleaseTag.split('.');
assert(tag === 'rc', 'Only "rc" tag is supported.');

assert(!packageJSON.publishConfig, 'Can not override "publishConfig".');
packageJSON.publishConfig = { tag: tag || 'latest' };
}

return packageJSON;
}
155 changes: 97 additions & 58 deletions resources/gen-changelog.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

const util = require('util');
const https = require('https');
const { exec } = require('./utils');

const packageJSON = require('../package.json');

const { exec } = require('./utils');

const graphqlRequest = util.promisify(graphqlRequestImpl);
const labelsConfig = {
'PR: breaking change 💥': {
Expand Down Expand Up @@ -35,10 +37,10 @@ const labelsConfig = {
fold: true,
},
};
const GH_TOKEN = process.env['GH_TOKEN'];
const { GH_TOKEN } = process.env;

if (!GH_TOKEN) {
console.error('Must provide GH_TOKEN as enviroment variable!');
console.error('Must provide GH_TOKEN as environment variable!');
process.exit(1);
}

Expand All @@ -47,18 +49,21 @@ if (!packageJSON.repository || typeof packageJSON.repository.url !== 'string') {
process.exit(1);
}

const match = /https:\/\/github.com\/([^/]+)\/([^/]+).git/.exec(
const repoURLMatch = /https:\/\/github.com\/([^/]+)\/([^/]+).git/.exec(
packageJSON.repository.url,
);
if (match == null) {
console.error('Can not extract organisation and repo name from repo URL!');
if (repoURLMatch == null) {
console.error('Cannot extract organization and repo name from repo URL!');
process.exit(1);
}
const [, githubOrg, githubRepo] = match;
const [, githubOrg, githubRepo] = repoURLMatch;

getChangeLog()
.then((changelog) => process.stdout.write(changelog))
.catch((error) => console.error(error));
.catch((error) => {
console.error(error);
process.exit(1);
});

function getChangeLog() {
const { version } = packageJSON;
Expand All @@ -73,23 +78,36 @@ function getChangeLog() {
}

const date = exec('git log -1 --format=%cd --date=short');
return getCommitsInfo(commitsList.split('\n')).then((commitsInfo) =>
genChangeLog(tag, date, commitsInfo),
);
return getCommitsInfo(commitsList.split('\n'))
.then((commitsInfo) => getPRsInfo(commitsInfoToPRs(commitsInfo)))
.then((prsInfo) => genChangeLog(tag, date, prsInfo));
}

function genChangeLog(tag, date, commitsInfo) {
const allPRs = commitsInfoToPRs(commitsInfo);
function genChangeLog(tag, date, allPRs) {
const byLabel = {};
const commitersByLogin = {};
const committersByLogin = {};

for (const pr of allPRs) {
if (!labelsConfig[pr.label]) {
throw new Error('Unknown label: ' + pr.label + pr.number);
const labels = pr.labels.nodes
.map((label) => label.name)
.filter((label) => label.startsWith('PR: '));

if (labels.length === 0) {
throw new Error(`PR is missing label. See ${pr.url}`);
}
if (labels.length > 1) {
throw new Error(
`PR has conflicting labels: ${labels.join('\n')}\nSee ${pr.url}`,
);
}

const label = labels[0];
if (!labelsConfig[label]) {
throw new Error(`Unknown label: ${label}. See ${pr.url}`);
}
byLabel[pr.label] = byLabel[pr.label] || [];
byLabel[pr.label].push(pr);
commitersByLogin[pr.author.login] = pr.author;
byLabel[label] = byLabel[label] || [];
byLabel[label].push(pr);
committersByLogin[pr.author.login] = pr.author;
}

let changelog = `## ${tag || 'Unreleased'} (${date})\n`;
Expand All @@ -115,12 +133,12 @@ function genChangeLog(tag, date, commitsInfo) {
}
}

const commiters = Object.values(commitersByLogin).sort((a, b) =>
const committers = Object.values(committersByLogin).sort((a, b) =>
(a.name || a.login).localeCompare(b.name || b.login),
);
changelog += `\n#### Committers: ${commiters.length}\n`;
for (const commiter of commiters) {
changelog += `* ${commiter.name}([@${commiter.login}](${commiter.url}))\n`;
changelog += `\n#### Committers: ${committers.length}\n`;
for (const committer of committers) {
changelog += `* ${committer.name}([@${committer.login}](${committer.url}))\n`;
}

return changelog;
Expand Down Expand Up @@ -172,7 +190,7 @@ function graphqlRequestImpl(query, variables, cb) {
});
});

req.on('error', (error) => cb(error));
req.on('error', (error) => resultCB(error));
req.write(JSON.stringify({ query, variables }));
req.end();
}
Expand All @@ -188,23 +206,9 @@ async function batchCommitInfo(commits) {
associatedPullRequests(first: 10) {
nodes {
number
title
url
author {
login
url
... on User {
name
}
}
repository {
nameWithOwner
}
labels(first: 10) {
nodes {
name
}
}
}
}
}
Expand All @@ -227,13 +231,57 @@ async function batchCommitInfo(commits) {
return commitsInfo;
}

async function batchPRInfo(prs) {
let prsSubQuery = '';
for (const number of prs) {
prsSubQuery += `
pr_${number}: pullRequest(number: ${number}) {
number
title
url
author {
login
url
... on User {
name
}
}
labels(first: 10) {
nodes {
name
}
}
}
`;
}

const response = await graphqlRequest(`
{
repository(owner: "${githubOrg}", name: "${githubRepo}") {
${prsSubQuery}
}
}
`);

const prsInfo = [];
for (const number of prs) {
prsInfo.push(response.repository['pr_' + number]);
}
return prsInfo;
}

function commitsInfoToPRs(commits) {
const prs = {};
for (const commit of commits) {
const associatedPRs = commit.associatedPullRequests.nodes.filter(
(pr) => pr.repository.nameWithOwner === `${githubOrg}/${githubRepo}`,
);
if (associatedPRs.length === 0) {
const match = / \(#([0-9]+)\)$/m.exec(commit.message);
if (match) {
prs[parseInt(match[1], 10)] = true;
continue;
}
throw new Error(
`Commit ${commit.oid} has no associated PR: ${commit.message}`,
);
Expand All @@ -244,30 +292,21 @@ function commitsInfoToPRs(commits) {
);
}

const pr = associatedPRs[0];
const labels = pr.labels.nodes
.map((label) => label.name)
.filter((label) => label.startsWith('PR: '));
prs[associatedPRs[0].number] = true;
}

if (labels.length === 0) {
throw new Error(`PR #${pr.number} missing label`);
}
if (labels.length > 1) {
throw new Error(
`PR #${pr.number} has conflicting labels: ` + labels.join('\n'),
);
}
return Object.keys(prs);
}

prs[pr.number] = {
number: pr.number,
title: pr.title,
url: pr.url,
author: pr.author,
label: labels[0],
};
async function getPRsInfo(commits) {
// Split pr into batches of 50 to prevent timeouts
const prInfoPromises = [];
for (let i = 0; i < commits.length; i += 50) {
const batch = commits.slice(i, i + 50);
prInfoPromises.push(batchPRInfo(batch));
}

return Object.values(prs);
return (await Promise.all(prInfoPromises)).flat();
}

async function getCommitsInfo(commits) {
Expand Down

0 comments on commit 579caef

Please sign in to comment.