Fixed an issue with ignored package being included in PR body incorrectly (#38)

This commit is contained in:
Mateusz Burzyński
2020-07-06 19:08:41 +10:00
committed by GitHub
parent c240b08dfb
commit cd904b92c7
22 changed files with 2246 additions and 1654 deletions
@@ -0,0 +1,4 @@
{
"$schema": "https://unpkg.com/@changesets/config@1.3.0/schema.json",
"ignore": ["ignored-package-pkg-a"]
}
@@ -0,0 +1,8 @@
{
"private": true,
"name": "ignored-package",
"version": "1.0.0",
"workspaces": [
"packages/*"
]
}
@@ -0,0 +1,7 @@
{
"name": "ignored-package-pkg-a",
"version": "1.0.0",
"dependencies": {
"ignored-package-pkg-b": "1.0.0"
}
}
@@ -0,0 +1,4 @@
{
"name": "ignored-package-pkg-b",
"version": "1.0.0"
}
@@ -0,0 +1,3 @@
{
"$schema": "https://unpkg.com/@changesets/config@1.3.0/schema.json"
}
+8
View File
@@ -0,0 +1,8 @@
{
"private": true,
"name": "simple-project",
"version": "1.0.0",
"workspaces": [
"packages/*"
]
}
@@ -0,0 +1,7 @@
{
"name": "simple-project-pkg-a",
"version": "1.0.0",
"dependencies": {
"simple-project-pkg-b": "1.0.0"
}
}
@@ -0,0 +1,4 @@
{
"name": "simple-project-pkg-b",
"version": "1.0.0"
}
+781 -1191
View File
File diff suppressed because one or more lines are too long
-312
View File
@@ -1,312 +0,0 @@
import * as core from "@actions/core";
import { exec } from "@actions/exec";
import * as github from "@actions/github";
import fs from "fs-extra";
import { getPackages, Package } from "@manypkg/get-packages";
import path from "path";
import {
getChangelogEntry,
execWithOutput,
getChangedPackages,
sortTheThings,
} from "./utils";
import * as semver from "semver";
import { readPreState } from "@changesets/pre";
import readChangesets from "@changesets/read";
const createRelease = async (
octokit: github.GitHub,
{ pkg, tagName }: { pkg: Package; tagName: string }
) => {
try {
let changelogFileName = path.join(pkg.dir, "CHANGELOG.md");
let changelog = await fs.readFile(changelogFileName, "utf8");
let changelogEntry = getChangelogEntry(changelog, pkg.packageJson.version);
if (!changelogEntry) {
// we can find a changelog but not the entry for this version
// if this is true, something has probably gone wrong
return core.setFailed(
`Could not find changelog entry for ${pkg.packageJson.name}@${pkg.packageJson.version}`
);
}
await octokit.repos.createRelease({
tag_name: tagName,
body: changelogEntry.content,
prerelease: pkg.packageJson.version.includes("-"),
...github.context.repo,
});
} catch (err) {
// if we can't find a changelog, the user has probably disabled changelogs
if (err.code !== "ENOENT") {
throw err;
}
}
};
(async () => {
let githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
core.setFailed("Please add the GITHUB_TOKEN to the changesets action");
return;
}
let repo = `${github.context.repo.owner}/${github.context.repo.repo}`;
let branch = github.context.ref.replace("refs/heads/", "");
let preState = await readPreState(process.cwd());
let isInPreMode = preState !== undefined && preState.mode === "pre";
const octokit = new github.GitHub(githubToken);
console.log("setting git user");
await exec("git", [
"config",
"--global",
"user.name",
`"github-actions[bot]"`,
]);
await exec("git", [
"config",
"--global",
"user.email",
`"github-actions[bot]@users.noreply.github.com"`,
]);
console.log("setting GitHub credentials");
await fs.writeFile(
`${process.env.HOME}/.netrc`,
`machine github.com\nlogin github-actions[bot]\npassword ${githubToken}`
);
let changesets = await readChangesets(process.cwd());
if (isInPreMode) {
let changesetsToFilter = new Set(preState.changesets);
changesets = changesets.filter((x) => !changesetsToFilter.has(x.id));
}
let hasChangesets = changesets.length !== 0;
let inputs = {
publishScript: core.getInput("publish"),
versionScript: core.getInput("version"),
commit: core.getInput("commit") || "Version Packages",
prTitle: core.getInput("title") || "Version Packages",
};
core.setOutput("published", "false");
core.setOutput("publishedPackages", "[]");
if (!hasChangesets && !inputs.publishScript) {
console.log("No changesets found");
return;
}
if (!hasChangesets && inputs.publishScript) {
console.log(
"No changesets found, attempting to publish any unpublished packages to npm"
);
let npmrcPath = `${process.env.HOME}/.npmrc`;
if (fs.existsSync(npmrcPath)) {
console.log("Found existing .npmrc file");
} else {
console.log("No .npmrc file found, creating one");
fs.writeFileSync(
npmrcPath,
`//registry.npmjs.org/:_authToken=${process.env.NPM_TOKEN}`
);
}
let [publishCommand, ...publishArgs] = inputs.publishScript.split(/\s+/);
let changesetPublishOutput = await execWithOutput(
publishCommand,
publishArgs
);
await exec("git", ["pull", "origin", branch]);
await exec("git", ["push", "origin", `HEAD:${branch}`, "--tags"]);
let { packages, tool } = await getPackages(process.cwd());
let releasedPackages: Package[] = [];
if (tool !== "root") {
let newTagRegex = /New tag:\s+(@[^/]+\/[^@]+|[^/]+)@([^\s]+)/;
let packagesByName = new Map(
packages.map((x) => [x.packageJson.name, x])
);
for (let line of changesetPublishOutput.stdout.split("\n")) {
let match = line.match(newTagRegex);
if (match === null) {
continue;
}
let pkgName = match[1];
let pkg = packagesByName.get(pkgName);
if (pkg === undefined) {
return core.setFailed(
`Package "${pkgName}" not found.` +
"This is probably a bug in the action, please open an issue"
);
}
releasedPackages.push(pkg);
}
await Promise.all(
releasedPackages.map((pkg) =>
createRelease(octokit, {
pkg,
tagName: `${pkg.packageJson.name}@${pkg.packageJson.version}`,
})
)
);
} else {
if (packages.length === 0) {
return core.setFailed(
`No package found.` +
"This is probably a bug in the action, please open an issue"
);
}
const pkg = packages[0];
releasedPackages.push(pkg);
await createRelease(octokit, {
pkg,
tagName: `v${pkg.packageJson.version}`,
});
}
if (releasedPackages.length) {
core.setOutput("published", "true");
core.setOutput(
"publishedPackages",
JSON.stringify(
releasedPackages.map((pkg) => ({
name: pkg.packageJson.name,
version: pkg.packageJson.version,
}))
)
);
}
return;
}
if (hasChangesets) {
let versionBranch = `changeset-release/${branch}`;
let { stderr } = await execWithOutput("git", ["checkout", versionBranch], {
ignoreReturnCode: true,
});
let isCreatingChangesetReleaseBranch = !stderr
.toString()
.includes(`Switched to a new branch '${versionBranch}'`);
if (isCreatingChangesetReleaseBranch) {
await exec("git", ["checkout", "-b", versionBranch]);
}
await exec("git", ["reset", "--hard", github.context.sha]);
if (inputs.versionScript) {
let [versionCommand, ...versionArgs] = inputs.versionScript.split(/\s+/);
await exec(versionCommand, versionArgs);
} else {
let changesetsCliPkgJson = await fs.readJson(
path.join("node_modules", "@changesets", "cli", "package.json")
);
let cmd = semver.lt(changesetsCliPkgJson.version, "2.0.0")
? "bump"
: "version";
await exec("node", ["./node_modules/@changesets/cli/bin.js", cmd]);
}
let searchQuery = `repo:${repo}+state:open+head:${versionBranch}+base:${branch}`;
let searchResultPromise = octokit.search.issuesAndPullRequests({
q: searchQuery,
});
let changedPackages = await getChangedPackages(process.cwd());
let prBodyPromise = (async () => {
return (
`This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and ${
inputs.publishScript
? `the packages will be published to npm automatically`
: `publish to npm yourself or [setup this action to publish automatically](https://github.com/changesets/action#with-publishing)`
}. If you're not ready to do a release yet, that's fine, whenever you add more changesets to ${branch}, this PR will be updated.
${
isInPreMode
? `
⚠️⚠️⚠️⚠️⚠️⚠️
\`${branch}\` is currently in **pre mode** so this branch has prereleases rather than normal releases. If you want to exit prereleases, run \`changeset pre exit\` on \`${branch}\`.
⚠️⚠️⚠️⚠️⚠️⚠️
`
: ""
}
# Releases
` +
(
await Promise.all(
changedPackages.map(async (pkg) => {
let changelogContents = await fs.readFile(
path.join(pkg.dir, "CHANGELOG.md"),
"utf8"
);
let entry = getChangelogEntry(
changelogContents,
pkg.packageJson.version
);
return {
highestLevel: entry.highestLevel,
private: !!pkg.packageJson.private,
content:
`## ${pkg.packageJson.name}@${pkg.packageJson.version}\n\n` +
entry.content,
};
})
)
)
.filter((x) => x)
.sort(sortTheThings)
.map((x) => x.content)
.join("\n ")
);
})();
const prTitle = `${inputs.prTitle}${
isInPreMode ? ` (${preState.tag})` : ""
}`;
const commitMsg = `${inputs.commit}${
isInPreMode ? ` (${preState.tag})` : ""
}`;
await exec("git", ["add", "."]);
await exec("git", ["commit", "-m", commitMsg]);
await exec("git", ["push", "origin", versionBranch, "--force"]);
let searchResult = await searchResultPromise;
console.log(JSON.stringify(searchResult.data, null, 2));
if (searchResult.data.items.length === 0) {
console.log("creating pull request");
await octokit.pulls.create({
base: branch,
head: versionBranch,
title: prTitle,
body: await prBodyPromise,
...github.context.repo,
});
} else {
octokit.pulls.update({
pull_number: searchResult.data.items[0].number,
title: prTitle,
body: await prBodyPromise,
...github.context.repo,
});
console.log("pull request found");
}
}
})().catch((err) => {
console.error(err);
core.setFailed(err.message);
});
+7 -4
View File
@@ -4,12 +4,16 @@
"main": "dist/index.js",
"license": "MIT",
"devDependencies": {
"@changesets/cli": "^2.9.1",
"@changesets/write": "^0.1.3",
"fixturez": "^1.1.0",
"parcel": "^1.12.3",
"prettier": "^2.0.5",
"typescript": "^3.5.3"
},
"scripts": {
"build": "parcel build ./index.ts --no-source-maps --target=node --bundle-node-modules"
"build": "parcel build ./src/index.ts --no-source-maps --target=node --bundle-node-modules",
"test:watch": "jest --watch"
},
"dependencies": {
"@actions/core": "^1.0.0",
@@ -18,8 +22,8 @@
"@babel/core": "^7.6.0",
"@babel/preset-env": "^7.6.0",
"@babel/preset-typescript": "^7.6.0",
"@changesets/pre": "^0.2.0",
"@changesets/read": "^0.4.1",
"@changesets/pre": "^1.0.4",
"@changesets/read": "^0.4.6",
"@manypkg/get-packages": "^1.0.1",
"@types/fs-extra": "^8.0.0",
"@types/jest": "^24.0.18",
@@ -33,7 +37,6 @@
"remark-parse": "^7.0.1",
"remark-stringify": "^7.0.3",
"semver": "^6.3.0",
"tempy": "^0.3.0",
"unified": "^8.3.2"
},
"husky": {
+75
View File
@@ -0,0 +1,75 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`version creates simple PR 1`] = `
Array [
Object {
"base": "some-branch",
"body": "This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and publish to npm yourself or [setup this action to publish automatically](https://github.com/changesets/action#with-publishing). If you're not ready to do a release yet, that's fine, whenever you add more changesets to some-branch, this PR will be updated.
# Releases
## simple-project-pkg-a@1.1.0
### Minor Changes
- Awesome feature
### Patch Changes
- Updated dependencies [undefined]
- simple-project-pkg-b@1.1.0
## simple-project-pkg-b@1.1.0
### Minor Changes
- Awesome feature
",
"head": "changeset-release/some-branch",
"owner": "changesets",
"repo": "action",
"title": "Version Packages",
},
]
`;
exports[`version doesn't include ignored package that got a dependency update in the PR body 1`] = `
Array [
Object {
"base": "some-branch",
"body": "This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and publish to npm yourself or [setup this action to publish automatically](https://github.com/changesets/action#with-publishing). If you're not ready to do a release yet, that's fine, whenever you add more changesets to some-branch, this PR will be updated.
# Releases
## ignored-package-pkg-b@1.1.0
### Minor Changes
- Awesome feature
",
"head": "changeset-release/some-branch",
"owner": "changesets",
"repo": "action",
"title": "Version Packages",
},
]
`;
exports[`version only includes bumped packages in the PR body 1`] = `
Array [
Object {
"base": "some-branch",
"body": "This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and publish to npm yourself or [setup this action to publish automatically](https://github.com/changesets/action#with-publishing). If you're not ready to do a release yet, that's fine, whenever you add more changesets to some-branch, this PR will be updated.
# Releases
## simple-project-pkg-a@1.1.0
### Minor Changes
- Awesome feature
",
"head": "changeset-release/some-branch",
"owner": "changesets",
"repo": "action",
"title": "Version Packages",
},
]
`;
+61
View File
@@ -0,0 +1,61 @@
import { exec } from "@actions/exec";
import { execWithOutput } from "./utils";
export const setupUser = async () => {
await exec("git", [
"config",
"--global",
"user.name",
`"github-actions[bot]"`,
]);
await exec("git", [
"config",
"--global",
"user.email",
`"github-actions[bot]@users.noreply.github.com"`,
]);
};
export const pullBranch = async (branch: string) => {
await exec("git", ["pull", "origin", branch]);
};
export const push = async (
branch: string,
{ force, includeTags }: { force?: boolean; includeTags?: boolean } = {}
) => {
await exec(
"git",
[
"push",
"origin",
`HEAD:${branch}`,
includeTags && "--tags",
force && "--force",
].filter<string>(Boolean as any)
);
};
export const switchToMaybeExistingBranch = async (branch: string) => {
let { stderr } = await execWithOutput("git", ["checkout", branch], {
ignoreReturnCode: true,
});
let isCreatingBranch = !stderr
.toString()
.includes(`Switched to a new branch '${branch}'`);
if (isCreatingBranch) {
await exec("git", ["checkout", "-b", branch]);
}
};
export const reset = async (
pathSpec: string,
mode: "hard" | "soft" | "mixed" = "hard"
) => {
await exec("git", ["reset", `--${mode}`, pathSpec]);
};
export const commitAll = async (message: string) => {
await exec("git", ["add", "."]);
await exec("git", ["commit", "-m", message]);
};
+82
View File
@@ -0,0 +1,82 @@
import * as core from "@actions/core";
import fs from "fs-extra";
import * as gitUtils from "./gitUtils";
import { runPublish, runVersion } from "./run";
import readChangesetState from "./readChangesetState";
const getOptionalInput = (name: string) => core.getInput(name) || undefined;
(async () => {
let githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
core.setFailed("Please add the GITHUB_TOKEN to the changesets action");
return;
}
core.setOutput("published", "false");
core.setOutput("publishedPackages", "[]");
console.log("setting git user");
await gitUtils.setupUser();
console.log("setting GitHub credentials");
await fs.writeFile(
`${process.env.HOME}/.netrc`,
`machine github.com\nlogin github-actions[bot]\npassword ${githubToken}`
);
let { changesets } = await readChangesetState();
let publishScript = core.getInput("publish");
let hasChangesets = changesets.length !== 0;
let hasPublishScript = !!publishScript;
switch (true) {
case !hasChangesets && !hasPublishScript:
console.log("No changesets found");
return;
case !hasChangesets && hasPublishScript: {
console.log(
"No changesets found, attempting to publish any unpublished packages to npm"
);
let npmrcPath = `${process.env.HOME}/.npmrc`;
if (fs.existsSync(npmrcPath)) {
console.log("Found existing .npmrc file");
} else {
console.log("No .npmrc file found, creating one");
fs.writeFileSync(
npmrcPath,
`//registry.npmjs.org/:_authToken=${process.env.NPM_TOKEN}`
);
}
const result = await runPublish({
script: publishScript,
githubToken,
});
if (result.published) {
core.setOutput("published", "true");
core.setOutput(
"publishedPackages",
JSON.stringify(result.publishedPackages)
);
}
return;
}
case hasChangesets:
await runVersion({
script: getOptionalInput("version"),
githubToken,
prTitle: getOptionalInput("title"),
commitMessage: getOptionalInput("commit"),
hasPublishScript,
});
return;
}
})().catch((err) => {
console.error(err);
core.setFailed(err.message);
});
+27
View File
@@ -0,0 +1,27 @@
import { PreState, NewChangeset } from "@changesets/types";
import { readPreState } from "@changesets/pre";
import readChangesets from "@changesets/read";
export type ChangesetState = {
preState: PreState | undefined;
changesets: NewChangeset[];
};
export default async function readChangesetState(
cwd: string = process.cwd()
): Promise<ChangesetState> {
let preState = await readPreState(cwd);
let isInPreMode = preState !== undefined && preState.mode === "pre";
let changesets = await readChangesets(cwd);
if (isInPreMode) {
let changesetsToFilter = new Set(preState.changesets);
changesets = changesets.filter((x) => !changesetsToFilter.has(x.id));
}
return {
preState: isInPreMode ? preState : undefined,
changesets,
};
}
+148
View File
@@ -0,0 +1,148 @@
import fixturez from "fixturez";
import * as github from "@actions/github";
import fs from "fs-extra";
import path from "path";
import writeChangeset from "@changesets/write";
import { Changeset } from "@changesets/types";
import { runVersion } from "./run";
jest.mock("@actions/github", () => ({
context: {
repo: {
owner: "changesets",
repo: "action",
},
ref: "refs/heads/some-branch",
sha: "xeac7",
},
GitHub: jest.fn(),
}));
jest.mock("./gitUtils");
let mockedGithubMethods = {
search: {
issuesAndPullRequests: jest.fn(),
},
pulls: {
create: jest.fn(),
},
repos: {
createRelease: jest.fn(),
},
};
(github.GitHub as any).mockImplementation(() => mockedGithubMethods);
let f = fixturez(__dirname);
const linkNodeModules = async (cwd: string) => {
await fs.symlink(
path.join(__dirname, "..", "node_modules"),
path.join(cwd, "node_modules")
);
};
const writeChangesets = (changesets: Changeset[], cwd: string) => {
return Promise.all(changesets.map((commit) => writeChangeset(commit, cwd)));
};
beforeEach(() => {
jest.clearAllMocks();
});
describe("version", () => {
it("creates simple PR", async () => {
let cwd = f.copy("simple-project");
linkNodeModules(cwd);
mockedGithubMethods.search.issuesAndPullRequests.mockImplementationOnce(
() => ({ data: { items: [] } })
);
await writeChangesets(
[
{
releases: [
{
name: "simple-project-pkg-a",
type: "minor",
},
{
name: "simple-project-pkg-b",
type: "minor",
},
],
summary: "Awesome feature",
},
],
cwd
);
await runVersion({
githubToken: "@@GITHUB_TOKEN",
cwd,
});
expect(mockedGithubMethods.pulls.create.mock.calls[0]).toMatchSnapshot();
});
it("only includes bumped packages in the PR body", async () => {
let cwd = f.copy("simple-project");
linkNodeModules(cwd);
mockedGithubMethods.search.issuesAndPullRequests.mockImplementationOnce(
() => ({ data: { items: [] } })
);
await writeChangesets(
[
{
releases: [
{
name: "simple-project-pkg-a",
type: "minor",
},
],
summary: "Awesome feature",
},
],
cwd
);
await runVersion({
githubToken: "@@GITHUB_TOKEN",
cwd,
});
expect(mockedGithubMethods.pulls.create.mock.calls[0]).toMatchSnapshot();
});
it("doesn't include ignored package that got a dependency update in the PR body", async () => {
let cwd = f.copy("ignored-package");
linkNodeModules(cwd);
mockedGithubMethods.search.issuesAndPullRequests.mockImplementationOnce(
() => ({ data: { items: [] } })
);
await writeChangesets(
[
{
releases: [
{
name: "ignored-package-pkg-b",
type: "minor",
},
],
summary: "Awesome feature",
},
],
cwd
);
await runVersion({
githubToken: "@@GITHUB_TOKEN",
cwd,
});
expect(mockedGithubMethods.pulls.create.mock.calls[0]).toMatchSnapshot();
});
});
+272
View File
@@ -0,0 +1,272 @@
import { exec } from "@actions/exec";
import * as github from "@actions/github";
import fs from "fs-extra";
import { getPackages, Package } from "@manypkg/get-packages";
import path from "path";
import * as semver from "semver";
import {
getChangelogEntry,
execWithOutput,
getChangedPackages,
sortTheThings,
getVersionsByDirectory,
} from "./utils";
import * as gitUtils from "./gitUtils";
import readChangesetState from "./readChangesetState";
const createRelease = async (
octokit: github.GitHub,
{ pkg, tagName }: { pkg: Package; tagName: string }
) => {
try {
let changelogFileName = path.join(pkg.dir, "CHANGELOG.md");
let changelog = await fs.readFile(changelogFileName, "utf8");
let changelogEntry = getChangelogEntry(changelog, pkg.packageJson.version);
if (!changelogEntry) {
// we can find a changelog but not the entry for this version
// if this is true, something has probably gone wrong
throw new Error(
`Could not find changelog entry for ${pkg.packageJson.name}@${pkg.packageJson.version}`
);
}
await octokit.repos.createRelease({
tag_name: tagName,
body: changelogEntry.content,
prerelease: pkg.packageJson.version.includes("-"),
...github.context.repo,
});
} catch (err) {
// if we can't find a changelog, the user has probably disabled changelogs
if (err.code !== "ENOENT") {
throw err;
}
}
};
type PublishOptions = {
script: string;
githubToken: string;
cwd?: string;
};
type PublishedPackage = { name: string; version: string };
type PublishResult =
| {
published: true;
publishedPackages: PublishedPackage[];
}
| {
published: false;
};
export async function runPublish({
script,
githubToken,
cwd = process.cwd(),
}: PublishOptions): Promise<PublishResult> {
let branch = github.context.ref.replace("refs/heads/", "");
let octokit = new github.GitHub(githubToken);
let [publishCommand, ...publishArgs] = script.split(/\s+/);
let changesetPublishOutput = await execWithOutput(
publishCommand,
publishArgs,
{ cwd }
);
await gitUtils.pullBranch(branch);
await gitUtils.push(branch, { includeTags: true });
let { packages, tool } = await getPackages(cwd);
let releasedPackages: Package[] = [];
if (tool !== "root") {
let newTagRegex = /New tag:\s+(@[^/]+\/[^@]+|[^/]+)@([^\s]+)/;
let packagesByName = new Map(packages.map((x) => [x.packageJson.name, x]));
for (let line of changesetPublishOutput.stdout.split("\n")) {
let match = line.match(newTagRegex);
if (match === null) {
continue;
}
let pkgName = match[1];
let pkg = packagesByName.get(pkgName);
if (pkg === undefined) {
throw new Error(
`Package "${pkgName}" not found.` +
"This is probably a bug in the action, please open an issue"
);
}
releasedPackages.push(pkg);
}
await Promise.all(
releasedPackages.map((pkg) =>
createRelease(octokit, {
pkg,
tagName: `${pkg.packageJson.name}@${pkg.packageJson.version}`,
})
)
);
} else {
if (packages.length === 0) {
throw new Error(
`No package found.` +
"This is probably a bug in the action, please open an issue"
);
}
const pkg = packages[0];
releasedPackages.push(pkg);
await createRelease(octokit, {
pkg,
tagName: `v${pkg.packageJson.version}`,
});
}
if (releasedPackages.length) {
return {
published: true,
publishedPackages: releasedPackages.map((pkg) => ({
name: pkg.packageJson.name,
version: pkg.packageJson.version,
})),
};
}
return { published: false };
}
type VersionOptions = {
script?: string;
githubToken: string;
cwd?: string;
prTitle?: string;
commitMessage?: string;
hasPublishScript?: boolean;
};
export async function runVersion({
script,
githubToken,
cwd = process.cwd(),
prTitle = "Version Packages",
commitMessage = "Version Packages",
hasPublishScript = false,
}: VersionOptions) {
let repo = `${github.context.repo.owner}/${github.context.repo.repo}`;
let branch = github.context.ref.replace("refs/heads/", "");
let versionBranch = `changeset-release/${branch}`;
let octokit = new github.GitHub(githubToken);
let { preState } = await readChangesetState(cwd);
await gitUtils.switchToMaybeExistingBranch(versionBranch);
await gitUtils.reset(github.context.sha);
let versionsByDirectory = await getVersionsByDirectory(cwd);
if (script) {
let [versionCommand, ...versionArgs] = script.split(/\s+/);
await exec(versionCommand, versionArgs, { cwd });
} else {
// technically
let changesetsCliPkgJson = await require(path.join(
cwd,
"node_modules",
"@changesets",
"cli",
"package.json"
));
let cmd = semver.lt(changesetsCliPkgJson.version, "2.0.0")
? "bump"
: "version";
await exec("node", ["./node_modules/@changesets/cli/bin.js", cmd], { cwd });
}
let searchQuery = `repo:${repo}+state:open+head:${versionBranch}+base:${branch}`;
let searchResultPromise = octokit.search.issuesAndPullRequests({
q: searchQuery,
});
let changedPackages = await getChangedPackages(cwd, versionsByDirectory);
let prBodyPromise = (async () => {
return (
`This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and ${
hasPublishScript
? `the packages will be published to npm automatically`
: `publish to npm yourself or [setup this action to publish automatically](https://github.com/changesets/action#with-publishing)`
}. If you're not ready to do a release yet, that's fine, whenever you add more changesets to ${branch}, this PR will be updated.
${
!!preState
? `
⚠️⚠️⚠️⚠️⚠️⚠️
\`${branch}\` is currently in **pre mode** so this branch has prereleases rather than normal releases. If you want to exit prereleases, run \`changeset pre exit\` on \`${branch}\`.
⚠️⚠️⚠️⚠️⚠️⚠️
`
: ""
}
# Releases
` +
(
await Promise.all(
changedPackages.map(async (pkg) => {
let changelogContents = await fs.readFile(
path.join(pkg.dir, "CHANGELOG.md"),
"utf8"
);
let entry = getChangelogEntry(
changelogContents,
pkg.packageJson.version
);
return {
highestLevel: entry.highestLevel,
private: !!pkg.packageJson.private,
content:
`## ${pkg.packageJson.name}@${pkg.packageJson.version}\n\n` +
entry.content,
};
})
)
)
.filter((x) => x)
.sort(sortTheThings)
.map((x) => x.content)
.join("\n ")
);
})();
const finalPrTitle = `${prTitle}${!!preState ? ` (${preState.tag})` : ""}`;
const finalCommitMessage = `${commitMessage}${
!!preState ? ` (${preState.tag})` : ""
}`;
await gitUtils.commitAll(finalCommitMessage);
await gitUtils.push(versionBranch, { force: true });
let searchResult = await searchResultPromise;
console.log(JSON.stringify(searchResult.data, null, 2));
if (searchResult.data.items.length === 0) {
console.log("creating pull request");
await octokit.pulls.create({
base: branch,
head: versionBranch,
title: finalPrTitle,
body: await prBodyPromise,
...github.context.repo,
});
} else {
octokit.pulls.update({
pull_number: searchResult.data.items[0].number,
title: finalPrTitle,
body: await prBodyPromise,
...github.context.repo,
});
console.log("pull request found");
}
}
+1 -34
View File
@@ -1,13 +1,4 @@
import {
getChangelogEntry,
BumpLevels,
getChangedPackages,
sortTheThings,
} from "./utils";
import tempy from "tempy";
import { exec } from "@actions/exec";
import * as fs from "fs-extra";
import path from "path";
import { getChangelogEntry, BumpLevels, sortTheThings } from "./utils";
let changelog = `# @keystone-alpha/email
@@ -88,30 +79,6 @@ test("it works", () => {
expect(entry.highestLevel).toBe(BumpLevels.patch);
});
test("getChangedPackages works", async () => {
let dir = tempy.directory();
await exec("git", ["init"], { cwd: dir });
await fs.writeJson(path.join(dir, "package.json"), {
name: "some-package",
version: "1.0.0",
});
await fs.writeFile(path.join(dir, "CHANGELOG.md"), "some content");
await exec("git", ["add", "."], { cwd: dir });
await exec("git", ["commit", "-m", "initial commit"], { cwd: dir });
let emptyWorkspaces = await getChangedPackages(dir);
expect(emptyWorkspaces).toEqual([]);
let workspace = {
name: "some-package",
version: "2.0.0",
};
await fs.writeFile(path.join(dir, "CHANGELOG.md"), "other content");
await fs.writeJson(path.join(dir, "package.json"), workspace);
let workspaces = await getChangedPackages(dir);
expect(workspaces).toEqual([
{ name: "some-package", dir, config: workspace },
]);
});
test("it sorts the things right", () => {
let things = [
{
+13 -11
View File
@@ -5,7 +5,6 @@ import remarkStringify from "remark-stringify";
import mdastToString from "mdast-util-to-string";
import { exec } from "@actions/exec";
import { getPackages, Package } from "@manypkg/get-packages";
import path from "path";
export const BumpLevels = {
dep: 0,
@@ -14,19 +13,22 @@ export const BumpLevels = {
major: 3,
} as const;
export async function getChangedPackages(cwd: string) {
export async function getVersionsByDirectory(cwd: string) {
let { packages } = await getPackages(cwd);
let packagesByDirectory = new Map(packages.map((x) => [x.dir, x]));
return new Map(packages.map((x) => [x.dir, x.packageJson.version]));
}
let output = await execWithOutput("git", ["diff", "--name-only"], { cwd });
let names = output.stdout.split("\n");
export async function getChangedPackages(
cwd: string,
previousVersions: Map<string, string>
) {
let { packages } = await getPackages(cwd);
let changedPackages = new Set<Package>();
for (let name of names) {
if (name === "") continue;
let dirname = path.resolve(cwd, path.dirname(name));
let workspace = packagesByDirectory.get(dirname);
if (workspace !== undefined) {
changedPackages.add(workspace);
for (let pkg of packages) {
const previousVersion = previousVersions.get(pkg.dir);
if (previousVersion !== pkg.packageJson.version) {
changedPackages.add(pkg);
}
}
+17
View File
@@ -0,0 +1,17 @@
type Opts = {
glob?: string | Array<string>;
root?: string;
cleanup?: boolean;
};
declare module "fixturez" {
export default function (
cwd: string,
opts?: Opts
): {
find: (a: string) => string;
temp: () => string;
copy: (a: string) => string;
cleanup: () => any;
};
}
+717 -102
View File
File diff suppressed because it is too large Load Diff