Skip to content

Commit 06a6067

Browse files
committed
Make using GitHub API Optional
Change this to a minor version bump, with a new feature that allows for using the GitHub API to create tags and commits.
1 parent 67a10a1 commit 06a6067

8 files changed

+123
-54
lines changed

.changeset/green-dogs-change.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
---
2-
"@changesets/action": major
2+
"@changesets/action": minor
33
---
44

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

7-
Rather than use local git commands to push changes to GitHub,
8-
this action now uses the GitHub API directly,
9-
which means that all tags and commits will be attributed to the user whose
10-
GITHUB_TOKEN is used, and signed.
8+
When used, this means means that all tags and commits will be attributed
9+
to the user whose GITHUB_TOKEN is used,
10+
and also signed using GitHub's internal GPG key.

.changeset/ninety-poems-explode.md

-5
This file was deleted.

.changeset/thick-jars-chew.md

-5
This file was deleted.

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This action for [Changesets](https://github.com/atlassian/changesets) creates a
1212
- title - The pull request title. Default to `Version Packages`
1313
- setupGitUser - Sets up the git user for commits as `"github-actions[bot]"`. Default to `true`
1414
- createGithubReleases - A boolean value to indicate whether to create Github releases after `publish` or not. Default to `true`
15+
- 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`
1516
- cwd - Changes node's `process.cwd()` if the project is not located on the root. Default to `process.cwd()`
1617

1718
### Outputs

action.yml

+7
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ inputs:
2828
description: "A boolean value to indicate whether to create Github releases after `publish` or not"
2929
required: false
3030
default: true
31+
commitUsingApi:
32+
description: >
33+
A boolean value to indicate whether to push changes via Github API or not,
34+
this will mean all commits and tags are signed using GitHub's GPG key,
35+
and attributed to the user or app who owns the GITHUB_TOKEN
36+
required: false
37+
default: false
3138
branch:
3239
description: Sets the branch in which the action will run. Default to `github.ref_name` if not provided
3340
required: false

src/gitUtils.ts

+50-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,53 @@ export const setupUser = async () => {
1111
"user.email",
1212
`"github-actions[bot]@users.noreply.github.com"`,
1313
]);
14-
};
14+
};
15+
16+
export const pullBranch = async (branch: string) => {
17+
await exec("git", ["pull", "origin", branch]);
18+
};
19+
20+
export const push = async (
21+
branch: string,
22+
{ force }: { force?: boolean } = {}
23+
) => {
24+
await exec(
25+
"git",
26+
["push", "origin", `HEAD:${branch}`, force && "--force"].filter<string>(
27+
Boolean as any
28+
)
29+
);
30+
};
31+
32+
export const pushTags = async () => {
33+
await exec("git", ["push", "origin", "--tags"]);
34+
};
35+
36+
export const switchToMaybeExistingBranch = async (branch: string) => {
37+
let { stderr } = await getExecOutput("git", ["checkout", branch], {
38+
ignoreReturnCode: true,
39+
});
40+
let isCreatingBranch = !stderr
41+
.toString()
42+
.includes(`Switched to a new branch '${branch}'`);
43+
if (isCreatingBranch) {
44+
await exec("git", ["checkout", "-b", branch]);
45+
}
46+
};
47+
48+
export const reset = async (
49+
pathSpec: string,
50+
mode: "hard" | "soft" | "mixed" = "hard"
51+
) => {
52+
await exec("git", ["reset", `--${mode}`, pathSpec]);
53+
};
54+
55+
export const commitAll = async (message: string) => {
56+
await exec("git", ["add", "."]);
57+
await exec("git", ["commit", "-m", message]);
58+
};
59+
60+
export const checkIfClean = async (): Promise<boolean> => {
61+
const { stdout } = await getExecOutput("git", ["status", "--porcelain"]);
62+
return !stdout.length;
63+
};

src/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ const getOptionalInput = (name: string) => core.getInput(name) || undefined;
2727
await gitUtils.setupUser();
2828
}
2929

30+
const commitUsingApi = core.getBooleanInput("commitUsingApi");
31+
3032
core.info("setting GitHub credentials");
3133
await fs.writeFile(
3234
`${process.env.HOME}/.netrc`,
@@ -88,6 +90,7 @@ const getOptionalInput = (name: string) => core.getInput(name) || undefined;
8890
script: publishScript,
8991
githubToken,
9092
createGithubReleases: core.getBooleanInput("createGithubReleases"),
93+
commitUsingApi
9194
});
9295

9396
if (result.published) {
@@ -109,6 +112,7 @@ const getOptionalInput = (name: string) => core.getInput(name) || undefined;
109112
prTitle: getOptionalInput("title"),
110113
commitMessage: getOptionalInput("commit"),
111114
hasPublishScript,
115+
commitUsingApi,
112116
branch: getOptionalInput("branch"),
113117
});
114118

src/run.ts

+55-37
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ type PublishOptions = {
101101
script: string;
102102
githubToken: string;
103103
createGithubReleases: boolean;
104+
commitUsingApi: boolean;
104105
cwd?: string;
105106
};
106107

@@ -119,6 +120,7 @@ export async function runPublish({
119120
script,
120121
githubToken,
121122
createGithubReleases,
123+
commitUsingApi,
122124
cwd = process.cwd(),
123125
}: PublishOptions): Promise<PublishResult> {
124126
const octokit = setupOctokit(githubToken);
@@ -158,21 +160,21 @@ export async function runPublish({
158160
await Promise.all(
159161
releasedPackages.map(async (pkg) => {
160162
const tagName = `${pkg.packageJson.name}@${pkg.packageJson.version}`;
161-
// Tag will only be created locally,
162-
// Create it using the GitHub API so it's signed.
163-
await octokit.rest.git
164-
.createRef({
165-
...github.context.repo,
166-
ref: `refs/tags/${tagName}`,
167-
sha: github.context.sha,
168-
})
169-
.catch((err) => {
170-
// Assuming tag was manually pushed in custom publish script
171-
core.warning(`Failed to create tag ${tagName}: ${err.message}`);
172-
});
173-
if (createGithubReleases) {
174-
await createRelease(octokit, { pkg, tagName });
163+
if (commitUsingApi) {
164+
// Tag will usually only be created locally,
165+
// Create it using the GitHub API so it's signed.
166+
await octokit.rest.git
167+
.createRef({
168+
...github.context.repo,
169+
ref: `refs/tags/${tagName}`,
170+
sha: github.context.sha,
171+
})
172+
.catch((err) => {
173+
// Assuming tag was manually pushed in custom publish script
174+
core.warning(`Failed to create tag ${tagName}: ${err.message}`);
175+
});
175176
}
177+
await createRelease(octokit, { pkg, tagName });
176178
})
177179
);
178180
}
@@ -191,20 +193,22 @@ export async function runPublish({
191193

192194
if (match) {
193195
releasedPackages.push(pkg);
194-
const tagName = `v${pkg.packageJson.version}`;
195-
// Tag will only be created locally,
196-
// Create it using the GitHub API so it's signed.
197-
await octokit.rest.git
198-
.createRef({
199-
...github.context.repo,
200-
ref: `refs/tags/${tagName}`,
201-
sha: github.context.sha,
202-
})
203-
.catch((err) => {
204-
// Assuming tag was manually pushed in custom publish script
205-
core.warning(`Failed to create tag ${tagName}: ${err.message}`);
206-
});
207196
if (createGithubReleases) {
197+
const tagName = `v${pkg.packageJson.version}`;
198+
if (commitUsingApi) {
199+
// Tag will only be created locally,
200+
// Create it using the GitHub API so it's signed.
201+
await octokit.rest.git
202+
.createRef({
203+
...github.context.repo,
204+
ref: `refs/tags/${tagName}`,
205+
sha: github.context.sha,
206+
})
207+
.catch((err) => {
208+
// Assuming tag was manually pushed in custom publish script
209+
core.warning(`Failed to create tag ${tagName}: ${err.message}`);
210+
});
211+
}
208212
await createRelease(octokit, { pkg, tagName });
209213
}
210214
break;
@@ -320,6 +324,7 @@ type VersionOptions = {
320324
commitMessage?: string;
321325
hasPublishScript?: boolean;
322326
prBodyMaxCharacters?: number;
327+
commitUsingApi: boolean;
323328
branch?: string;
324329
};
325330

@@ -335,6 +340,7 @@ export async function runVersion({
335340
commitMessage = "Version Packages",
336341
hasPublishScript = false,
337342
prBodyMaxCharacters = MAX_CHARACTERS_PER_MESSAGE,
343+
commitUsingApi,
338344
branch,
339345
}: VersionOptions): Promise<RunVersionResult> {
340346
const octokit = setupOctokit(githubToken);
@@ -345,6 +351,11 @@ export async function runVersion({
345351

346352
let { preState } = await readChangesetState(cwd);
347353

354+
if (!commitUsingApi) {
355+
await gitUtils.switchToMaybeExistingBranch(versionBranch);
356+
await gitUtils.reset(github.context.sha);
357+
}
358+
348359
let versionsByDirectory = await getVersionsByDirectory(cwd);
349360

350361
if (script) {
@@ -389,16 +400,23 @@ export async function runVersion({
389400
!!preState ? ` (${preState.tag})` : ""
390401
}`;
391402

392-
await commitChangesFromRepo({
393-
octokit,
394-
...github.context.repo,
395-
branch: versionBranch,
396-
message: finalCommitMessage,
397-
base: {
398-
commit: github.context.sha,
399-
},
400-
force: true,
401-
});
403+
if (commitUsingApi) {
404+
await commitChangesFromRepo({
405+
octokit,
406+
...github.context.repo,
407+
branch: versionBranch,
408+
message: finalCommitMessage,
409+
base: {
410+
commit: github.context.sha,
411+
},
412+
force: true,
413+
});
414+
} else {
415+
// project with `commit: true` setting could have already committed files
416+
if (!(await gitUtils.checkIfClean())) {
417+
await gitUtils.commitAll(finalCommitMessage);
418+
}
419+
}
402420

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

0 commit comments

Comments
 (0)