Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce an option to use the GitHub API to commit changes, for GPG #391

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/green-dogs-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@changesets/action": major
---

Start using GitHub API to push tags and commits to repos

Rather than use local git commands to push changes to GitHub,
this action now uses the GitHub API directly,
which means that all tags and commits will be attributed to the user whose
GITHUB_TOKEN is used, and signed.
5 changes: 5 additions & 0 deletions .changeset/ninety-poems-explode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@changesets/action": patch
---

Update to latest version of ghcommit
5 changes: 5 additions & 0 deletions .changeset/thick-jars-chew.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@changesets/action": minor
---

Handle custom publish commands more gracefully
17 changes: 9 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@
"main": "dist/index.js",
"license": "MIT",
"devDependencies": {
"@changesets/changelog-github": "^0.4.2",
"@changesets/cli": "^2.20.0",
"@changesets/write": "^0.1.6",
"@vercel/ncc": "^0.36.1",
"fixturez": "^1.1.0",
"prettier": "^2.0.5",
"typescript": "^5.0.4",
"@babel/core": "^7.13.10",
"@babel/preset-env": "^7.13.10",
"@babel/preset-typescript": "^7.13.0",
"@changesets/changelog-github": "^0.4.2",
"@changesets/cli": "^2.20.0",
"@changesets/write": "^0.1.6",
"@types/fs-extra": "^8.0.0",
"@types/jest": "^29.5.1",
"@types/node": "^20.11.17",
"@types/semver": "^7.5.0",
"@vercel/ncc": "^0.36.1",
"babel-jest": "^29.5.0",
"fixturez": "^1.1.0",
"husky": "^3.0.3",
"jest": "^29.5.0"
"jest": "^29.5.0",
"prettier": "^2.0.5",
"typescript": "^5.0.4"
},
"scripts": {
"build": "ncc build src/index.ts -o dist --transpile-only --minify",
Expand All @@ -41,6 +41,7 @@
"@changesets/read": "^0.5.3",
"@manypkg/get-packages": "^1.1.3",
"@octokit/plugin-throttling": "^5.2.1",
"@s0/ghcommit": "^1.2.0",
"fs-extra": "^8.1.0",
"mdast-util-to-string": "^1.0.6",
"remark-parse": "^7.0.1",
Expand Down
51 changes: 1 addition & 50 deletions src/gitUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,53 +11,4 @@ export const setupUser = async () => {
"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 }: { force?: boolean } = {}
) => {
await exec(
"git",
["push", "origin", `HEAD:${branch}`, force && "--force"].filter<string>(
Boolean as any
)
);
};

export const pushTags = async () => {
await exec("git", ["push", "origin", "--tags"]);
};

export const switchToMaybeExistingBranch = async (branch: string) => {
let { stderr } = await getExecOutput("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]);
};

export const checkIfClean = async (): Promise<boolean> => {
const { stdout } = await getExecOutput("git", ["status", "--porcelain"]);
return !stdout.length;
};
};
1 change: 1 addition & 0 deletions src/run.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ jest.mock("@actions/github/lib/utils", () => ({
getOctokitOptions: jest.fn(),
}));
jest.mock("./gitUtils");
jest.mock("@s0/ghcommit/git");

let mockedGithubMethods = {
pulls: {
Expand Down
70 changes: 46 additions & 24 deletions src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import * as gitUtils from "./gitUtils";
import readChangesetState from "./readChangesetState";
import resolveFrom from "resolve-from";
import { throttling } from "@octokit/plugin-throttling";
import { commitChangesFromRepo } from "@s0/ghcommit/git";

// GitHub Issues/PRs messages have a max size limit on the
// message body payload.
Expand Down Expand Up @@ -130,8 +131,6 @@ export async function runPublish({
{ cwd }
);

await gitUtils.pushTags();

let { packages, tool } = await getPackages(cwd);
let releasedPackages: Package[] = [];

Expand All @@ -157,12 +156,24 @@ export async function runPublish({

if (createGithubReleases) {
await Promise.all(
releasedPackages.map((pkg) =>
createRelease(octokit, {
pkg,
tagName: `${pkg.packageJson.name}@${pkg.packageJson.version}`,
})
)
releasedPackages.map(async (pkg) => {
const tagName = `${pkg.packageJson.name}@${pkg.packageJson.version}`;
// Tag will only be created locally,
// Create it using the GitHub API so it's signed.
await octokit.rest.git
.createRef({
...github.context.repo,
ref: `refs/tags/${tagName}`,
sha: github.context.sha,
})
.catch((err) => {
// Assuming tag was manually pushed in custom publish script
core.warning(`Failed to create tag ${tagName}: ${err.message}`);
});
if (createGithubReleases) {
await createRelease(octokit, { pkg, tagName });
}
})
);
}
} else {
Expand All @@ -180,11 +191,21 @@ export async function runPublish({

if (match) {
releasedPackages.push(pkg);
if (createGithubReleases) {
await createRelease(octokit, {
pkg,
tagName: `v${pkg.packageJson.version}`,
const tagName = `v${pkg.packageJson.version}`;
// Tag will only be created locally,
// Create it using the GitHub API so it's signed.
await octokit.rest.git
.createRef({
...github.context.repo,
ref: `refs/tags/${tagName}`,
sha: github.context.sha,
})
.catch((err) => {
// Assuming tag was manually pushed in custom publish script
core.warning(`Failed to create tag ${tagName}: ${err.message}`);
});
if (createGithubReleases) {
await createRelease(octokit, { pkg, tagName });
}
break;
}
Expand Down Expand Up @@ -324,9 +345,6 @@ export async function runVersion({

let { preState } = await readChangesetState(cwd);

await gitUtils.switchToMaybeExistingBranch(versionBranch);
await gitUtils.reset(github.context.sha);

let versionsByDirectory = await getVersionsByDirectory(cwd);

if (script) {
Expand Down Expand Up @@ -367,16 +385,20 @@ export async function runVersion({
);

const finalPrTitle = `${prTitle}${!!preState ? ` (${preState.tag})` : ""}`;
const finalCommitMessage = `${commitMessage}${
!!preState ? ` (${preState.tag})` : ""
}`;

// project with `commit: true` setting could have already committed files
if (!(await gitUtils.checkIfClean())) {
const finalCommitMessage = `${commitMessage}${
!!preState ? ` (${preState.tag})` : ""
}`;
await gitUtils.commitAll(finalCommitMessage);
}

await gitUtils.push(versionBranch, { force: true });
await commitChangesFromRepo({
octokit,
...github.context.repo,
branch: versionBranch,
message: finalCommitMessage,
base: {
commit: github.context.sha,
},
force: true,
});

let existingPullRequests = await existingPullRequestsPromise;
core.info(JSON.stringify(existingPullRequests.data, null, 2));
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */

/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"moduleResolution": "nodenext", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
Expand Down
Loading