Skip to content

Commit 893ba16

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 893ba16

8 files changed

+131
-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

+63-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);
@@ -131,6 +133,10 @@ export async function runPublish({
131133
{ cwd }
132134
);
133135

136+
if (!commitUsingApi) {
137+
await gitUtils.pushTags();
138+
}
139+
134140
let { packages, tool } = await getPackages(cwd);
135141
let releasedPackages: Package[] = [];
136142

@@ -158,21 +164,21 @@ export async function runPublish({
158164
await Promise.all(
159165
releasedPackages.map(async (pkg) => {
160166
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 });
167+
if (commitUsingApi) {
168+
// Tag will usually only be created locally,
169+
// Create it using the GitHub API so it's signed.
170+
await octokit.rest.git
171+
.createRef({
172+
...github.context.repo,
173+
ref: `refs/tags/${tagName}`,
174+
sha: github.context.sha,
175+
})
176+
.catch((err) => {
177+
// Assuming tag was manually pushed in custom publish script
178+
core.warning(`Failed to create tag ${tagName}: ${err.message}`);
179+
});
175180
}
181+
await createRelease(octokit, { pkg, tagName });
176182
})
177183
);
178184
}
@@ -191,20 +197,22 @@ export async function runPublish({
191197

192198
if (match) {
193199
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-
});
207200
if (createGithubReleases) {
201+
const tagName = `v${pkg.packageJson.version}`;
202+
if (commitUsingApi) {
203+
// Tag will only be created locally,
204+
// Create it using the GitHub API so it's signed.
205+
await octokit.rest.git
206+
.createRef({
207+
...github.context.repo,
208+
ref: `refs/tags/${tagName}`,
209+
sha: github.context.sha,
210+
})
211+
.catch((err) => {
212+
// Assuming tag was manually pushed in custom publish script
213+
core.warning(`Failed to create tag ${tagName}: ${err.message}`);
214+
});
215+
}
208216
await createRelease(octokit, { pkg, tagName });
209217
}
210218
break;
@@ -320,6 +328,7 @@ type VersionOptions = {
320328
commitMessage?: string;
321329
hasPublishScript?: boolean;
322330
prBodyMaxCharacters?: number;
331+
commitUsingApi: boolean;
323332
branch?: string;
324333
};
325334

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

346356
let { preState } = await readChangesetState(cwd);
347357

358+
if (!commitUsingApi) {
359+
await gitUtils.switchToMaybeExistingBranch(versionBranch);
360+
await gitUtils.reset(github.context.sha);
361+
}
362+
348363
let versionsByDirectory = await getVersionsByDirectory(cwd);
349364

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

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-
});
407+
if (commitUsingApi) {
408+
await commitChangesFromRepo({
409+
octokit,
410+
...github.context.repo,
411+
branch: versionBranch,
412+
message: finalCommitMessage,
413+
base: {
414+
commit: github.context.sha,
415+
},
416+
force: true,
417+
});
418+
} else {
419+
// project with `commit: true` setting could have already committed files
420+
if (!(await gitUtils.checkIfClean())) {
421+
await gitUtils.commitAll(finalCommitMessage);
422+
}
423+
}
424+
425+
if (!commitUsingApi) {
426+
await gitUtils.push(versionBranch, { force: true });
427+
}
402428

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

0 commit comments

Comments
 (0)