chore: ensure release notes always come from Clerk (#23777)

* chore: ensure release notes always come from Clerk

Now with tests!

* chore: move sinon devDependency into `spec-main`

* refactor: tweak note-spec variable for readability
This commit is contained in:
Charles Kerr 2020-06-08 13:39:44 -05:00 коммит произвёл GitHub
Родитель c6c022dc46
Коммит 980e592271
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
25 изменённых файлов: 372 добавлений и 72 удалений

Просмотреть файл

@ -18,7 +18,6 @@ const { ELECTRON_VERSION, SRC_DIR } = require('../../lib/utils');
const MAX_FAIL_COUNT = 3;
const CHECK_INTERVAL = 5000;
const CACHE_DIR = path.resolve(__dirname, '.cache');
const NO_NOTES = 'No notes';
const FOLLOW_REPOS = ['electron/electron', 'electron/node'];
@ -28,6 +27,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');
/**
***
**/
@ -53,9 +54,7 @@ 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
this.prKeys = new Set(); // GHKey
this.revertHash = null; // string
@ -129,41 +128,11 @@ const OMIT_FROM_RELEASE_NOTES_KEYS = [
'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
@ -181,13 +150,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+)\)$/))) {
@ -219,7 +181,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';
}
@ -243,32 +204,32 @@ 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]+/).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]+/).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'));
}
@ -292,7 +253,8 @@ 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}`;
@ -300,7 +262,7 @@ const getPullCacheFilename = ghKey => `${ghKey.owner}-${ghKey.repo}-pull-${ghKey
const getCommitPulls = async (owner, repo, hash) => {
const name = `${owner}-${repo}-commit-${hash}`;
const retryableFunc = () => octokit.repos.listPullRequestsAssociatedWithCommit({ owner, repo, commit_sha: hash });
const ret = await checkCache(name, () => runRetryable(retryableFunc, MAX_FAIL_COUNT));
let ret = await checkCache(name, () => runRetryable(retryableFunc, MAX_FAIL_COUNT));
// only merged pulls belong in release notes
if (ret && ret.data) {
@ -316,6 +278,11 @@ const getCommitPulls = async (owner, repo, hash) => {
}
}
// ensure the return value has the expected structure, even on failure
if (!ret || !ret.data) {
ret = { data: [] };
}
return ret;
};
@ -447,19 +414,25 @@ function getOldestMajorBranchOfCommit (commit, pool) {
.shift();
}
function commitExistsBeforeMajor (commit, pool, major) {
const firstAppearance = getOldestMajorBranchOfCommit(commit, pool);
return firstAppearance && (firstAppearance < major);
}
/***
**** Main
***/
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();
// get the electron/electron commits
const electron = { owner: 'electron', repo: 'electron', dir: ELECTRON_VERSION };
const electron = { owner: 'electron', repo: 'electron', dir: path.resolve(SRC_DIR, 'electron') };
await addRepoToPool(pool, electron, fromRef, toRef);
// Don't include submodules if comparing across major versions;
@ -496,27 +469,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.note !== NO_NOTES)
.filter(commit => commit.note && (commit.note !== NO_NOTES))
.filter(commit => !((commit.note || commit.subject).match(/^[Bb]ump v\d+\.\d+\.\d+/)));
if (!shouldIncludeMultibranchChanges(newVersion)) {
const currentMajor = semver.parse(newVersion).major;
pool.commits = pool.commits
.filter(commit => getOldestMajorBranchOfCommit(commit, pool) >= currentMajor);
const { major } = semver.parse(newVersion);
pool.commits = pool.commits.filter(commit => !commitExistsBeforeMajor(commit, pool, major));
}
pool.commits = removeSupercededChromiumUpdates(pool.commits);
pool.commits = removeSupercededStackUpdates(pool.commits);
const notes = {
breaking: [],
@ -550,18 +520,24 @@ 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) ];
};
/***

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -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 "}]}

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -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."}]}

Просмотреть файл

@ -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"}]}

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -4,10 +4,12 @@
"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": {

Просмотреть файл

@ -0,0 +1,185 @@
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 {
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)');
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('changes that exist in older branches', () => {
// use case: this fix is NOT news because it was already fixed
// while oldBranch was the latest stable release
it('are skipped if the target version is a new major line (x.0.0)', 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(0);
});
// use case: this fix IS news because it's being fixed in
// multiple stable branches at once, including newBranch.
it('are included if the target version is a minor or patch bump', async function () {
const version = 'v9.0.1';
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);
});
});
// 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);
});
// 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.0"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.0.tgz#c8d68821a854c555bba172f3b06959a0039b236d"
integrity sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q==
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.0.3":
version "5.0.3"
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.0.3.tgz#86f21bdb3d52480faf0892a480c9906aa5a52938"
integrity sha512-QucHkc2uMJ0pFGjJUDP3F9dq5dx8QIaqISl9QgwLOh6P9yv877uONPGXh/OH/0zmM3tW1JjuJltAZV2l7zU+uQ==
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.4"
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.4.tgz#e934f904606632287a6e7f7ab0ce3f08a0dad4b1"
integrity sha512-sJmb32asJZY6Z2u09bl0G2wglSxDlROlAejCjsnor+LzBMz17gu8IU7vKC/vWDnv9zEq2wqADHVXFjf4eE8Gdw==
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.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/nise/-/nise-4.0.3.tgz#9f79ff02fa002ed5ffbc538ad58518fa011dc913"
integrity sha512-EGlhjm7/4KvmmE6B/UFsKh7eHykRl9VH+au8dduHLCyWUO/hr7+N+WtTvDUwc9zHuM1IaIJs/0lQ6Ag1jDkQSg==
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.2"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.0.2.tgz#b9017e24633f4b1c98dfb6e784a5f0509f5fd85d"
integrity sha512-0uF8Q/QHkizNUmbK3LRFqx5cpTttEVXudywY9Uwzy8bTfZUhljZ7ARzSxnRHWYWtVTeh4Cw+tTb3iU21FQVO9A==
dependencies:
"@sinonjs/commons" "^1.7.2"
"@sinonjs/fake-timers" "^6.0.1"
"@sinonjs/formatio" "^5.0.1"
"@sinonjs/samsam" "^5.0.3"
diff "^4.0.2"
nise "^4.0.1"
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.1.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==
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"