feat(rn-changelog-generator): add `single` command to RN changelog generator script and refactor (#955)

This commit is contained in:
Luna 2021-12-20 16:57:08 -08:00 коммит произвёл GitHub
Родитель e90f6fd854
Коммит e136a309dd
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
15 изменённых файлов: 699 добавлений и 400 удалений

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

@ -0,0 +1,5 @@
---
"@rnx-kit/rn-changelog-generator": minor
---
Add `single` command to see changelog message and dimensions for a single commit

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

@ -61,22 +61,28 @@ git pull
popd
```
Then create a GitHub personal access token that can be used by the script to
fetch commit metadata from the GitHub API.
1. Visit the [token settings page](https://github.com/settings/tokens).
1. Generate a new token and **only** give it the `public_repo` scope.
1. Store this token somewhere secure for future usage.
## Usage
Generate a changelog for `react-native` commits between versions 0.65.0 and
0.66.0:
```sh
npx rn-changelog-generator --base v0.65.0 --compare v0.66.0 --repo ../../../react-native --changelog ../../../react-native/CHANGELOG.md --token [GH_TOKEN] > NEW_CHANGES.md
npx rn-changelog-generator --base v0.65.0 --compare v0.66.0 --repo ../../../react-native --changelog ../../../react-native/CHANGELOG.md > NEW_CHANGES.md
```
As explained above, you will need to have a local clone of `react-native`, which
is referenced by the `--repo` parameter. You'll also need to provide a GitHub
personal access token for the `--token` parameter.
is referenced by the `--repo` parameter.
### [Optional] Get a Github 'personal acccess token'
This script uses the Github API to fetch commit metadata. This data is
accessable without authentication as its public.
You can optionally provide a GitHub personal access token for the `--token`
parameter which may be necessary in case Github API rate-limits your requests.
Instructions to create a GitHub personal access token:
1. Visit the [token settings page](https://github.com/settings/tokens).
1. Generate a new token and **only** give it the `public_repo` scope.
1. Store this token somewhere secure for future usage.

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

@ -7,26 +7,19 @@ import fs from "fs";
import chalk from "chalk";
import pLimit from "p-limit";
import deepmerge from "deepmerge";
import https from "https";
import child_process from "child_process";
import { IncomingHttpHeaders } from "http";
import { fetchCommits, Commit } from "./utils/commits";
import getChangeMessage from "./utils/getChangeMessage";
import formatCommitLink from "./utils/formatCommitLink";
import getChangeDimensions, {
CHANGE_TYPE,
CHANGE_CATEGORY,
ChangeType,
ChangeCategory,
} from "./utils/getChangeDimensions";
const execFile = util.promisify(child_process.execFile);
const CHANGE_TYPE = [
"breaking",
"added",
"changed",
"deprecated",
"removed",
"fixed",
"security",
"unknown",
"failed",
] as const;
const CHANGE_CATEGORY = ["android", "ios", "general", "internal"] as const;
export const CHANGES_TEMPLATE: Changes = Object.freeze(
CHANGE_TYPE.reduce(
(acc, key) => ({
@ -39,105 +32,9 @@ export const CHANGES_TEMPLATE: Changes = Object.freeze(
)
) as Changes;
const CHANGELOG_LINE_REGEXP = new RegExp(
`(\\[(${[...CHANGE_TYPE, ...CHANGE_CATEGORY].join("|")})\\]s*)+`,
"i"
);
export type PlatformChanges = Record<ChangeCategory, string[]>;
export interface Commit {
sha: string;
commit: { message: string };
author?: { login: string };
}
export type PlatformChanges = Record<typeof CHANGE_CATEGORY[number], string[]>;
export type Changes = Record<typeof CHANGE_TYPE[number], PlatformChanges>;
//#region NETWORK
//*****************************************************************************
function fetchJSON<T>(token: string, path: string) {
const host = "api.github.com";
console.warn(chalk.yellow(`https://${host}${path}`));
return new Promise<{ json: T; headers: IncomingHttpHeaders }>(
(resolve, reject) => {
let data = "";
https
.get({
host,
path,
headers: {
Authorization: `token ${token}`,
"User-Agent":
"https://github.com/react-native-community/releases/blob/master/scripts/changelog-generator.js",
},
})
.on("response", (response) => {
if (response.statusCode !== 200) {
return reject(
new Error(`[!] Got HTTP status: ${response.statusCode}`)
);
}
response.on("data", (chunk) => {
data += chunk;
});
response.on("end", () => {
try {
resolve({ json: JSON.parse(data), headers: response.headers });
} catch (e) {
reject(e);
}
});
response.on("error", (error) => {
reject(error);
});
});
}
);
}
export function fetchCommits(token: string, base: string, compare: string) {
console.warn(chalk.green("Fetch commit data"));
console.group();
const commits: Commit[] = [];
let page = 1;
return new Promise<Commit[]>((resolve, reject) => {
const fetchPage = () => {
fetchJSON<Commit[]>(
token,
`/repos/facebook/react-native/commits?sha=${compare}&page=${page++}`
)
.then(({ json, headers }) => {
for (const commit of json) {
commits.push(commit);
if (commit.sha === base) {
console.groupEnd();
return resolve(commits);
}
}
if (!(headers["link"] as string).includes("next")) {
throw new Error(
"Did not find commit after paging through all commits"
);
}
setImmediate(fetchPage);
})
.catch((e) => {
console.groupEnd();
reject(e);
});
};
fetchPage();
});
}
//*****************************************************************************
//#endregion
export type Changes = Record<ChangeType, PlatformChanges>;
//#region FILTER COMMITS
//*****************************************************************************
@ -385,108 +282,9 @@ export async function getOffsetBaseCommit(
//*****************************************************************************
//#endregion
//#region UTILITIES
//*****************************************************************************
function isAndroidCommit(change: string) {
return (
!/(\[ios\]|\[general\])/i.test(change) &&
(/\b(android|java)\b/i.test(change) || /android/i.test(change))
);
}
function isIOSCommit(change: string) {
return (
!/(\[android\]|\[general\])/i.test(change) &&
(/\b(ios|xcode|swift|objective-c|iphone|ipad)\b/i.test(change) ||
/ios\b/i.test(change) ||
/\brct/i.test(change))
);
}
function isBreaking(change: string) {
return /\b(breaking)\b/i.test(change);
}
function isAdded(change: string) {
return /\b(added)\b/i.test(change);
}
function isChanged(change: string) {
return /\b(changed)\b/i.test(change);
}
function isDeprecated(change: string) {
return /\b(deprecated)\b/i.test(change);
}
function isRemoved(change: string) {
return /\b(removed)\b/i.test(change);
}
function isFixed(change: string) {
return /\b(fixed)\b/i.test(change);
}
function isSecurity(change: string) {
return /\b(security)\b/i.test(change);
}
function isFabric(change: string) {
return /\b(fabric)\b/i.test(change);
}
function isTurboModules(change: string) {
return /\b(tm)\b/i.test(change);
}
function isInternal(change: string) {
return /\[internal\]/i.test(change);
}
//*****************************************************************************
//#endregion
//#region FORMATTING
//*****************************************************************************
function formatCommitLink(sha: string) {
return `https://github.com/facebook/react-native/commit/${sha}`;
}
export function getChangeMessage(item: Commit, onlyMessage = false) {
const commitMessage = item.commit.message.split("\n");
let entry =
commitMessage
.reverse()
.find((a) => /\[ios\]|\[android\]|\[general\]/i.test(a)) ||
commitMessage.reverse()[0];
entry = entry.replace(/^((changelog:\s*)?(\[\w+\]\s?)+[\s-]*)/i, ""); //Remove the [General] [whatever]
entry = entry.replace(/ \(#\d*\)$/i, ""); //Remove the PR number if it's on the end
// Capitalize
if (/^[a-z]/.test(entry)) {
entry = entry.slice(0, 1).toUpperCase() + entry.slice(1);
}
if (onlyMessage) {
return entry;
}
const authorSection = `([${item.sha.slice(0, 10)}](${formatCommitLink(
item.sha
)})${
item.author
? " by [@" +
item.author.login +
"](https://github.com/" +
item.author.login +
")"
: ""
})`;
return `- ${entry} ${authorSection}`;
}
export function getChangelogDesc(
commits: Commit[],
verbose: boolean,
@ -496,88 +294,28 @@ export function getChangelogDesc(
const commitsWithoutExactChangelogTemplate: string[] = [];
commits.forEach((item) => {
let change = item.commit.message.split("\n").find((line) => {
return CHANGELOG_LINE_REGEXP.test(line);
});
if (!change) {
const {
changeCategory,
changeType,
doesNotFollowTemplate,
fabric,
internal,
turboModules,
} = getChangeDimensions(item);
if (doesNotFollowTemplate) {
commitsWithoutExactChangelogTemplate.push(item.sha);
change = item.commit.message;
}
if (!verbose && (fabric || turboModules || internal)) {
return;
}
const message = getChangeMessage(item, onlyMessage);
if (!verbose) {
if (isFabric(change.split("\n")[0])) return;
if (isTurboModules(change.split("\n")[0])) return;
if (isInternal(change)) return;
}
if (isBreaking(change)) {
if (isAndroidCommit(change)) {
acc.breaking.android.push(message);
} else if (isIOSCommit(change)) {
acc.breaking.ios.push(message);
} else {
acc.breaking.general.push(message);
}
} else if (isAdded(change)) {
if (isAndroidCommit(change)) {
acc.added.android.push(message);
} else if (isIOSCommit(change)) {
acc.added.ios.push(message);
} else {
acc.added.general.push(message);
}
} else if (isChanged(change)) {
if (isAndroidCommit(change)) {
acc.changed.android.push(message);
} else if (isIOSCommit(change)) {
acc.changed.ios.push(message);
} else {
acc.changed.general.push(message);
}
} else if (isFixed(change)) {
if (isAndroidCommit(change)) {
acc.fixed.android.push(message);
} else if (isIOSCommit(change)) {
acc.fixed.ios.push(message);
} else {
acc.fixed.general.push(message);
}
} else if (isRemoved(change)) {
if (isAndroidCommit(change)) {
acc.removed.android.push(message);
} else if (isIOSCommit(change)) {
acc.removed.ios.push(message);
} else {
acc.removed.general.push(message);
}
} else if (isDeprecated(change)) {
if (isAndroidCommit(change)) {
acc.deprecated.android.push(message);
} else if (isIOSCommit(change)) {
acc.deprecated.ios.push(message);
} else {
acc.deprecated.general.push(message);
}
} else if (isSecurity(change)) {
if (isAndroidCommit(change)) {
acc.security.android.push(message);
} else if (isIOSCommit(change)) {
acc.security.ios.push(message);
} else {
acc.security.general.push(message);
}
} else if (item.commit.message.match(/changelog/i)) {
acc.failed.general.push(message);
if (changeType === "failed") {
acc[changeType].general.push(message);
} else {
if (isAndroidCommit(change)) {
acc.unknown.android.push(message);
} else if (isIOSCommit(change)) {
acc.unknown.ios.push(message);
} else {
acc.unknown.general.push(message);
}
acc[changeType][changeCategory].push(message);
}
});
@ -741,7 +479,7 @@ export async function getAllChangelogDescriptions(
export async function run(
options: Parameters<typeof getAllChangelogDescriptions>[1] & {
token: string;
token: string | null;
base: string;
compare: string;
}
@ -755,66 +493,17 @@ export async function run(
return buildMarkDown(options.compare, changes);
}
if (require.main === module) {
const argv = require("yargs")
.usage(
"$0 [args]",
"Generate a React Native changelog from the commits and PRs"
)
.options({
base: {
alias: "b",
string: true,
describe:
"The base branch/tag/commit to compare against (most likely the previous stable version)",
demandOption: true,
},
compare: {
alias: "c",
string: true,
describe:
"The new version branch/tag/commit (most likely the latest release candidate)",
demandOption: true,
},
repo: {
alias: "r",
string: true,
describe: "The path to an up-to-date clone of the react-native repo",
demandOption: true,
},
changelog: {
alias: "f",
string: true,
describe: "The path to the existing CHANGELOG.md file",
demandOption: true,
default: path.resolve(__dirname, "../CHANGELOG.md"),
},
token: {
alias: "t",
string: true,
describe:
"A GitHub token that has `public_repo` access (generate at https://github.com/settings/tokens)",
demandOption: true,
},
maxWorkers: {
alias: "w",
number: true,
describe:
"Specifies the maximum number of concurrent sub-processes that will be spawned",
default: 10,
},
verbose: {
alias: "v",
boolean: true,
describe:
"Verbose listing, includes internal changes as well as public-facing changes",
demandOption: false,
default: false,
},
})
.version(false)
.help("help").argv;
interface GenerateArgs {
base: string;
compare: string;
repo: string;
changelog: string;
token: string | null;
maxWorkers: number;
verbose: boolean;
}
function handler(argv: GenerateArgs) {
const gitDir = path.join(argv.repo, ".git");
git(gitDir, "rev-parse")
.catch(() => {
@ -834,5 +523,61 @@ if (require.main === module) {
});
}
export default {
handler,
args: {
base: {
alias: "b",
string: true,
describe:
"The base branch/tag/commit to compare against (most likely the previous stable version)",
demandOption: true,
},
compare: {
alias: "c",
string: true,
describe:
"The new version branch/tag/commit (most likely the latest release candidate)",
demandOption: true,
},
repo: {
alias: "r",
string: true,
describe: "The path to an up-to-date clone of the react-native repo",
demandOption: true,
},
changelog: {
alias: "f",
string: true,
describe: "The path to the existing CHANGELOG.md file",
demandOption: true,
default: path.resolve(__dirname, "../CHANGELOG.md"),
},
token: {
alias: "t",
string: true,
describe:
"A GitHub token that has `public_repo` access (generate at https://github.com/settings/tokens)",
demandOption: false,
default: null,
},
maxWorkers: {
alias: "w",
number: true,
describe:
"Specifies the maximum number of concurrent sub-processes that will be spawned",
default: 10,
},
verbose: {
alias: "v",
boolean: true,
describe:
"Verbose listing, includes internal changes as well as public-facing changes",
demandOption: false,
default: false,
},
},
};
//*****************************************************************************
//#endregion

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

@ -1 +1,19 @@
export { run } from "./changelog-generator";
import generator from "./generator";
import single from "./single";
if (require.main === module) {
require("yargs")
.command(
"$0",
"Generate a React Native changelog from the commits and PRs",
generator.args,
generator.handler
)
.command(
"single",
"Generate changelog message and classification for single commit",
single.args,
single.handler
)
.help("help").argv;
}

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

@ -0,0 +1,33 @@
import { fetchCommit } from "./utils/commits";
import getChangeDimensions from "./utils/getChangeDimensions";
import getChangeMessage from "./utils/getChangeMessage";
interface SingleArgs {
commit: string;
token: string | null;
}
async function handler(argv: SingleArgs) {
const commitData = await fetchCommit(argv.token, argv.commit);
console.log(getChangeMessage(commitData));
console.log(getChangeDimensions(commitData));
}
export default {
handler,
args: {
token: {
alias: "t",
string: true,
describe:
"A GitHub token that has `public_repo` access (generate at https://github.com/settings/tokens)",
demandOption: false,
default: null,
},
commit: {
alias: "c",
string: true,
describe: "Commit sha to generate changelog message for",
},
},
};

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

@ -0,0 +1,105 @@
import https from "https";
import chalk from "chalk";
import { IncomingHttpHeaders } from "http";
export interface Commit {
sha: string;
commit: { message: string };
author?: { login: string };
}
function fetchJSON<T>(token: string | null, path: string) {
const host = "api.github.com";
console.warn(chalk.yellow(`https://${host}${path}`));
return new Promise<{ json: T; headers: IncomingHttpHeaders }>(
(resolve, reject) => {
let data = "";
https
.get({
host,
path,
headers: {
...(token != null ? { Authorization: `token ${token}` } : null),
"User-Agent":
"https://github.com/react-native-community/releases/blob/master/scripts/changelog-generator.js",
},
})
.on("response", (response) => {
if (response.statusCode !== 200) {
return reject(
new Error(`[!] Got HTTP status: ${response.statusCode}`)
);
}
response.on("data", (chunk) => {
data += chunk;
});
response.on("end", () => {
try {
resolve({ json: JSON.parse(data), headers: response.headers });
} catch (e) {
reject(e);
}
});
response.on("error", (error) => {
reject(error);
});
});
}
);
}
export function fetchCommits(
token: string | null,
base: string,
compare: string
) {
console.warn(chalk.green("Fetch commit data"));
console.group();
const commits: Commit[] = [];
let page = 1;
return new Promise<Commit[]>((resolve, reject) => {
const fetchPage = () => {
fetchJSON<Commit[]>(
token,
`/repos/facebook/react-native/commits?sha=${compare}&page=${page++}`
)
.then(({ json, headers }) => {
for (const commit of json) {
commits.push(commit);
if (commit.sha === base) {
console.groupEnd();
return resolve(commits);
}
}
if (!(headers["link"] as string).includes("next")) {
throw new Error(
"Did not find commit after paging through all commits"
);
}
setImmediate(fetchPage);
})
.catch((e) => {
console.groupEnd();
reject(e);
});
};
fetchPage();
});
}
export function fetchCommit(token: string | null, sha: string) {
return new Promise<Commit>((resolve, reject) => {
fetchJSON<Commit>(token, `/repos/facebook/react-native/commits/${sha}`)
.then(({ json }) => {
resolve(json);
})
.catch((e) => {
console.error(e);
reject(e);
});
});
}

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

@ -0,0 +1,3 @@
export default function formatCommitLink(sha: string) {
return `https://github.com/facebook/react-native/commit/${sha}`;
}

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

@ -0,0 +1,133 @@
import { Commit } from "./commits";
export const CHANGE_TYPE = [
"breaking",
"added",
"changed",
"deprecated",
"removed",
"fixed",
"security",
"unknown",
"failed",
] as const;
export type ChangeType = typeof CHANGE_TYPE[number];
export const CHANGE_CATEGORY = [
"android",
"ios",
"general",
"internal",
] as const;
export type ChangeCategory = typeof CHANGE_CATEGORY[number];
const CHANGELOG_LINE_REGEXP = new RegExp(
`(\\[(${[...CHANGE_TYPE, ...CHANGE_CATEGORY].join("|")})\\]s*)+`,
"i"
);
function isAndroidCommit(change: string) {
return (
!/(\[ios\]|\[general\])/i.test(change) &&
(/\b(android|java)\b/i.test(change) || /android/i.test(change))
);
}
function isIOSCommit(change: string) {
return (
!/(\[android\]|\[general\])/i.test(change) &&
(/\b(ios|xcode|swift|objective-c|iphone|ipad)\b/i.test(change) ||
/ios\b/i.test(change) ||
/\brct/i.test(change))
);
}
function isBreaking(change: string) {
return /\b(breaking)\b/i.test(change);
}
function isAdded(change: string) {
return /\b(added)\b/i.test(change);
}
function isChanged(change: string) {
return /\b(changed)\b/i.test(change);
}
function isDeprecated(change: string) {
return /\b(deprecated)\b/i.test(change);
}
function isRemoved(change: string) {
return /\b(removed)\b/i.test(change);
}
function isFixed(change: string) {
return /\b(fixed)\b/i.test(change);
}
function isSecurity(change: string) {
return /\b(security)\b/i.test(change);
}
function isFabric(change: string) {
return /\b(fabric)\b/i.test(change);
}
function isTurboModules(change: string) {
return /\b(tm)\b/i.test(change);
}
function isInternal(change: string) {
return /\[internal\]/i.test(change);
}
function getChangeType(changelogMsg: string, commitMsg: string): ChangeType {
if (isBreaking(changelogMsg)) {
return "breaking";
} else if (isAdded(changelogMsg)) {
return "added";
} else if (isChanged(changelogMsg)) {
return "changed";
} else if (isFixed(changelogMsg)) {
return "fixed";
} else if (isRemoved(changelogMsg)) {
return "removed";
} else if (isDeprecated(changelogMsg)) {
return "deprecated";
} else if (isSecurity(commitMsg)) {
return "security";
} else if (commitMsg.match(/changelog/i)) {
return "failed";
} else {
return "unknown";
}
}
function getChangeCategory(commitMsg: string): ChangeCategory {
if (isAndroidCommit(commitMsg)) {
return "android";
} else if (isIOSCommit(commitMsg)) {
return "ios";
} else {
return "general";
}
}
export default function getChangeDimensions(item: Commit) {
const commitMsg = item.commit.message;
let changelogMsg = commitMsg.split("\n").find((line) => {
return CHANGELOG_LINE_REGEXP.test(line);
});
const doesNotFollowTemplate = !changelogMsg;
if (!changelogMsg) {
changelogMsg = commitMsg;
}
return {
doesNotFollowTemplate,
changeCategory: getChangeCategory(changelogMsg),
changeType: getChangeType(changelogMsg, commitMsg),
fabric: isFabric(changelogMsg.split("\n")[0]),
internal: isInternal(changelogMsg),
turboModules: isTurboModules(changelogMsg.split("\n")[0]),
};
}

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

@ -0,0 +1,35 @@
import { Commit } from "./commits";
import formatCommitLink from "./formatCommitLink";
export default function getChangeMessage(item: Commit, onlyMessage = false) {
const commitMessage = item.commit.message.split("\n");
let entry =
commitMessage
.reverse()
.find((a) => /\[ios\]|\[android\]|\[general\]/i.test(a)) ||
commitMessage.reverse()[0];
entry = entry.replace(/^((changelog:\s*)?(\[\w+\]\s?)+[\s-]*)/i, ""); //Remove the [General] [whatever]
entry = entry.replace(/ \(#\d*\)$/i, ""); //Remove the PR number if it's on the end
// Capitalize
if (/^[a-z]/.test(entry)) {
entry = entry.slice(0, 1).toUpperCase() + entry.slice(1);
}
if (onlyMessage) {
return entry;
}
const authorSection = `([${item.sha.slice(0, 10)}](${formatCommitLink(
item.sha
)})${
item.author
? " by [@" +
item.author.login +
"](https://github.com/" +
item.author.login +
")"
: ""
})`;
return `- ${entry} ${authorSection}`;
}

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

@ -0,0 +1,110 @@
{
"sha": "50e109c78d3ae9c10d8472d2f4888d7f59214fdd",
"node_id": "C_kwDOAbrxp9oAKDUwZTEwOWM3OGQzYWU5YzEwZDg0NzJkMmY0ODg4ZDdmNTkyMTRmZGQ",
"commit": {
"author": {
"name": "Luna Wei",
"email": "luwe@fb.com",
"date": "2021-12-18T02:35:48Z"
},
"committer": {
"name": "Facebook GitHub Bot",
"email": "facebook-github-bot@users.noreply.github.com",
"date": "2021-12-18T02:37:37Z"
},
"message": "Fix test_js/test_js_prev_lts\n\nSummary:\nChangelog: [Internal] Remove un-necessary package installs which was using `npm install flow-bin --save-dev` which was wiping out our `node_modules` from the circleCI yarn install.\n\nIt was un-necessary as we already have `flow-bin` as a dependency in our current set-up.\n\nIn addtion, we were running `npm pack` without properly copying over our package.json dependencies (which occurs in `prepare-package-for-release`) for a consumable react-native package.\n\nThis may not have caused issue but technically we were creating an \"incomplete\" package to do our e2e testing on.\n\nReviewed By: charlesbdudley\n\nDifferential Revision: D33197965\n\nfbshipit-source-id: 6583ef1f8e17333c0f27ecc37635c36ae5a0bb62",
"tree": {
"sha": "bc76306afa2dda29080c242f8fd999cf47499b1f",
"url": "https://api.github.com/repos/facebook/react-native/git/trees/bc76306afa2dda29080c242f8fd999cf47499b1f"
},
"url": "https://api.github.com/repos/facebook/react-native/git/commits/50e109c78d3ae9c10d8472d2f4888d7f59214fdd",
"comment_count": 0,
"verification": {
"verified": false,
"reason": "unsigned",
"signature": null,
"payload": null
}
},
"url": "https://api.github.com/repos/facebook/react-native/commits/50e109c78d3ae9c10d8472d2f4888d7f59214fdd",
"html_url": "https://github.com/facebook/react-native/commit/50e109c78d3ae9c10d8472d2f4888d7f59214fdd",
"comments_url": "https://api.github.com/repos/facebook/react-native/commits/50e109c78d3ae9c10d8472d2f4888d7f59214fdd/comments",
"author": {
"login": "lunaleaps",
"id": 1309636,
"node_id": "MDQ6VXNlcjEzMDk2MzY=",
"avatar_url": "https://avatars.githubusercontent.com/u/1309636?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/lunaleaps",
"html_url": "https://github.com/lunaleaps",
"followers_url": "https://api.github.com/users/lunaleaps/followers",
"following_url": "https://api.github.com/users/lunaleaps/following{/other_user}",
"gists_url": "https://api.github.com/users/lunaleaps/gists{/gist_id}",
"starred_url": "https://api.github.com/users/lunaleaps/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/lunaleaps/subscriptions",
"organizations_url": "https://api.github.com/users/lunaleaps/orgs",
"repos_url": "https://api.github.com/users/lunaleaps/repos",
"events_url": "https://api.github.com/users/lunaleaps/events{/privacy}",
"received_events_url": "https://api.github.com/users/lunaleaps/received_events",
"type": "User",
"site_admin": false
},
"committer": {
"login": "facebook-github-bot",
"id": 6422482,
"node_id": "MDQ6VXNlcjY0MjI0ODI=",
"avatar_url": "https://avatars.githubusercontent.com/u/6422482?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/facebook-github-bot",
"html_url": "https://github.com/facebook-github-bot",
"followers_url": "https://api.github.com/users/facebook-github-bot/followers",
"following_url": "https://api.github.com/users/facebook-github-bot/following{/other_user}",
"gists_url": "https://api.github.com/users/facebook-github-bot/gists{/gist_id}",
"starred_url": "https://api.github.com/users/facebook-github-bot/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/facebook-github-bot/subscriptions",
"organizations_url": "https://api.github.com/users/facebook-github-bot/orgs",
"repos_url": "https://api.github.com/users/facebook-github-bot/repos",
"events_url": "https://api.github.com/users/facebook-github-bot/events{/privacy}",
"received_events_url": "https://api.github.com/users/facebook-github-bot/received_events",
"type": "User",
"site_admin": false
},
"parents": [
{
"sha": "d1e359a15a9398ad64335b7e135c187bb7442373",
"url": "https://api.github.com/repos/facebook/react-native/commits/d1e359a15a9398ad64335b7e135c187bb7442373",
"html_url": "https://github.com/facebook/react-native/commit/d1e359a15a9398ad64335b7e135c187bb7442373"
}
],
"stats": {
"total": 26,
"additions": 7,
"deletions": 19
},
"files": [
{
"sha": "9129098dce574b342aa7d9223041f80806e51306",
"filename": ".circleci/config.yml",
"status": "modified",
"additions": 2,
"deletions": 2,
"changes": 4,
"blob_url": "https://github.com/facebook/react-native/blob/50e109c78d3ae9c10d8472d2f4888d7f59214fdd/.circleci/config.yml",
"raw_url": "https://github.com/facebook/react-native/raw/50e109c78d3ae9c10d8472d2f4888d7f59214fdd/.circleci/config.yml",
"contents_url": "https://api.github.com/repos/facebook/react-native/contents/.circleci/config.yml?ref=50e109c78d3ae9c10d8472d2f4888d7f59214fdd",
"patch": "@@ -78,7 +78,7 @@ commands:\n steps:\n - restore_cache:\n keys:\n- - v4-yarn-cache-{{ arch }}-{{ checksum \"yarn.lock\" }}\n+ - v5-yarn-cache-{{ arch }}-{{ checksum \"yarn.lock\" }}\n - run:\n name: \"Yarn: Install Dependencies\"\n command: |\n@@ -90,7 +90,7 @@ commands:\n - save_cache:\n paths:\n - ~/.cache/yarn\n- key: v4-yarn-cache-{{ arch }}-{{ checksum \"yarn.lock\" }}\n+ key: v5-yarn-cache-{{ arch }}-{{ checksum \"yarn.lock\" }}\n \n install_buck_tooling:\n steps:"
},
{
"sha": "fa7fe3f6e556ef24a986ebcaf6a4160a62761ac9",
"filename": "scripts/run-ci-e2e-tests.js",
"status": "modified",
"additions": 5,
"deletions": 17,
"changes": 22,
"blob_url": "https://github.com/facebook/react-native/blob/50e109c78d3ae9c10d8472d2f4888d7f59214fdd/scripts/run-ci-e2e-tests.js",
"raw_url": "https://github.com/facebook/react-native/raw/50e109c78d3ae9c10d8472d2f4888d7f59214fdd/scripts/run-ci-e2e-tests.js",
"contents_url": "https://api.github.com/repos/facebook/react-native/contents/scripts/run-ci-e2e-tests.js?ref=50e109c78d3ae9c10d8472d2f4888d7f59214fdd",
"patch": "@@ -56,25 +56,13 @@ try {\n }\n }\n \n- if (argv.js) {\n- describe('Install Flow');\n- if (\n- tryExecNTimes(\n- () => {\n- return exec('npm install --save-dev flow-bin').code;\n- },\n- numberOfRetries,\n- () => exec('sleep 10s'),\n- )\n- ) {\n- echo('Failed to install Flow');\n- echo('Most common reason is npm registry connectivity, try again');\n- exitCode = 1;\n- throw Error(exitCode);\n- }\n+ describe('Create react-native package');\n+ if (exec('node ./scripts/set-rn-version.js --version 1000.0.0').code) {\n+ echo('Failed to set version and update package.json ready for release');\n+ exitCode = 1;\n+ throw Error(exitCode);\n }\n \n- describe('Create react-native package');\n if (exec('npm pack').code) {\n echo('Failed to pack react-native');\n exitCode = 1;"
}
]
}

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

@ -1,26 +1,20 @@
import https from "https";
import { EventEmitter } from "events";
import fs from "fs";
import { promises as fs } from "fs";
import path from "path";
import util from "util";
import deepmerge from "deepmerge";
const readFile = util.promisify(fs.readFile);
import {
CHANGES_TEMPLATE,
git,
run,
fetchCommits,
getAllChangelogDescriptions,
getChangeMessage,
getChangelogDesc,
getOffsetBaseCommit,
getOriginalCommit,
getFirstCommitAfterForkingFromMain,
Changes,
PlatformChanges,
} from "../src/changelog-generator";
} from "../src/generator";
if (!process.env.RN_REPO) {
throw new Error(
@ -39,7 +33,7 @@ function requestWithFixtureResponse(fixture: string) {
(responseEmitter as any).headers = { link: 'rel="next"' };
setImmediate(() => {
requestEmitter.emit("response", responseEmitter);
readFile(path.join(__dirname, "__fixtures__", fixture), "utf-8").then(
fs.readFile(path.join(__dirname, "__fixtures__", fixture), "utf-8").then(
(data) => {
responseEmitter.emit("data", data);
responseEmitter.emit("end");
@ -95,23 +89,6 @@ describe(getOffsetBaseCommit, () => {
});
});
describe(getChangeMessage, () => {
it("formats a changelog entry", () => {
expect(
getChangeMessage({
sha: "abcde123456789",
commit: {
message:
"Some ignored commit message\n\n[iOS] [Fixed] - Some great fixes! (#42)",
},
author: { login: "alloy" },
})
).toEqual(
"- Some great fixes! ([abcde12345](https://github.com/facebook/react-native/commit/abcde123456789) by [@alloy](https://github.com/alloy))"
);
});
});
describe("functions that hit GitHub's commits API", () => {
// The 2nd to last commit in commits-v0.60.5-page-2.json, which is the 59th commit.
const base = "53e32a47e4062f428f8d714333236cedbe05b482";
@ -138,20 +115,6 @@ describe("functions that hit GitHub's commits API", () => {
Object.defineProperty(https, "get", { value: getMock });
});
describe(fetchCommits, () => {
it("paginates back from `compare` to `base`", () => {
return fetchCommits("authn-token", base, compare).then((commits) => {
expect(commits.length).toEqual(59);
expect(commits[0].sha).toEqual(
"35300147ca66677f42e8544264be72ac0e9d1b45"
);
expect(commits[30].sha).toEqual(
"99bc31cfa609e838779c29343684365a2ed6169f"
);
});
});
});
describe(run, () => {
it("fetches commits, filters them, and generates markdown", () => {
return run({

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

@ -0,0 +1,81 @@
import https from "https";
import { EventEmitter } from "events";
import { promises as fs } from "fs";
import path from "path";
import { fetchCommits, fetchCommit } from "../../src/utils/commits";
console.warn = () => {};
console.error = () => {};
function requestWithFixtureResponse(fixture: string) {
const requestEmitter = new EventEmitter();
const responseEmitter = new EventEmitter();
(responseEmitter as any).statusCode = 200;
(responseEmitter as any).headers = { link: 'rel="next"' };
setImmediate(() => {
requestEmitter.emit("response", responseEmitter);
fs.readFile(
path.join(__dirname, "..", "__fixtures__", fixture),
"utf-8"
).then((data) => {
responseEmitter.emit("data", data);
responseEmitter.emit("end");
});
});
return requestEmitter;
}
describe("functions that hit GitHub's commits API", () => {
// The 2nd to last commit in commits-v0.60.5-page-2.json, which is the 59th commit.
const base = "53e32a47e4062f428f8d714333236cedbe05b482";
// The first commit in commits-v0.60.5-page-1.json, which is the last chronologically as the
// GH API returns commits in DESC order.
const compare = "35300147ca66677f42e8544264be72ac0e9d1b45";
const internalCommit = "50e109c78d3ae9c10d8472d2f4888d7f59214fdd";
beforeAll(() => {
const getMock = jest.fn((uri) => {
if (
uri.path ===
`/repos/facebook/react-native/commits?sha=${compare}&page=1`
) {
return requestWithFixtureResponse("commits-v0.60.5-page-1.json");
} else if (
uri.path ===
`/repos/facebook/react-native/commits?sha=${compare}&page=2`
) {
return requestWithFixtureResponse("commits-v0.60.5-page-2.json");
} else if (
uri.path === `/repos/facebook/react-native/commits/${internalCommit}`
) {
return requestWithFixtureResponse("commit-internal.json");
} else {
throw new Error(`Unexpected request: ${uri.path}`);
}
});
Object.defineProperty(https, "get", { value: getMock });
});
describe(fetchCommits, () => {
it("paginates back from `compare` to `base`", () => {
return fetchCommits("authn-token", base, compare).then((commits) => {
expect(commits.length).toEqual(59);
expect(commits[0].sha).toEqual(
"35300147ca66677f42e8544264be72ac0e9d1b45"
);
expect(commits[30].sha).toEqual(
"99bc31cfa609e838779c29343684365a2ed6169f"
);
});
});
});
describe(fetchCommit, () => {
it("returns metadata for single commit", () => {
return fetchCommit(null, internalCommit).then((commitMetadata) => {
expect(commitMetadata.sha).toEqual(internalCommit);
});
});
});
});

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

@ -0,0 +1,41 @@
import getChangeDimensions from "../../src/utils/getChangeDimensions";
console.warn = () => {};
console.error = () => {};
describe(getChangeDimensions, () => {
it("should have fixed, ios dimensions", () => {
const dimensions = getChangeDimensions({
sha: "abcde123456789",
commit: {
message:
"Some ignored commit message\n\n[iOS] [Fixed] - Some great fixes! (#42)",
},
author: { login: "alloy" },
});
expect(dimensions.changeCategory).toBe("ios");
expect(dimensions.changeType).toBe("fixed");
expect(dimensions.fabric).toBe(false);
expect(dimensions.turboModules).toBe(false);
expect(dimensions.doesNotFollowTemplate).toBe(false);
});
it("should have failed, general dimensions", () => {
const dimensions = getChangeDimensions({
sha: "50e109c78d3ae9c10d8472d2f4888d7f59214fdd",
commit: {
message:
'Fix test_js/test_js_prev_lts\n\nSummary:\nChangelog: [Internal] Remove un-necessary package installs which was using `npm install flow-bin --save-dev` which was wiping out our `node_modules` from the circleCI yarn install.\n\nIt was un-necessary as we already have `flow-bin` as a dependency in our current set-up.\n\nIn addtion, we were running `npm pack` without properly copying over our package.json dependencies (which occurs in `prepare-package-for-release`) for a consumable react-native package.\n\nThis may not have caused issue but technically we were creating an "incomplete" package to do our e2e testing on.\n\nReviewed By: charlesbdudley\n\nDifferential Revision: D33197965\n\nfbshipit-source-id: 6583ef1f8e17333c0f27ecc37635c36ae5a0bb62',
},
author: {
login: "lunaleaps",
},
});
expect(dimensions.doesNotFollowTemplate).toBe(false);
expect(dimensions.fabric).toBe(false);
expect(dimensions.turboModules).toBe(false);
expect(dimensions.internal).toBe(true);
expect(dimensions.changeCategory).toBe("general");
expect(dimensions.changeType).toBe("failed");
});
});

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

@ -0,0 +1,21 @@
import getChangeMessage from "../../src/utils/getChangeMessage";
console.warn = () => {};
console.error = () => {};
describe(getChangeMessage, () => {
it("formats a changelog entry", () => {
expect(
getChangeMessage({
sha: "abcde123456789",
commit: {
message:
"Some ignored commit message\n\n[iOS] [Fixed] - Some great fixes! (#42)",
},
author: { login: "alloy" },
})
).toEqual(
"- Some great fixes! ([abcde12345](https://github.com/facebook/react-native/commit/abcde123456789) by [@alloy](https://github.com/alloy))"
);
});
});