mirror of
https://github.com/changesets/action
synced 2026-10-26 16:39:56 +08:00
Fixed an issue with ignored package being included in PR body incorrectly (#38)
This commit is contained in:
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
Vendored
+781
-1191
File diff suppressed because one or more lines are too long
@@ -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
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
]
|
||||
`;
|
||||
@@ -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]);
|
||||
};
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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
@@ -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
@@ -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,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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+17
@@ -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;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user