Skip to content

Commit

Permalink
Make using GitHub API Optional
Browse files Browse the repository at this point in the history
Change this to a minor version bump,
with a new feature that allows for using the
GitHub API to create tags and commits.
  • Loading branch information
s0 committed Nov 2, 2024
1 parent 67a10a1 commit 893ba16
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 54 deletions.
12 changes: 6 additions & 6 deletions .changeset/green-dogs-change.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
"@changesets/action": major
"@changesets/action": minor
---

Start using GitHub API to push tags and commits to repos
Introduce a new input commitUsingApi that allows pushing tags and commits
using the GitHub API instead of the git CLI.

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.
When used, this means means that all tags and commits will be attributed
to the user whose GITHUB_TOKEN is used,
and also signed using GitHub's internal GPG key.
5 changes: 0 additions & 5 deletions .changeset/ninety-poems-explode.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/thick-jars-chew.md

This file was deleted.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This action for [Changesets](https://github.com/atlassian/changesets) creates a
- title - The pull request title. Default to `Version Packages`
- setupGitUser - Sets up the git user for commits as `"github-actions[bot]"`. Default to `true`
- createGithubReleases - A boolean value to indicate whether to create Github releases after `publish` or not. Default to `true`
- commitUsingApi - A boolean value to indicate whether to use the GitHub API to push changes or not, so changes are GPG-signed. Default to `false`
- cwd - Changes node's `process.cwd()` if the project is not located on the root. Default to `process.cwd()`

### Outputs
Expand Down
7 changes: 7 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ inputs:
description: "A boolean value to indicate whether to create Github releases after `publish` or not"
required: false
default: true
commitUsingApi:
description: >
A boolean value to indicate whether to push changes via Github API or not,
this will mean all commits and tags are signed using GitHub's GPG key,
and attributed to the user or app who owns the GITHUB_TOKEN
required: false
default: false
branch:
description: Sets the branch in which the action will run. Default to `github.ref_name` if not provided
required: false
Expand Down
51 changes: 50 additions & 1 deletion src/gitUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,53 @@ 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;
};
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ const getOptionalInput = (name: string) => core.getInput(name) || undefined;
await gitUtils.setupUser();
}

const commitUsingApi = core.getBooleanInput("commitUsingApi");

core.info("setting GitHub credentials");
await fs.writeFile(
`${process.env.HOME}/.netrc`,
Expand Down Expand Up @@ -88,6 +90,7 @@ const getOptionalInput = (name: string) => core.getInput(name) || undefined;
script: publishScript,
githubToken,
createGithubReleases: core.getBooleanInput("createGithubReleases"),
commitUsingApi
});

if (result.published) {
Expand All @@ -109,6 +112,7 @@ const getOptionalInput = (name: string) => core.getInput(name) || undefined;
prTitle: getOptionalInput("title"),
commitMessage: getOptionalInput("commit"),
hasPublishScript,
commitUsingApi,
branch: getOptionalInput("branch"),
});

Expand Down
100 changes: 63 additions & 37 deletions src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ type PublishOptions = {
script: string;
githubToken: string;
createGithubReleases: boolean;
commitUsingApi: boolean;
cwd?: string;
};

Expand All @@ -119,6 +120,7 @@ export async function runPublish({
script,
githubToken,
createGithubReleases,
commitUsingApi,
cwd = process.cwd(),
}: PublishOptions): Promise<PublishResult> {
const octokit = setupOctokit(githubToken);
Expand All @@ -131,6 +133,10 @@ export async function runPublish({
{ cwd }
);

if (!commitUsingApi) {
await gitUtils.pushTags();
}

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

Expand Down Expand Up @@ -158,21 +164,21 @@ export async function runPublish({
await Promise.all(
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 });
if (commitUsingApi) {
// Tag will usually 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}`);
});
}
await createRelease(octokit, { pkg, tagName });
})
);
}
Expand All @@ -191,20 +197,22 @@ export async function runPublish({

if (match) {
releasedPackages.push(pkg);
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) {
const tagName = `v${pkg.packageJson.version}`;
if (commitUsingApi) {
// 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}`);
});
}
await createRelease(octokit, { pkg, tagName });
}
break;
Expand Down Expand Up @@ -320,6 +328,7 @@ type VersionOptions = {
commitMessage?: string;
hasPublishScript?: boolean;
prBodyMaxCharacters?: number;
commitUsingApi: boolean;
branch?: string;
};

Expand All @@ -335,6 +344,7 @@ export async function runVersion({
commitMessage = "Version Packages",
hasPublishScript = false,
prBodyMaxCharacters = MAX_CHARACTERS_PER_MESSAGE,
commitUsingApi,
branch,
}: VersionOptions): Promise<RunVersionResult> {
const octokit = setupOctokit(githubToken);
Expand All @@ -345,6 +355,11 @@ export async function runVersion({

let { preState } = await readChangesetState(cwd);

if (!commitUsingApi) {
await gitUtils.switchToMaybeExistingBranch(versionBranch);
await gitUtils.reset(github.context.sha);
}

let versionsByDirectory = await getVersionsByDirectory(cwd);

if (script) {
Expand Down Expand Up @@ -389,16 +404,27 @@ export async function runVersion({
!!preState ? ` (${preState.tag})` : ""
}`;

await commitChangesFromRepo({
octokit,
...github.context.repo,
branch: versionBranch,
message: finalCommitMessage,
base: {
commit: github.context.sha,
},
force: true,
});
if (commitUsingApi) {
await commitChangesFromRepo({
octokit,
...github.context.repo,
branch: versionBranch,
message: finalCommitMessage,
base: {
commit: github.context.sha,
},
force: true,
});
} else {
// project with `commit: true` setting could have already committed files
if (!(await gitUtils.checkIfClean())) {
await gitUtils.commitAll(finalCommitMessage);
}
}

if (!commitUsingApi) {
await gitUtils.push(versionBranch, { force: true });
}

let existingPullRequests = await existingPullRequestsPromise;
core.info(JSON.stringify(existingPullRequests.data, null, 2));
Expand Down

0 comments on commit 893ba16

Please sign in to comment.