mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
chore: sync 9-x-y release notes script to master (#25305)
* adds the '(Also in N-x-y)' annotations * handle sublists in release notes (#25279) * has prepare-release.js catch thrown exceptions (#24923) * syncs related tests
This commit is contained in:
@@ -8,9 +8,10 @@ const semver = require('semver');
|
||||
const { ELECTRON_DIR } = require('../../lib/utils');
|
||||
const notesGenerator = require('./notes.js');
|
||||
|
||||
const semverify = version => version.replace(/^origin\//, '').replace('x', '0').replace(/-/g, '.');
|
||||
const semverify = version => version.replace(/^origin\//, '').replace(/[xy]/g, '0').replace(/-/g, '.');
|
||||
|
||||
const runGit = async (args) => {
|
||||
console.info(`Running: git ${args.join(' ')}`);
|
||||
const response = await GitProcess.exec(args, ELECTRON_DIR);
|
||||
if (response.exitCode !== 0) {
|
||||
throw new Error(response.stderr.trim());
|
||||
@@ -19,15 +20,20 @@ const runGit = async (args) => {
|
||||
};
|
||||
|
||||
const tagIsSupported = tag => tag && !tag.includes('nightly') && !tag.includes('unsupported');
|
||||
const tagIsBeta = tag => tag.includes('beta');
|
||||
const tagIsBeta = tag => tag && tag.includes('beta');
|
||||
const tagIsStable = tag => tagIsSupported(tag) && !tagIsBeta(tag);
|
||||
|
||||
const getTagsOf = async (point) => {
|
||||
return (await runGit(['tag', '--merged', point]))
|
||||
.split('\n')
|
||||
.map(tag => tag.trim())
|
||||
.filter(tag => semver.valid(tag))
|
||||
.sort(semver.compare);
|
||||
try {
|
||||
const tags = await runGit(['tag', '--merged', point]);
|
||||
return tags.split('\n')
|
||||
.map(tag => tag.trim())
|
||||
.filter(tag => semver.valid(tag))
|
||||
.sort(semver.compare);
|
||||
} catch (err) {
|
||||
console.error(`Failed to fetch tags for point ${point}`);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const getTagsOnBranch = async (point) => {
|
||||
@@ -41,26 +47,36 @@ const getTagsOnBranch = async (point) => {
|
||||
};
|
||||
|
||||
const getBranchOf = async (point) => {
|
||||
const branches = (await runGit(['branch', '-a', '--contains', point]))
|
||||
.split('\n')
|
||||
.map(branch => branch.trim())
|
||||
.filter(branch => !!branch);
|
||||
const current = branches.find(branch => branch.startsWith('* '));
|
||||
return current ? current.slice(2) : branches.shift();
|
||||
try {
|
||||
const branches = (await runGit(['branch', '-a', '--contains', point]))
|
||||
.split('\n')
|
||||
.map(branch => branch.trim())
|
||||
.filter(branch => !!branch);
|
||||
const current = branches.find(branch => branch.startsWith('* '));
|
||||
return current ? current.slice(2) : branches.shift();
|
||||
} catch (err) {
|
||||
console.error(`Failed to fetch branch for ${point}: `, err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const getAllBranches = async () => {
|
||||
return (await runGit(['branch', '--remote']))
|
||||
.split('\n')
|
||||
.map(branch => branch.trim())
|
||||
.filter(branch => !!branch)
|
||||
.filter(branch => branch !== 'origin/HEAD -> origin/master')
|
||||
.sort();
|
||||
try {
|
||||
const branches = await runGit(['branch', '--remote']);
|
||||
return branches.split('\n')
|
||||
.map(branch => branch.trim())
|
||||
.filter(branch => !!branch)
|
||||
.filter(branch => branch !== 'origin/HEAD -> origin/master')
|
||||
.sort();
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch all branches');
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const getStabilizationBranches = async () => {
|
||||
return (await getAllBranches())
|
||||
.filter(branch => /^origin\/\d+-\d+-x$/.test(branch));
|
||||
.filter(branch => /^origin\/\d+-\d+-x$/.test(branch) || /^origin\/\d+-x-y$/.test(branch));
|
||||
};
|
||||
|
||||
const getPreviousStabilizationBranch = async (current) => {
|
||||
@@ -120,7 +136,7 @@ const getPreviousPoint = async (point) => {
|
||||
}
|
||||
};
|
||||
|
||||
async function getReleaseNotes (range, newVersion, explicitLinks) {
|
||||
async function getReleaseNotes (range, newVersion) {
|
||||
const rangeList = range.split('..') || ['HEAD'];
|
||||
const to = rangeList.pop();
|
||||
const from = rangeList.pop() || (await getPreviousPoint(to));
|
||||
@@ -129,10 +145,9 @@ async function getReleaseNotes (range, newVersion, explicitLinks) {
|
||||
newVersion = to;
|
||||
}
|
||||
|
||||
console.log(`Generating release notes between ${from} and ${to} for version ${newVersion}`);
|
||||
const notes = await notesGenerator.get(from, to, newVersion);
|
||||
const ret = {
|
||||
text: notesGenerator.render(notes, explicitLinks)
|
||||
text: notesGenerator.render(notes)
|
||||
};
|
||||
|
||||
if (notes.unknown.length) {
|
||||
@@ -144,7 +159,7 @@ async function getReleaseNotes (range, newVersion, explicitLinks) {
|
||||
|
||||
async function main () {
|
||||
const opts = minimist(process.argv.slice(2), {
|
||||
boolean: ['explicit-links', 'help'],
|
||||
boolean: ['help'],
|
||||
string: ['version']
|
||||
});
|
||||
opts.range = opts._.shift();
|
||||
@@ -153,14 +168,13 @@ async function main () {
|
||||
console.log(`
|
||||
easy usage: ${name} version
|
||||
|
||||
full usage: ${name} [begin..]end [--version version] [--explicit-links]
|
||||
full usage: ${name} [begin..]end [--version version]
|
||||
|
||||
* 'begin' and 'end' are two git references -- tags, branches, etc --
|
||||
from which the release notes are generated.
|
||||
* if omitted, 'begin' defaults to the previous tag in end's branch.
|
||||
* if omitted, 'version' defaults to 'end'. Specifying a version is
|
||||
useful if you're making notes on a new version that isn't tagged yet.
|
||||
* 'explicit-links' makes every note's issue, commit, or pull an MD link
|
||||
|
||||
For example, these invocations are equivalent:
|
||||
${process.argv[1]} v4.0.1
|
||||
@@ -169,7 +183,7 @@ For example, these invocations are equivalent:
|
||||
return 0;
|
||||
}
|
||||
|
||||
const notes = await getReleaseNotes(opts.range, opts.version, opts['explicit-links']);
|
||||
const notes = await getReleaseNotes(opts.range, opts.version);
|
||||
console.log(notes.text);
|
||||
if (notes.warning) {
|
||||
throw new Error(notes.warning);
|
||||
|
||||
@@ -2,25 +2,23 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const childProcess = require('child_process');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
const { GitProcess } = require('dugite');
|
||||
|
||||
const octokit = require('@octokit/rest')({
|
||||
auth: process.env.ELECTRON_GITHUB_TOKEN
|
||||
});
|
||||
const semver = require('semver');
|
||||
|
||||
const { ELECTRON_VERSION, SRC_DIR } = require('../../lib/utils');
|
||||
const { ELECTRON_DIR } = require('../../lib/utils');
|
||||
|
||||
const MAX_FAIL_COUNT = 3;
|
||||
const CHECK_INTERVAL = 5000;
|
||||
|
||||
const CACHE_DIR = path.resolve(__dirname, '.cache');
|
||||
const TROP_LOGIN = 'trop[bot]';
|
||||
|
||||
const NO_NOTES = 'No notes';
|
||||
const FOLLOW_REPOS = ['electron/electron', 'electron/node'];
|
||||
|
||||
const docTypes = new Set(['doc', 'docs']);
|
||||
const featTypes = new Set(['feat', 'feature']);
|
||||
@@ -28,6 +26,8 @@ const fixTypes = new Set(['fix']);
|
||||
const otherTypes = new Set(['spec', 'build', 'test', 'chore', 'deps', 'refactor', 'tools', 'vendor', 'perf', 'style', 'ci']);
|
||||
const knownTypes = new Set([...docTypes.keys(), ...featTypes.keys(), ...fixTypes.keys(), ...otherTypes.keys()]);
|
||||
|
||||
const getCacheDir = () => process.env.NOTES_CACHE_PATH || path.resolve(__dirname, '.cache');
|
||||
|
||||
/**
|
||||
***
|
||||
**/
|
||||
@@ -39,6 +39,13 @@ class GHKey {
|
||||
this.repo = repo;
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
static NewFromPull (pull) {
|
||||
const owner = pull.base.repo.owner.login;
|
||||
const repo = pull.base.repo.name;
|
||||
const number = pull.number;
|
||||
return new GHKey(owner, repo, number);
|
||||
}
|
||||
}
|
||||
|
||||
class Commit {
|
||||
@@ -47,10 +54,13 @@ class Commit {
|
||||
this.owner = owner; // string
|
||||
this.repo = repo; // string
|
||||
|
||||
this.body = null; // string
|
||||
this.isBreakingChange = false;
|
||||
this.issueNumber = null; // number
|
||||
this.note = null; // string
|
||||
|
||||
// A set of branches to which this change has been merged.
|
||||
// '8-x-y' => GHKey { owner: 'electron', repo: 'electron', number: 23714 }
|
||||
this.trops = new Map(); // Map<string,GHKey>
|
||||
|
||||
this.prKeys = new Set(); // GHKey
|
||||
this.revertHash = null; // string
|
||||
this.semanticType = null; // string
|
||||
@@ -99,65 +109,31 @@ const getNoteFromClerk = async (ghKey) => {
|
||||
return NO_NOTES;
|
||||
}
|
||||
if (comment.body.startsWith(PERSIST_LEAD)) {
|
||||
return comment.body
|
||||
let lines = comment.body
|
||||
.slice(PERSIST_LEAD.length).trim() // remove PERSIST_LEAD
|
||||
.split('\r?\n') // break into lines
|
||||
.split(/\r?\n/) // split into lines
|
||||
.map(line => line.trim())
|
||||
.filter(line => line.startsWith(QUOTE_LEAD)) // notes are quoted
|
||||
.map(line => line.slice(QUOTE_LEAD.length)) // unquote the lines
|
||||
.join(' ') // join the note lines
|
||||
.map(line => line.slice(QUOTE_LEAD.length)); // unquote the lines
|
||||
|
||||
const firstLine = lines.shift();
|
||||
// indent anything after the first line to ensure that
|
||||
// multiline notes with their own sub-lists don't get
|
||||
// parsed in the markdown as part of the top-level list
|
||||
// (example: https://github.com/electron/electron/pull/25216)
|
||||
lines = lines.map(line => ' ' + line);
|
||||
return [firstLine, ...lines]
|
||||
.join('\n') // join the lines
|
||||
.trim();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// copied from https://github.com/electron/clerk/blob/master/src/index.ts#L4-L13
|
||||
const OMIT_FROM_RELEASE_NOTES_KEYS = [
|
||||
'no-notes',
|
||||
'no notes',
|
||||
'no_notes',
|
||||
'none',
|
||||
'no',
|
||||
'nothing',
|
||||
'empty',
|
||||
'blank'
|
||||
];
|
||||
|
||||
const getNoteFromBody = body => {
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const NOTE_PREFIX = 'Notes: ';
|
||||
const NOTE_HEADER = '#### Release Notes';
|
||||
|
||||
let note = body
|
||||
.split(/\r?\n\r?\n/) // split into paragraphs
|
||||
.map(paragraph => paragraph.trim())
|
||||
.map(paragraph => paragraph.startsWith(NOTE_HEADER) ? paragraph.slice(NOTE_HEADER.length).trim() : paragraph)
|
||||
.find(paragraph => paragraph.startsWith(NOTE_PREFIX));
|
||||
|
||||
if (note) {
|
||||
note = note
|
||||
.slice(NOTE_PREFIX.length)
|
||||
.replace(/<!--.*-->/, '') // '<!-- change summary here-->'
|
||||
.replace(/\r?\n/, ' ') // remove newlines
|
||||
.trim();
|
||||
}
|
||||
|
||||
if (note && OMIT_FROM_RELEASE_NOTES_KEYS.includes(note.toLowerCase())) {
|
||||
return NO_NOTES;
|
||||
}
|
||||
|
||||
return note;
|
||||
};
|
||||
|
||||
/**
|
||||
* Looks for our project's conventions in the commit message:
|
||||
*
|
||||
* 'semantic: some description' -- sets semanticType, subject
|
||||
* 'some description (#99999)' -- sets subject, pr
|
||||
* 'Fixes #3333' -- sets issueNumber
|
||||
* 'Merge pull request #99999 from ${branchname}' -- sets pr
|
||||
* 'This reverts commit ${sha}' -- sets revertHash
|
||||
* line starting with 'BREAKING CHANGE' in body -- sets isBreakingChange
|
||||
@@ -175,13 +151,6 @@ const parseCommitMessage = (commitMessage, commit) => {
|
||||
subject = subject.slice(0, pos).trim();
|
||||
}
|
||||
|
||||
if (body) {
|
||||
commit.body = body;
|
||||
|
||||
const note = getNoteFromBody(body);
|
||||
if (note) { commit.note = note; }
|
||||
}
|
||||
|
||||
// if the subject ends in ' (#dddd)', treat it as a pull request id
|
||||
let match;
|
||||
if ((match = subject.match(/^(.*)\s\(#(\d+)\)$/))) {
|
||||
@@ -213,7 +182,6 @@ const parseCommitMessage = (commitMessage, commit) => {
|
||||
|
||||
// https://help.github.com/articles/closing-issues-using-keywords/
|
||||
if ((match = body.match(/\b(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved|for)\s#(\d+)\b/i))) {
|
||||
commit.issueNumber = parseInt(match[1]);
|
||||
commit.semanticType = commit.semanticType || 'fix';
|
||||
}
|
||||
|
||||
@@ -237,32 +205,36 @@ const parseCommitMessage = (commitMessage, commit) => {
|
||||
const parsePullText = (pull, commit) => parseCommitMessage(`${pull.data.title}\n\n${pull.data.body}`, commit);
|
||||
|
||||
const getLocalCommitHashes = async (dir, ref) => {
|
||||
const args = ['log', '-z', '--format=%H', ref];
|
||||
return (await runGit(dir, args)).split('\0').map(hash => hash.trim());
|
||||
const args = ['log', '--format=%H', ref];
|
||||
return (await runGit(dir, args))
|
||||
.split(/\r?\n/) // split into lines
|
||||
.map(hash => hash.trim());
|
||||
};
|
||||
|
||||
// return an array of Commits
|
||||
const getLocalCommits = async (module, point1, point2) => {
|
||||
const { owner, repo, dir } = module;
|
||||
|
||||
const fieldSep = '||';
|
||||
const format = ['%H', '%B'].join(fieldSep);
|
||||
const args = ['log', '-z', '--cherry-pick', '--right-only', '--first-parent', `--format=${format}`, `${point1}..${point2}`];
|
||||
const logs = (await runGit(dir, args)).split('\0').map(field => field.trim());
|
||||
const fieldSep = ',';
|
||||
const format = ['%H', '%s'].join(fieldSep);
|
||||
const args = ['log', '--cherry-pick', '--right-only', '--first-parent', `--format=${format}`, `${point1}..${point2}`];
|
||||
const logs = (await runGit(dir, args))
|
||||
.split(/\r?\n/) // split into lines
|
||||
.map(field => field.trim());
|
||||
|
||||
const commits = [];
|
||||
for (const log of logs) {
|
||||
if (!log) {
|
||||
continue;
|
||||
}
|
||||
const [hash, message] = log.split(fieldSep, 2).map(field => field.trim());
|
||||
commits.push(parseCommitMessage(message, new Commit(hash, owner, repo)));
|
||||
const [hash, subject] = log.split(fieldSep, 2).map(field => field.trim());
|
||||
commits.push(parseCommitMessage(subject, new Commit(hash, owner, repo)));
|
||||
}
|
||||
return commits;
|
||||
};
|
||||
|
||||
const checkCache = async (name, operation) => {
|
||||
const filename = path.resolve(CACHE_DIR, name);
|
||||
const filename = path.resolve(getCacheDir(), name);
|
||||
if (fs.existsSync(filename)) {
|
||||
return JSON.parse(fs.readFileSync(filename, 'utf8'));
|
||||
}
|
||||
@@ -286,12 +258,42 @@ async function runRetryable (fn, maxRetries) {
|
||||
}
|
||||
}
|
||||
// Silently eat 404s.
|
||||
if (lastError.status !== 404) throw lastError;
|
||||
// Silently eat 422s, which come from "No commit found for SHA"
|
||||
if (lastError.status !== 404 && lastError.status !== 422) throw lastError;
|
||||
}
|
||||
|
||||
const getPullCacheFilename = ghKey => `${ghKey.owner}-${ghKey.repo}-pull-${ghKey.number}`;
|
||||
|
||||
const getCommitPulls = async (owner, repo, hash) => {
|
||||
const name = `${owner}-${repo}-commit-${hash}`;
|
||||
const retryableFunc = () => octokit.repos.listPullRequestsAssociatedWithCommit({ owner, repo, commit_sha: hash });
|
||||
let ret = await checkCache(name, () => runRetryable(retryableFunc, MAX_FAIL_COUNT));
|
||||
|
||||
// only merged pulls belong in release notes
|
||||
if (ret && ret.data) {
|
||||
ret.data = ret.data.filter(pull => pull.merged_at);
|
||||
}
|
||||
|
||||
// cache the pulls
|
||||
if (ret && ret.data) {
|
||||
for (const pull of ret.data) {
|
||||
const cachefile = getPullCacheFilename(GHKey.NewFromPull(pull));
|
||||
const payload = { ...ret, data: pull };
|
||||
await checkCache(cachefile, () => payload);
|
||||
}
|
||||
}
|
||||
|
||||
// ensure the return value has the expected structure, even on failure
|
||||
if (!ret || !ret.data) {
|
||||
ret = { data: [] };
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
const getPullRequest = async (ghKey) => {
|
||||
const { number, owner, repo } = ghKey;
|
||||
const name = `${owner}-${repo}-pull-${number}`;
|
||||
const name = getPullCacheFilename(ghKey);
|
||||
const retryableFunc = () => octokit.pulls.get({ pull_number: number, owner, repo });
|
||||
return checkCache(name, () => runRetryable(retryableFunc, MAX_FAIL_COUNT));
|
||||
};
|
||||
@@ -306,10 +308,20 @@ const getComments = async (ghKey) => {
|
||||
const addRepoToPool = async (pool, repo, from, to) => {
|
||||
const commonAncestor = await getCommonAncestor(repo.dir, from, to);
|
||||
|
||||
// add the commits
|
||||
const oldHashes = await getLocalCommitHashes(repo.dir, from);
|
||||
oldHashes.forEach(hash => { pool.processedHashes.add(hash); });
|
||||
// mark the old branch's commits as old news
|
||||
for (const oldHash of await getLocalCommitHashes(repo.dir, from)) {
|
||||
pool.processedHashes.add(oldHash);
|
||||
}
|
||||
|
||||
// get the new branch's commits and the pulls associated with them
|
||||
const commits = await getLocalCommits(repo, commonAncestor, to);
|
||||
for (const commit of commits) {
|
||||
const { owner, repo, hash } = commit;
|
||||
for (const pull of (await getCommitPulls(owner, repo, hash)).data) {
|
||||
commit.prKeys.add(GHKey.NewFromPull(pull));
|
||||
}
|
||||
}
|
||||
|
||||
pool.commits.push(...commits);
|
||||
|
||||
// add the pulls
|
||||
@@ -317,95 +329,59 @@ const addRepoToPool = async (pool, repo, from, to) => {
|
||||
let prKey;
|
||||
for (prKey of commit.prKeys.values()) {
|
||||
const pull = await getPullRequest(prKey);
|
||||
if (!pull || !pull.data) break; // couldn't get it
|
||||
if (pool.pulls[prKey.number]) break; // already have it
|
||||
if (!pull || !pull.data) continue; // couldn't get it
|
||||
pool.pulls[prKey.number] = pull;
|
||||
parsePullText(pull, commit);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/***
|
||||
**** Other Repos
|
||||
***/
|
||||
// @return Map<string,GHKey>
|
||||
// where the key is a branch name (e.g. '7-1-x' or '8-x-y')
|
||||
// and the value is a GHKey to the PR
|
||||
async function getMergedTrops (commit, pool) {
|
||||
const branches = new Map();
|
||||
|
||||
// other repos - gn
|
||||
for (const prKey of commit.prKeys.values()) {
|
||||
const pull = pool.pulls[prKey.number];
|
||||
const mergedBranches = new Set(
|
||||
((pull && pull.data && pull.data.labels) ? pull.data.labels : [])
|
||||
.map(label => ((label && label.name) ? label.name : '').match(/merged\/([0-9]+-[x0-9]-[xy0-9])/))
|
||||
.filter(match => match)
|
||||
.map(match => match[1])
|
||||
);
|
||||
|
||||
const getDepsVariable = async (ref, key) => {
|
||||
// get a copy of that reference point's DEPS file
|
||||
const deps = await runGit(ELECTRON_VERSION, ['show', `${ref}:DEPS`]);
|
||||
const filename = path.resolve(os.tmpdir(), 'DEPS');
|
||||
fs.writeFileSync(filename, deps);
|
||||
if (mergedBranches.size > 0) {
|
||||
const isTropComment = (comment) => comment && comment.user && comment.user.login === TROP_LOGIN;
|
||||
|
||||
// query the DEPS file
|
||||
const response = childProcess.spawnSync(
|
||||
'gclient',
|
||||
['getdep', '--deps-file', filename, '--var', key],
|
||||
{ encoding: 'utf8' }
|
||||
);
|
||||
const ghKey = GHKey.NewFromPull(pull.data);
|
||||
const backportRegex = /backported this PR to "(.*)",\s+please check out #(\d+)/;
|
||||
const getBranchNameAndPullKey = (comment) => {
|
||||
const match = ((comment && comment.body) ? comment.body : '').match(backportRegex);
|
||||
return match ? [match[1], new GHKey(ghKey.owner, ghKey.repo, parseInt(match[2]))] : null;
|
||||
};
|
||||
|
||||
// cleanup
|
||||
fs.unlinkSync(filename);
|
||||
return response.stdout.trim();
|
||||
};
|
||||
|
||||
const getDependencyCommitsGN = async (pool, fromRef, toRef) => {
|
||||
const repos = [{ // just node
|
||||
owner: 'electron',
|
||||
repo: 'node',
|
||||
dir: path.resolve(SRC_DIR, 'third_party', 'electron_node'),
|
||||
deps_variable_name: 'node_version'
|
||||
}];
|
||||
|
||||
for (const repo of repos) {
|
||||
// the 'DEPS' file holds the dependency reference point
|
||||
const key = repo.deps_variable_name;
|
||||
const from = await getDepsVariable(fromRef, key);
|
||||
const to = await getDepsVariable(toRef, key);
|
||||
await addRepoToPool(pool, repo, from, to);
|
||||
}
|
||||
};
|
||||
|
||||
// Changes are interesting if they make a change relative to a previous
|
||||
// release in the same series. For example if you fix a Y.0.0 bug, that
|
||||
// should be included in the Y.0.1 notes even if it's also tropped back
|
||||
// to X.0.1.
|
||||
//
|
||||
// The phrase 'previous release' is important: if this is the first
|
||||
// prerelease or first stable release in a series, we omit previous
|
||||
// branches' changes. Otherwise we will have an overwhelmingly long
|
||||
// list of mostly-irrelevant changes.
|
||||
const shouldIncludeMultibranchChanges = (version) => {
|
||||
let show = true;
|
||||
|
||||
if (semver.valid(version)) {
|
||||
const prerelease = semver.prerelease(version);
|
||||
show = prerelease
|
||||
? parseInt(prerelease.pop()) > 1
|
||||
: semver.patch(version) > 0;
|
||||
const comments = await getComments(ghKey);
|
||||
((comments && comments.data) ? comments.data : [])
|
||||
.filter(isTropComment)
|
||||
.map(getBranchNameAndPullKey)
|
||||
.filter(pair => pair)
|
||||
.filter(([branch]) => mergedBranches.has(branch))
|
||||
.forEach(([branch, key]) => branches.set(branch, key));
|
||||
}
|
||||
}
|
||||
|
||||
return show;
|
||||
};
|
||||
|
||||
function getOldestMajorBranchOfPull (pull) {
|
||||
return pull.data.labels
|
||||
.map(label => label.name.match(/merged\/(\d+)-(\d+)-x/) || label.name.match(/merged\/(\d+)-x-y/))
|
||||
.filter(label => !!label)
|
||||
.map(label => parseInt(label[1]))
|
||||
.filter(major => !!major)
|
||||
.sort()
|
||||
.shift();
|
||||
return branches;
|
||||
}
|
||||
|
||||
function getOldestMajorBranchOfCommit (commit, pool) {
|
||||
return [ ...commit.prKeys.values() ]
|
||||
.map(prKey => pool.pulls[prKey.number])
|
||||
.filter(pull => !!pull)
|
||||
.map(pull => getOldestMajorBranchOfPull(pull))
|
||||
.filter(major => !!major)
|
||||
.sort()
|
||||
.shift();
|
||||
// @return the shorthand name of the branch that `ref` is on,
|
||||
// e.g. a ref of '10.0.0-beta.1' will return '10-x-y'
|
||||
async function getBranchNameOfRef (ref, dir) {
|
||||
return (await runGit(dir, ['branch', '--all', '--contains', ref, '--sort', 'version:refname']))
|
||||
.split(/\r?\n/) // split into lines
|
||||
.shift() // we sorted by refname and want the first result
|
||||
.match(/(?:.*\/)?(.*)/)[1] // 'remote/origins/10-x-y' -> '10-x-y'
|
||||
.trim();
|
||||
}
|
||||
|
||||
/***
|
||||
@@ -413,26 +389,20 @@ function getOldestMajorBranchOfCommit (commit, pool) {
|
||||
***/
|
||||
|
||||
const getNotes = async (fromRef, toRef, newVersion) => {
|
||||
if (!fs.existsSync(CACHE_DIR)) {
|
||||
fs.mkdirSync(CACHE_DIR);
|
||||
const cacheDir = getCacheDir();
|
||||
if (!fs.existsSync(cacheDir)) {
|
||||
fs.mkdirSync(cacheDir);
|
||||
}
|
||||
|
||||
const pool = new Pool();
|
||||
const toBranch = await getBranchNameOfRef(toRef, ELECTRON_DIR);
|
||||
|
||||
console.log(`Generating release notes between ${fromRef} and ${toRef} for version ${newVersion} in branch ${toBranch}`);
|
||||
|
||||
// get the electron/electron commits
|
||||
const electron = { owner: 'electron', repo: 'electron', dir: ELECTRON_VERSION };
|
||||
const electron = { owner: 'electron', repo: 'electron', dir: ELECTRON_DIR };
|
||||
await addRepoToPool(pool, electron, fromRef, toRef);
|
||||
|
||||
// Don't include submodules if comparing across major versions;
|
||||
// there's just too much churn otherwise.
|
||||
const includeDeps = semver.valid(fromRef) &&
|
||||
semver.valid(toRef) &&
|
||||
semver.major(fromRef) === semver.major(toRef);
|
||||
|
||||
if (includeDeps) {
|
||||
await getDependencyCommitsGN(pool, fromRef, toRef);
|
||||
}
|
||||
|
||||
// remove any old commits
|
||||
pool.commits = pool.commits.filter(commit => !pool.processedHashes.has(commit.hash));
|
||||
|
||||
@@ -457,27 +427,24 @@ const getNotes = async (fromRef, toRef, newVersion) => {
|
||||
// ensure the commit has a note
|
||||
for (const commit of pool.commits) {
|
||||
for (const prKey of commit.prKeys.values()) {
|
||||
commit.note = commit.note || await getNoteFromClerk(prKey);
|
||||
if (commit.note) {
|
||||
break;
|
||||
}
|
||||
commit.note = await getNoteFromClerk(prKey);
|
||||
}
|
||||
// use a fallback note in case someone missed a 'Notes' comment
|
||||
commit.note = commit.note || commit.subject;
|
||||
}
|
||||
|
||||
// remove non-user-facing commits
|
||||
pool.commits = pool.commits
|
||||
.filter(commit => commit && commit.note)
|
||||
.filter(commit => commit.note !== NO_NOTES)
|
||||
.filter(commit => !((commit.note || commit.subject).match(/^[Bb]ump v\d+\.\d+\.\d+/)));
|
||||
.filter(commit => commit.note.match(/^[Bb]ump v\d+\.\d+\.\d+/) === null);
|
||||
|
||||
if (!shouldIncludeMultibranchChanges(newVersion)) {
|
||||
const currentMajor = semver.parse(newVersion).major;
|
||||
pool.commits = pool.commits
|
||||
.filter(commit => getOldestMajorBranchOfCommit(commit, pool) >= currentMajor);
|
||||
for (const commit of pool.commits) {
|
||||
commit.trops = await getMergedTrops(commit, pool);
|
||||
}
|
||||
|
||||
pool.commits = removeSupercededChromiumUpdates(pool.commits);
|
||||
pool.commits = removeSupercededStackUpdates(pool.commits);
|
||||
|
||||
const notes = {
|
||||
breaking: [],
|
||||
@@ -486,7 +453,8 @@ const getNotes = async (fromRef, toRef, newVersion) => {
|
||||
fix: [],
|
||||
other: [],
|
||||
unknown: [],
|
||||
name: newVersion
|
||||
name: newVersion,
|
||||
toBranch
|
||||
};
|
||||
|
||||
pool.commits.forEach(commit => {
|
||||
@@ -511,50 +479,76 @@ const getNotes = async (fromRef, toRef, newVersion) => {
|
||||
return notes;
|
||||
};
|
||||
|
||||
const removeSupercededChromiumUpdates = (commits) => {
|
||||
const chromiumRegex = /^Updated Chromium to \d+\.\d+\.\d+\.\d+/;
|
||||
const updates = commits.filter(commit => (commit.note || commit.subject).match(chromiumRegex));
|
||||
const keepers = commits.filter(commit => !updates.includes(commit));
|
||||
const removeSupercededStackUpdates = (commits) => {
|
||||
const updateRegex = /^Updated ([a-zA-Z.]+) to v?([\d.]+)/;
|
||||
const notupdates = [];
|
||||
|
||||
// keep the newest update.
|
||||
if (updates.length) {
|
||||
const compare = (a, b) => (a.note || a.subject).localeCompare(b.note || b.subject);
|
||||
keepers.push(updates.sort(compare).pop());
|
||||
const newest = {};
|
||||
for (const commit of commits) {
|
||||
const match = (commit.note || commit.subject).match(updateRegex);
|
||||
if (!match) {
|
||||
notupdates.push(commit);
|
||||
continue;
|
||||
}
|
||||
const [, dep, version] = match;
|
||||
if (!newest[dep] || newest[dep].version < version) {
|
||||
newest[dep] = { commit, version };
|
||||
}
|
||||
}
|
||||
|
||||
return keepers;
|
||||
return [...notupdates, ...Object.values(newest).map(o => o.commit)];
|
||||
};
|
||||
|
||||
/***
|
||||
**** Render
|
||||
***/
|
||||
|
||||
const renderLink = (commit, explicitLinks) => {
|
||||
let link;
|
||||
const { owner, repo } = commit;
|
||||
const keyIt = commit.prKeys.values().next();
|
||||
if (keyIt.done) /* no PRs */ {
|
||||
const { hash } = commit;
|
||||
const url = `https://github.com/${owner}/${repo}/commit/${hash}`;
|
||||
const text = owner === 'electron' && repo === 'electron'
|
||||
? `${hash.slice(0, 8)}`
|
||||
: `${owner}/${repo}@${hash.slice(0, 8)}`;
|
||||
link = explicitLinks ? `[${text}](${url})` : text;
|
||||
} else {
|
||||
const { number } = keyIt.value;
|
||||
const url = `https://github.com/${owner}/${repo}/pull/${number}`;
|
||||
const text = owner === 'electron' && repo === 'electron'
|
||||
? `#${number}`
|
||||
: `${owner}/${repo}#${number}`;
|
||||
link = explicitLinks ? `[${text}](${url})` : text;
|
||||
}
|
||||
return link;
|
||||
};
|
||||
// @return the pull request's GitHub URL
|
||||
const buildPullURL = ghKey => `https://github.com/${ghKey.owner}/${ghKey.repo}/pull/${ghKey.number}`;
|
||||
|
||||
const renderCommit = (commit, explicitLinks) => {
|
||||
// clean up the note
|
||||
let note = commit.note || commit.subject;
|
||||
const renderPull = ghKey => `[#${ghKey.number}](${buildPullURL(ghKey)})`;
|
||||
|
||||
// @return the commit's GitHub URL
|
||||
const buildCommitURL = commit => `https://github.com/${commit.owner}/${commit.repo}/commit/${commit.hash}`;
|
||||
|
||||
const renderCommit = commit => `[${commit.hash.slice(0, 8)}](${buildCommitURL(commit)})`;
|
||||
|
||||
// @return a markdown link to the PR if available; otherwise, the git commit
|
||||
function renderLink (commit) {
|
||||
const maybePull = commit.prKeys.values().next();
|
||||
return maybePull.value ? renderPull(maybePull.value) : renderCommit(commit);
|
||||
}
|
||||
|
||||
// @return a terser branch name,
|
||||
// e.g. '7-2-x' -> '7.2' and '8-x-y' -> '8'
|
||||
const renderBranchName = name => name.replace(/-[a-zA-Z]/g, '').replace('-', '.');
|
||||
|
||||
const renderTrop = (branch, ghKey) => `[${renderBranchName(branch)}](${buildPullURL(ghKey)})`;
|
||||
|
||||
// @return markdown-formatted links to other branches' trops,
|
||||
// e.g. "(Also in 7.2, 8, 9)"
|
||||
function renderTrops (commit, excludeBranch) {
|
||||
const body = [...commit.trops.entries()]
|
||||
.filter(([branch]) => branch !== excludeBranch)
|
||||
.sort(([branchA], [branchB]) => parseInt(branchA) - parseInt(branchB)) // sort by semver major
|
||||
.map(([branch, key]) => renderTrop(branch, key))
|
||||
.join(', ');
|
||||
return body ? `<span style="font-size:small;">(Also in ${body})</span>` : body;
|
||||
}
|
||||
|
||||
// @return a slightly cleaned-up human-readable change description
|
||||
function renderDescription (commit) {
|
||||
let note = commit.note || commit.subject || '';
|
||||
note = note.trim();
|
||||
|
||||
// release notes bullet point every change, so if the note author
|
||||
// manually started the content with a bullet point, that will confuse
|
||||
// the markdown renderer -- remove the redundant bullet point
|
||||
// (example: https://github.com/electron/electron/pull/25216)
|
||||
if (note.startsWith('*')) {
|
||||
note = note.slice(1).trim();
|
||||
}
|
||||
|
||||
if (note.length !== 0) {
|
||||
note = note[0].toUpperCase() + note.substr(1);
|
||||
|
||||
@@ -590,30 +584,24 @@ const renderCommit = (commit, explicitLinks) => {
|
||||
}
|
||||
}
|
||||
|
||||
const link = renderLink(commit, explicitLinks);
|
||||
return note;
|
||||
}
|
||||
|
||||
return { note, link };
|
||||
};
|
||||
// @return markdown-formatted release note line item,
|
||||
// e.g. '* Fixed a foo. #12345 (Also in 7.2, 8, 9)'
|
||||
const renderNote = (commit, excludeBranch) =>
|
||||
`* ${renderDescription(commit)} ${renderLink(commit)} ${renderTrops(commit, excludeBranch)}\n`;
|
||||
|
||||
const renderNotes = (notes, explicitLinks) => {
|
||||
const renderNotes = (notes) => {
|
||||
const rendered = [`# Release Notes for ${notes.name}\n\n`];
|
||||
|
||||
const renderSection = (title, commits) => {
|
||||
if (commits.length === 0) {
|
||||
return;
|
||||
if (commits.length > 0) {
|
||||
rendered.push(
|
||||
`## ${title}\n\n`,
|
||||
...(commits.map(commit => renderNote(commit, notes.toBranch)).sort())
|
||||
);
|
||||
}
|
||||
const notes = new Map();
|
||||
for (const note of commits.map(commit => renderCommit(commit, explicitLinks))) {
|
||||
if (!notes.has(note.note)) {
|
||||
notes.set(note.note, [note.link]);
|
||||
} else {
|
||||
notes.get(note.note).push(note.link);
|
||||
}
|
||||
}
|
||||
rendered.push(`## ${title}\n\n`);
|
||||
const lines = [];
|
||||
notes.forEach((links, key) => lines.push(` * ${key} ${links.map(link => link.toString()).sort().join(', ')}\n`));
|
||||
rendered.push(...lines.sort(), '\n');
|
||||
};
|
||||
|
||||
renderSection('Breaking Changes', notes.breaking);
|
||||
@@ -622,7 +610,7 @@ const renderNotes = (notes, explicitLinks) => {
|
||||
renderSection('Other Changes', notes.other);
|
||||
|
||||
if (notes.docs.length) {
|
||||
const docs = notes.docs.map(commit => renderLink(commit, explicitLinks)).sort();
|
||||
const docs = notes.docs.map(commit => renderLink(commit)).sort();
|
||||
rendered.push('## Documentation\n\n', ` * Documentation changes: ${docs.join(', ')}\n`, '\n');
|
||||
}
|
||||
|
||||
|
||||
@@ -211,4 +211,8 @@ async function prepareRelease (isBeta, notesOnly) {
|
||||
}
|
||||
}
|
||||
|
||||
prepareRelease(!args.stable, args.notesOnly);
|
||||
prepareRelease(!args.stable, args.notesOnly)
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-20214-comments
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-20214-comments
vendored
Normal file
File diff suppressed because one or more lines are too long
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-21497-comments
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-21497-comments
vendored
Normal file
File diff suppressed because one or more lines are too long
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-21891-comments
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-21891-comments
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"status":200,"url":"https://api.github.com/repos/electron/electron/issues/21891/comments?per_page=100","headers":{"access-control-allow-origin":"*","access-control-expose-headers":"ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset","cache-control":"private, max-age=60, s-maxage=60","connection":"close","content-encoding":"gzip","content-security-policy":"default-src 'none'","content-type":"application/json; charset=utf-8","date":"Tue, 26 May 2020 04:07:22 GMT","etag":"W/\"f3361c5c493edbcc6774be228131a636\"","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","server":"GitHub.com","status":"200 OK","strict-transport-security":"max-age=31536000; includeSubdomains; preload","transfer-encoding":"chunked","vary":"Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding, Accept, X-Requested-With","x-accepted-oauth-scopes":"","x-content-type-options":"nosniff","x-frame-options":"deny","x-github-media-type":"github.v3; format=json","x-github-request-id":"8F4E:231E:52E8BF:8AF8CD:5ECC95F9","x-oauth-scopes":"repo","x-ratelimit-limit":"5000","x-ratelimit-remaining":"4997","x-ratelimit-reset":"1590469446","x-xss-protection":"1; mode=block"},"data":[{"url":"https://api.github.com/repos/electron/electron/issues/comments/579570143","html_url":"https://github.com/electron/electron/pull/21891#issuecomment-579570143","issue_url":"https://api.github.com/repos/electron/electron/issues/21891","id":579570143,"node_id":"MDEyOklzc3VlQ29tbWVudDU3OTU3MDE0Mw==","user":{"login":"bitdisaster","id":5191943,"node_id":"MDQ6VXNlcjUxOTE5NDM=","avatar_url":"https://avatars3.githubusercontent.com/u/5191943?v=4","gravatar_id":"","url":"https://api.github.com/users/bitdisaster","html_url":"https://github.com/bitdisaster","followers_url":"https://api.github.com/users/bitdisaster/followers","following_url":"https://api.github.com/users/bitdisaster/following{/other_user}","gists_url":"https://api.github.com/users/bitdisaster/gists{/gist_id}","starred_url":"https://api.github.com/users/bitdisaster/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bitdisaster/subscriptions","organizations_url":"https://api.github.com/users/bitdisaster/orgs","repos_url":"https://api.github.com/users/bitdisaster/repos","events_url":"https://api.github.com/users/bitdisaster/events{/privacy}","received_events_url":"https://api.github.com/users/bitdisaster/received_events","type":"User","site_admin":false},"created_at":"2020-01-29T02:58:25Z","updated_at":"2020-01-29T02:58:25Z","author_association":"MEMBER","body":"@zcbenz I solved the mac/linux problem a bit differently. @MarshallOfSound recommended the use of converter to me and I like the approach. Does the typedef via conditional compiling work for you to?"},{"url":"https://api.github.com/repos/electron/electron/issues/comments/580589854","html_url":"https://github.com/electron/electron/pull/21891#issuecomment-580589854","issue_url":"https://api.github.com/repos/electron/electron/issues/21891","id":580589854,"node_id":"MDEyOklzc3VlQ29tbWVudDU4MDU4OTg1NA==","user":{"login":"release-clerk[bot]","id":42386326,"node_id":"MDM6Qm90NDIzODYzMjY=","avatar_url":"https://avatars0.githubusercontent.com/in/16104?v=4","gravatar_id":"","url":"https://api.github.com/users/release-clerk%5Bbot%5D","html_url":"https://github.com/apps/release-clerk","followers_url":"https://api.github.com/users/release-clerk%5Bbot%5D/followers","following_url":"https://api.github.com/users/release-clerk%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/release-clerk%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/release-clerk%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/release-clerk%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/release-clerk%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/release-clerk%5Bbot%5D/repos","events_url":"https://api.github.com/users/release-clerk%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/release-clerk%5Bbot%5D/received_events","type":"Bot","site_admin":false},"created_at":"2020-01-31T05:37:07Z","updated_at":"2020-01-31T05:37:07Z","author_association":"NONE","body":"**Release Notes Persisted**\n\n> Added GUID parameter to Tray API to avoid system tray icon demotion on Windows "}]}
|
||||
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-21946-comments
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-21946-comments
vendored
Normal file
File diff suppressed because one or more lines are too long
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-22750-comments
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-22750-comments
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"status":200,"url":"https://api.github.com/repos/electron/electron/issues/22750/comments?per_page=100","headers":{"access-control-allow-origin":"*","access-control-expose-headers":"ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset","cache-control":"private, max-age=60, s-maxage=60","connection":"close","content-encoding":"gzip","content-security-policy":"default-src 'none'","content-type":"application/json; charset=utf-8","date":"Tue, 26 May 2020 17:01:55 GMT","etag":"W/\"89f9bf1ce7fb984e50e64f9d80c482cb\"","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","server":"GitHub.com","status":"200 OK","strict-transport-security":"max-age=31536000; includeSubdomains; preload","transfer-encoding":"chunked","vary":"Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding, Accept, X-Requested-With","x-accepted-oauth-scopes":"","x-content-type-options":"nosniff","x-frame-options":"deny","x-github-media-type":"github.v3; format=json","x-github-request-id":"B828:443A:D1E7FE:149D73A:5ECD4B7D","x-oauth-scopes":"repo","x-ratelimit-limit":"5000","x-ratelimit-remaining":"4990","x-ratelimit-reset":"1590514321","x-xss-protection":"1; mode=block"},"data":[{"url":"https://api.github.com/repos/electron/electron/issues/comments/602930802","html_url":"https://github.com/electron/electron/pull/22750#issuecomment-602930802","issue_url":"https://api.github.com/repos/electron/electron/issues/22750","id":602930802,"node_id":"MDEyOklzc3VlQ29tbWVudDYwMjkzMDgwMg==","user":{"login":"loc","id":1815863,"node_id":"MDQ6VXNlcjE4MTU4NjM=","avatar_url":"https://avatars2.githubusercontent.com/u/1815863?v=4","gravatar_id":"","url":"https://api.github.com/users/loc","html_url":"https://github.com/loc","followers_url":"https://api.github.com/users/loc/followers","following_url":"https://api.github.com/users/loc/following{/other_user}","gists_url":"https://api.github.com/users/loc/gists{/gist_id}","starred_url":"https://api.github.com/users/loc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/loc/subscriptions","organizations_url":"https://api.github.com/users/loc/orgs","repos_url":"https://api.github.com/users/loc/repos","events_url":"https://api.github.com/users/loc/events{/privacy}","received_events_url":"https://api.github.com/users/loc/received_events","type":"User","site_admin":false},"created_at":"2020-03-24T00:23:09Z","updated_at":"2020-03-24T00:23:09Z","author_association":"MEMBER","body":"@zcbenz okay, I believe this is good to go."},{"url":"https://api.github.com/repos/electron/electron/issues/comments/603592578","html_url":"https://github.com/electron/electron/pull/22750#issuecomment-603592578","issue_url":"https://api.github.com/repos/electron/electron/issues/22750","id":603592578,"node_id":"MDEyOklzc3VlQ29tbWVudDYwMzU5MjU3OA==","user":{"login":"release-clerk[bot]","id":42386326,"node_id":"MDM6Qm90NDIzODYzMjY=","avatar_url":"https://avatars0.githubusercontent.com/in/16104?v=4","gravatar_id":"","url":"https://api.github.com/users/release-clerk%5Bbot%5D","html_url":"https://github.com/apps/release-clerk","followers_url":"https://api.github.com/users/release-clerk%5Bbot%5D/followers","following_url":"https://api.github.com/users/release-clerk%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/release-clerk%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/release-clerk%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/release-clerk%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/release-clerk%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/release-clerk%5Bbot%5D/repos","events_url":"https://api.github.com/users/release-clerk%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/release-clerk%5Bbot%5D/received_events","type":"Bot","site_admin":false},"created_at":"2020-03-25T01:40:16Z","updated_at":"2020-03-25T01:40:16Z","author_association":"NONE","body":"**Release Notes Persisted**\n\n> Added workaround for nativeWindowOpen hang."}]}
|
||||
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-22770-comments
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-22770-comments
vendored
Normal file
File diff suppressed because one or more lines are too long
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-22828-comments
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-22828-comments
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"status":200,"url":"https://api.github.com/repos/electron/electron/issues/22828/comments?per_page=100","headers":{"access-control-allow-origin":"*","access-control-expose-headers":"ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset","cache-control":"private, max-age=60, s-maxage=60","connection":"close","content-encoding":"gzip","content-security-policy":"default-src 'none'","content-type":"application/json; charset=utf-8","date":"Tue, 26 May 2020 16:42:42 GMT","etag":"W/\"9da63627de4ed4f8b7929f66f9b6aa2b\"","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","server":"GitHub.com","status":"200 OK","strict-transport-security":"max-age=31536000; includeSubdomains; preload","transfer-encoding":"chunked","vary":"Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding, Accept, X-Requested-With","x-accepted-oauth-scopes":"","x-content-type-options":"nosniff","x-frame-options":"deny","x-github-media-type":"github.v3; format=json","x-github-request-id":"C9A8:3A3F:B1FB:13DF6:5ECD46FB","x-oauth-scopes":"repo","x-ratelimit-limit":"5000","x-ratelimit-remaining":"4994","x-ratelimit-reset":"1590514322","x-xss-protection":"1; mode=block"},"data":[{"url":"https://api.github.com/repos/electron/electron/issues/comments/603916187","html_url":"https://github.com/electron/electron/pull/22828#issuecomment-603916187","issue_url":"https://api.github.com/repos/electron/electron/issues/22828","id":603916187,"node_id":"MDEyOklzc3VlQ29tbWVudDYwMzkxNjE4Nw==","user":{"login":"release-clerk[bot]","id":42386326,"node_id":"MDM6Qm90NDIzODYzMjY=","avatar_url":"https://avatars0.githubusercontent.com/in/16104?v=4","gravatar_id":"","url":"https://api.github.com/users/release-clerk%5Bbot%5D","html_url":"https://github.com/apps/release-clerk","followers_url":"https://api.github.com/users/release-clerk%5Bbot%5D/followers","following_url":"https://api.github.com/users/release-clerk%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/release-clerk%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/release-clerk%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/release-clerk%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/release-clerk%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/release-clerk%5Bbot%5D/repos","events_url":"https://api.github.com/users/release-clerk%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/release-clerk%5Bbot%5D/received_events","type":"Bot","site_admin":false},"created_at":"2020-03-25T15:45:36Z","updated_at":"2020-03-25T15:45:36Z","author_association":"NONE","body":"**Release Notes Persisted**\n\n> don't allow window to go behind menu bar on mac"}]}
|
||||
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-25052-comments
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-25052-comments
vendored
Normal file
File diff suppressed because one or more lines are too long
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-25216-comments
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-issue-25216-comments
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"status":200,"url":"https://api.github.com/repos/electron/electron/issues/25216/comments?per_page=100","headers":{"access-control-allow-origin":"*","access-control-expose-headers":"ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset","cache-control":"private, max-age=60, s-maxage=60","connection":"close","content-encoding":"gzip","content-security-policy":"default-src 'none'","content-type":"application/json; charset=utf-8","date":"Wed, 02 Sep 2020 15:55:20 GMT","etag":"W/\"dc98adeb828ec1f60e0be31a73a31f30\"","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","server":"GitHub.com","status":"200 OK","strict-transport-security":"max-age=31536000; includeSubdomains; preload","transfer-encoding":"chunked","vary":"Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding, Accept, X-Requested-With","x-accepted-oauth-scopes":"","x-content-type-options":"nosniff","x-frame-options":"deny","x-github-media-type":"github.v3; format=json","x-github-request-id":"E876:6741:2819ABF:5CAD2C6:5F4FC068","x-oauth-scopes":"repo","x-ratelimit-limit":"5000","x-ratelimit-remaining":"4943","x-ratelimit-reset":"1599063118","x-xss-protection":"1; mode=block"},"data":[{"url":"https://api.github.com/repos/electron/electron/issues/comments/684017257","html_url":"https://github.com/electron/electron/pull/25216#issuecomment-684017257","issue_url":"https://api.github.com/repos/electron/electron/issues/25216","id":684017257,"node_id":"MDEyOklzc3VlQ29tbWVudDY4NDAxNzI1Nw==","user":{"login":"release-clerk[bot]","id":42386326,"node_id":"MDM6Qm90NDIzODYzMjY=","avatar_url":"https://avatars0.githubusercontent.com/in/16104?v=4","gravatar_id":"","url":"https://api.github.com/users/release-clerk%5Bbot%5D","html_url":"https://github.com/apps/release-clerk","followers_url":"https://api.github.com/users/release-clerk%5Bbot%5D/followers","following_url":"https://api.github.com/users/release-clerk%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/release-clerk%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/release-clerk%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/release-clerk%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/release-clerk%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/release-clerk%5Bbot%5D/repos","events_url":"https://api.github.com/users/release-clerk%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/release-clerk%5Bbot%5D/received_events","type":"Bot","site_admin":false},"created_at":"2020-08-31T20:22:50Z","updated_at":"2020-08-31T20:22:50Z","author_association":"NONE","body":"**Release Notes Persisted**\n\n> * Fixes the following issues for frameless when maximized on Windows:\r\n> * fix unreachable task bar when auto hidden with position top\r\n> * fix 1px extending to secondary monitor\r\n> * fix 1px overflowing into taskbar at certain resolutions\r\n> * fix white line on top of window under 4k resolutions","performed_via_github_app":null}]}
|
||||
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-20214
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-20214
vendored
Normal file
File diff suppressed because one or more lines are too long
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-20620
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-20620
vendored
Normal file
File diff suppressed because one or more lines are too long
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-21497
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-21497
vendored
Normal file
File diff suppressed because one or more lines are too long
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-21591
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-21591
vendored
Normal file
File diff suppressed because one or more lines are too long
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-21891
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-21891
vendored
Normal file
File diff suppressed because one or more lines are too long
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-21946
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-21946
vendored
Normal file
File diff suppressed because one or more lines are too long
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-22750
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-22750
vendored
Normal file
File diff suppressed because one or more lines are too long
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-22770
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-22770
vendored
Normal file
File diff suppressed because one or more lines are too long
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-22828
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-22828
vendored
Normal file
File diff suppressed because one or more lines are too long
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-25052
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-25052
vendored
Normal file
File diff suppressed because one or more lines are too long
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-25216
vendored
Normal file
1
spec-main/fixtures/release-notes/cache/electron-electron-pull-25216
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -4,13 +4,15 @@
|
||||
"main": "index.js",
|
||||
"version": "0.1.0",
|
||||
"devDependencies": {
|
||||
"@types/sinon": "^9.0.4",
|
||||
"@types/ws": "^7.2.0",
|
||||
"busboy": "^0.3.1",
|
||||
"echo": "file:fixtures/native-addon/echo",
|
||||
"q": "^1.5.1",
|
||||
"sinon": "^9.0.1",
|
||||
"ws": "^7.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"busboy": "^0.3.1",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"pdfjs-dist": "^2.2.228"
|
||||
|
||||
213
spec-main/release-notes-spec.ts
Normal file
213
spec-main/release-notes-spec.ts
Normal file
@@ -0,0 +1,213 @@
|
||||
import { GitProcess, IGitExecutionOptions, IGitResult } from 'dugite';
|
||||
import { expect } from 'chai';
|
||||
import * as notes from '../script/release/notes/notes.js';
|
||||
import * as path from 'path';
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
/* Fake a Dugite GitProcess that only returns the specific
|
||||
commits that we want to test */
|
||||
|
||||
class Commit {
|
||||
sha1: string;
|
||||
subject: string;
|
||||
constructor (sha1: string, subject: string) {
|
||||
this.sha1 = sha1;
|
||||
this.subject = subject;
|
||||
}
|
||||
}
|
||||
|
||||
class GitFake {
|
||||
branches: {
|
||||
[key: string]: Commit[],
|
||||
};
|
||||
|
||||
constructor () {
|
||||
this.branches = {};
|
||||
}
|
||||
|
||||
setBranch (name: string, commits: Array<Commit>): void {
|
||||
this.branches[name] = commits;
|
||||
}
|
||||
|
||||
// find the newest shared commit between branches a and b
|
||||
mergeBase (a: string, b:string): string {
|
||||
for (const commit of [...this.branches[a].reverse()]) {
|
||||
if (this.branches[b].map((commit: Commit) => commit.sha1).includes(commit.sha1)) {
|
||||
return commit.sha1;
|
||||
}
|
||||
}
|
||||
console.error('test error: branches not related');
|
||||
return '';
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
exec (args: string[], path: string, options?: IGitExecutionOptions | undefined): Promise<IGitResult> {
|
||||
let stdout = '';
|
||||
const stderr = '';
|
||||
const exitCode = 0;
|
||||
|
||||
if (args.length === 3 && args[0] === 'merge-base') {
|
||||
// expected form: `git merge-base branchName1 branchName2`
|
||||
const a: string = args[1]!;
|
||||
const b: string = args[2]!;
|
||||
stdout = this.mergeBase(a, b);
|
||||
} else if (args.length === 3 && args[0] === 'log' && args[1] === '--format=%H') {
|
||||
// exepcted form: `git log --format=%H branchName
|
||||
const branch: string = args[2]!;
|
||||
stdout = this.branches[branch].map((commit: Commit) => commit.sha1).join('\n');
|
||||
} else if (args.length > 1 && args[0] === 'log' && args.includes('--format=%H,%s')) {
|
||||
// expected form: `git log --format=%H,%s sha1..branchName
|
||||
const [start, branch] = args[args.length - 1].split('..');
|
||||
const lines : string[] = [];
|
||||
let started = false;
|
||||
for (const commit of this.branches[branch]) {
|
||||
started = started || commit.sha1 === start;
|
||||
if (started) {
|
||||
lines.push(`${commit.sha1},${commit.subject}` /* %H,%s */);
|
||||
}
|
||||
}
|
||||
stdout = lines.join('\n');
|
||||
} else if (args.length === 6 &&
|
||||
args[0] === 'branch' &&
|
||||
args[1] === '--all' &&
|
||||
args[2] === '--contains' &&
|
||||
args[3].endsWith('-x-y')) {
|
||||
// "what branch is this tag in?"
|
||||
// git branch --all --contains ${ref} --sort version:refname
|
||||
stdout = args[3];
|
||||
} else {
|
||||
console.error('unhandled GitProcess.exec():', args);
|
||||
}
|
||||
|
||||
return Promise.resolve({ exitCode, stdout, stderr });
|
||||
}
|
||||
}
|
||||
|
||||
describe('release notes', () => {
|
||||
const sandbox = sinon.createSandbox();
|
||||
const gitFake = new GitFake();
|
||||
|
||||
const oldBranch = '8-x-y';
|
||||
const newBranch = '9-x-y';
|
||||
|
||||
// commits shared by both oldBranch and newBranch
|
||||
const sharedHistory = [
|
||||
new Commit('2abea22b4bffa1626a521711bacec7cd51425818', "fix: explicitly cancel redirects when mode is 'error' (#20686)"),
|
||||
new Commit('467409458e716c68b35fa935d556050ca6bed1c4', 'build: add support for automated minor releases (#20620)') // merge-base
|
||||
];
|
||||
|
||||
// these commits came after newBranch was created
|
||||
const newBreaking = new Commit('2fad53e66b1a2cb6f7dad88fe9bb62d7a461fe98', 'refactor: use v8 serialization for ipc (#20214)');
|
||||
const newFeat = new Commit('89eb309d0b22bd4aec058ffaf983e81e56a5c378', 'feat: allow GUID parameter to avoid systray demotion on Windows (#21891)');
|
||||
const newFix = new Commit('0600420bac25439fc2067d51c6aaa4ee11770577', "fix: don't allow window to go behind menu bar on mac (#22828)");
|
||||
const oldFix = new Commit('f77bd19a70ac2d708d17ddbe4dc12745ca3a8577', 'fix: prevent menu gc during popup (#20785)');
|
||||
|
||||
// a bug that's fixed in both branches by separate PRs
|
||||
const newTropFix = new Commit('a6ff42c190cb5caf8f3e217748e49183a951491b', 'fix: workaround for hang when preventDefault-ing nativeWindowOpen (#22750)');
|
||||
const oldTropFix = new Commit('8751f485c5a6c8c78990bfd55a4350700f81f8cd', 'fix: workaround for hang when preventDefault-ing nativeWindowOpen (#22749)');
|
||||
|
||||
// a PR that has unusual note formatting
|
||||
const sublist = new Commit('61dc1c88fd34a3e8fff80c80ed79d0455970e610', 'fix: client area inset calculation when maximized for framless windows (#25052) (#25216)');
|
||||
|
||||
before(() => {
|
||||
// location of relase-notes' octokit reply cache
|
||||
const fixtureDir = path.resolve(__dirname, 'fixtures', 'release-notes');
|
||||
process.env.NOTES_CACHE_PATH = path.resolve(fixtureDir, 'cache');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
const wrapper = (args: string[], path: string, options?: IGitExecutionOptions | undefined) => gitFake.exec(args, path, options);
|
||||
sandbox.replace(GitProcess, 'exec', wrapper);
|
||||
|
||||
gitFake.setBranch(oldBranch, [...sharedHistory, oldFix]);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('trop annotations', () => {
|
||||
it('shows sibling branches', async function () {
|
||||
const version = 'v9.0.0';
|
||||
gitFake.setBranch(oldBranch, [...sharedHistory, oldTropFix]);
|
||||
gitFake.setBranch(newBranch, [...sharedHistory, newTropFix]);
|
||||
const results: any = await notes.get(oldBranch, newBranch, version);
|
||||
expect(results.fix).to.have.lengthOf(1);
|
||||
expect(results.fix[0].trops).to.have.keys('8-x-y', '9-x-y');
|
||||
});
|
||||
});
|
||||
|
||||
// use case: A malicious contributor could edit the text of their 'Notes:'
|
||||
// in the PR body after a PR's been merged and the maintainers have moved on.
|
||||
// So instead always use the release-clerk PR comment
|
||||
it('uses the release-clerk text', async function () {
|
||||
// realText source: ${fixtureDir}/electron-electron-issue-21891-comments
|
||||
const realText = 'Added GUID parameter to Tray API to avoid system tray icon demotion on Windows';
|
||||
const testCommit = new Commit('89eb309d0b22bd4aec058ffaf983e81e56a5c378', 'feat: lole u got troled hard (#21891)');
|
||||
const version = 'v9.0.0';
|
||||
|
||||
gitFake.setBranch(newBranch, [...sharedHistory, testCommit]);
|
||||
const results: any = await notes.get(oldBranch, newBranch, version);
|
||||
expect(results.feat).to.have.lengthOf(1);
|
||||
expect(results.feat[0].hash).to.equal(testCommit.sha1);
|
||||
expect(results.feat[0].note).to.equal(realText);
|
||||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
it('removes redundant bullet points', async function () {
|
||||
const testCommit = sublist;
|
||||
const version = 'v10.1.1';
|
||||
|
||||
gitFake.setBranch(newBranch, [...sharedHistory, testCommit]);
|
||||
const results: any = await notes.get(oldBranch, newBranch, version);
|
||||
const rendered: any = await notes.render(results);
|
||||
|
||||
expect(rendered).to.not.include('* *');
|
||||
});
|
||||
|
||||
it('indents sublists', async function () {
|
||||
const testCommit = sublist;
|
||||
const version = 'v10.1.1';
|
||||
|
||||
gitFake.setBranch(newBranch, [...sharedHistory, testCommit]);
|
||||
const results: any = await notes.get(oldBranch, newBranch, version);
|
||||
const rendered: any = await notes.render(results);
|
||||
|
||||
expect(rendered).to.include([
|
||||
'* Fixed the following issues for frameless when maximized on Windows:',
|
||||
' * fix unreachable task bar when auto hidden with position top',
|
||||
' * fix 1px extending to secondary monitor',
|
||||
' * fix 1px overflowing into taskbar at certain resolutions',
|
||||
' * fix white line on top of window under 4k resolutions. [#25216]'].join('\n'));
|
||||
});
|
||||
});
|
||||
// test that when you feed in different semantic commit types,
|
||||
// the parser returns them in the results' correct category
|
||||
describe('semantic commit', () => {
|
||||
const version = 'v9.0.0';
|
||||
|
||||
it("honors 'feat' type", async function () {
|
||||
const testCommit = newFeat;
|
||||
gitFake.setBranch(newBranch, [...sharedHistory, testCommit]);
|
||||
const results: any = await notes.get(oldBranch, newBranch, version);
|
||||
expect(results.feat).to.have.lengthOf(1);
|
||||
expect(results.feat[0].hash).to.equal(testCommit.sha1);
|
||||
});
|
||||
|
||||
it("honors 'fix' type", async function () {
|
||||
const testCommit = newFix;
|
||||
gitFake.setBranch(newBranch, [...sharedHistory, testCommit]);
|
||||
const results: any = await notes.get(oldBranch, newBranch, version);
|
||||
expect(results.fix).to.have.lengthOf(1);
|
||||
expect(results.fix[0].hash).to.equal(testCommit.sha1);
|
||||
});
|
||||
|
||||
it("honors 'BREAKING CHANGE' message", async function () {
|
||||
const testCommit = newBreaking;
|
||||
gitFake.setBranch(newBranch, [...sharedHistory, testCommit]);
|
||||
const results: any = await notes.get(oldBranch, newBranch, version);
|
||||
expect(results.breaking).to.have.lengthOf(1);
|
||||
expect(results.breaking[0].hash).to.equal(testCommit.sha1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,11 +2,59 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@sinonjs/commons@^1", "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.7.2":
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217"
|
||||
integrity sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==
|
||||
dependencies:
|
||||
type-detect "4.0.8"
|
||||
|
||||
"@sinonjs/fake-timers@^6.0.0", "@sinonjs/fake-timers@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40"
|
||||
integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
|
||||
"@sinonjs/formatio@^5.0.1":
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-5.0.1.tgz#f13e713cb3313b1ab965901b01b0828ea6b77089"
|
||||
integrity sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1"
|
||||
"@sinonjs/samsam" "^5.0.2"
|
||||
|
||||
"@sinonjs/samsam@^5.0.2", "@sinonjs/samsam@^5.1.0":
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.1.0.tgz#3afe719232b541bb6cf3411a4c399a188de21ec0"
|
||||
integrity sha512-42nyaQOVunX5Pm6GRJobmzbS7iLI+fhERITnETXzzwDZh+TtDr/Au3yAvXVjFmZ4wEUaE4Y3NFZfKv0bV0cbtg==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.6.0"
|
||||
lodash.get "^4.4.2"
|
||||
type-detect "^4.0.8"
|
||||
|
||||
"@sinonjs/text-encoding@^0.7.1":
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
|
||||
integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
|
||||
|
||||
"@types/node@*":
|
||||
version "13.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.0.tgz#b417deda18cf8400f278733499ad5547ed1abec4"
|
||||
integrity sha512-GnZbirvmqZUzMgkFn70c74OQpTTUcCzlhQliTzYjQMqg+hVKcDnxdL19Ne3UdYzdMA/+W3eb646FWn/ZaT1NfQ==
|
||||
|
||||
"@types/sinon@^9.0.4":
|
||||
version "9.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.5.tgz#56b2a12662dd8c7d081cdc511af5f872cb37377f"
|
||||
integrity sha512-4CnkGdM/5/FXDGqL32JQ1ttVrGvhOoesLLF7VnTh4KdjK5N5VQOtxaylFqqTjnHx55MnD9O02Nbk5c1ELC8wlQ==
|
||||
dependencies:
|
||||
"@types/sinonjs__fake-timers" "*"
|
||||
|
||||
"@types/sinonjs__fake-timers@*":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz#681df970358c82836b42f989188d133e218c458e"
|
||||
integrity sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==
|
||||
|
||||
"@types/ws@^7.2.0":
|
||||
version "7.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.2.1.tgz#b800f2b8aee694e2b581113643e20d79dd3b8556"
|
||||
@@ -60,6 +108,11 @@ dicer@0.3.0:
|
||||
dependencies:
|
||||
streamsearch "0.1.2"
|
||||
|
||||
diff@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
||||
|
||||
dirty-chai@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/dirty-chai/-/dirty-chai-2.0.1.tgz#6b2162ef17f7943589da840abc96e75bda01aff3"
|
||||
@@ -83,6 +136,16 @@ fast-json-stable-stringify@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
|
||||
|
||||
has-flag@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
||||
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
||||
|
||||
isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
|
||||
|
||||
json-schema-traverse@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||
@@ -95,6 +158,11 @@ json5@^1.0.1:
|
||||
dependencies:
|
||||
minimist "^1.2.0"
|
||||
|
||||
just-extend@^4.0.2:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.1.0.tgz#7278a4027d889601640ee0ce0e5a00b992467da4"
|
||||
integrity sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==
|
||||
|
||||
loader-utils@^1.0.0:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
|
||||
@@ -104,16 +172,39 @@ loader-utils@^1.0.0:
|
||||
emojis-list "^2.0.0"
|
||||
json5 "^1.0.1"
|
||||
|
||||
lodash.get@^4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
|
||||
|
||||
minimist@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
|
||||
|
||||
nise@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/nise/-/nise-4.0.4.tgz#d73dea3e5731e6561992b8f570be9e363c4512dd"
|
||||
integrity sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
"@sinonjs/fake-timers" "^6.0.0"
|
||||
"@sinonjs/text-encoding" "^0.7.1"
|
||||
just-extend "^4.0.2"
|
||||
path-to-regexp "^1.7.0"
|
||||
|
||||
node-ensure@^0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7"
|
||||
integrity sha1-7K52QVDemYYexcgQ/V0Jaxg5Mqc=
|
||||
|
||||
path-to-regexp@^1.7.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
|
||||
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
|
||||
dependencies:
|
||||
isarray "0.0.1"
|
||||
|
||||
pdfjs-dist@^2.2.228:
|
||||
version "2.2.228"
|
||||
resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-2.2.228.tgz#777b068a0a16c96418433303807c183058b47aaa"
|
||||
@@ -140,11 +231,36 @@ schema-utils@^0.4.0:
|
||||
ajv "^6.1.0"
|
||||
ajv-keywords "^3.1.0"
|
||||
|
||||
sinon@^9.0.1:
|
||||
version "9.0.3"
|
||||
resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.0.3.tgz#bffc3ec28c936332cd2a41833547b9eed201ecff"
|
||||
integrity sha512-IKo9MIM111+smz9JGwLmw5U1075n1YXeAq8YeSFlndCLhAL5KGn6bLgu7b/4AYHTV/LcEMcRm2wU2YiL55/6Pg==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.2"
|
||||
"@sinonjs/fake-timers" "^6.0.1"
|
||||
"@sinonjs/formatio" "^5.0.1"
|
||||
"@sinonjs/samsam" "^5.1.0"
|
||||
diff "^4.0.2"
|
||||
nise "^4.0.4"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
streamsearch@0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
|
||||
integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
|
||||
|
||||
supports-color@^7.1.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
|
||||
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
type-detect@4.0.8, type-detect@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
|
||||
|
||||
uri-js@^4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
|
||||
|
||||
Reference in New Issue
Block a user