Skip to content

Commit 796b88d

Browse files
authored
feat: allow repositories input to be comma or newline-separated (#169)
Resolves #106 - Fixes the parsing to cope with whitespace in the input string. - Allows the input to be comma or newline-separated. (I've done this for all array-type inputs in my own actions, but I'm happy to remove this if you only want to support comma-separated.) - Added tests for parsing comma and newline-separated inputs.
1 parent 3378cda commit 796b88d

10 files changed

+75
-37
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,9 @@ jobs:
163163
app-id: ${{ vars.APP_ID }}
164164
private-key: ${{ secrets.PRIVATE_KEY }}
165165
owner: ${{ github.repository_owner }}
166-
repositories: "repo1,repo2"
166+
repositories: |
167+
repo1
168+
repo2
167169
- uses: peter-evans/create-or-update-comment@v3
168170
with:
169171
token: ${{ steps.app-token.outputs.token }}
@@ -302,7 +304,7 @@ steps:
302304

303305
### `repositories`
304306

305-
**Optional:** Comma-separated list of repositories to grant access to.
307+
**Optional:** Comma or newline-separated list of repositories to grant access to.
306308

307309
> [!NOTE]
308310
> If `owner` is set and `repositories` is empty, access will be scoped to all repositories in the provided repository owner's installation. If `owner` and `repositories` are empty, access will be scoped to only the current repository.

action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ inputs:
2323
description: "The owner of the GitHub App installation (defaults to current repository owner)"
2424
required: false
2525
repositories:
26-
description: "Repositories to install the GitHub App on (defaults to current repository if owner is unset)"
26+
description: "Comma or newline-separated list of repositories to install the GitHub App on (defaults to current repository if owner is unset)"
2727
required: false
2828
skip-token-revoke:
2929
description: "If truthy, the token will not be revoked when the current job is complete"

dist/main.cjs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39700,33 +39700,35 @@ async function pRetry(input, options) {
3970039700
// lib/main.js
3970139701
async function main(appId2, privateKey2, owner2, repositories2, core3, createAppAuth2, request2, skipTokenRevoke2) {
3970239702
let parsedOwner = "";
39703-
let parsedRepositoryNames = "";
39704-
if (!owner2 && !repositories2) {
39705-
[parsedOwner, parsedRepositoryNames] = String(
39703+
let parsedRepositoryNames = [];
39704+
if (!owner2 && repositories2.length === 0) {
39705+
const [owner3, repo] = String(
3970639706
process.env.GITHUB_REPOSITORY
3970739707
).split("/");
39708+
parsedOwner = owner3;
39709+
parsedRepositoryNames = [repo];
3970839710
core3.info(
39709-
`owner and repositories not set, creating token for the current repository ("${parsedRepositoryNames}")`
39711+
`owner and repositories not set, creating token for the current repository ("${repo}")`
3971039712
);
3971139713
}
39712-
if (owner2 && !repositories2) {
39714+
if (owner2 && repositories2.length === 0) {
3971339715
parsedOwner = owner2;
3971439716
core3.info(
3971539717
`repositories not set, creating token for all repositories for given owner "${owner2}"`
3971639718
);
3971739719
}
39718-
if (!owner2 && repositories2) {
39720+
if (!owner2 && repositories2.length > 0) {
3971939721
parsedOwner = String(process.env.GITHUB_REPOSITORY_OWNER);
3972039722
parsedRepositoryNames = repositories2;
3972139723
core3.info(
39722-
`owner not set, creating owner for given repositories "${repositories2}" in current owner ("${parsedOwner}")`
39724+
`owner not set, creating owner for given repositories "${repositories2.join(",")}" in current owner ("${parsedOwner}")`
3972339725
);
3972439726
}
39725-
if (owner2 && repositories2) {
39727+
if (owner2 && repositories2.length > 0) {
3972639728
parsedOwner = owner2;
3972739729
parsedRepositoryNames = repositories2;
3972839730
core3.info(
39729-
`owner and repositories set, creating token for repositories "${repositories2}" owned by "${owner2}"`
39731+
`owner and repositories set, creating token for repositories "${repositories2.join(",")}" owned by "${owner2}"`
3973039732
);
3973139733
}
3973239734
const auth5 = createAppAuth2({
@@ -39735,11 +39737,11 @@ async function main(appId2, privateKey2, owner2, repositories2, core3, createApp
3973539737
request: request2
3973639738
});
3973739739
let authentication, installationId, appSlug;
39738-
if (parsedRepositoryNames) {
39740+
if (parsedRepositoryNames.length > 0) {
3973939741
({ authentication, installationId, appSlug } = await pRetry(() => getTokenFromRepository(request2, auth5, parsedOwner, parsedRepositoryNames), {
3974039742
onFailedAttempt: (error) => {
3974139743
core3.info(
39742-
`Failed to create token for "${parsedRepositoryNames}" (attempt ${error.attemptNumber}): ${error.message}`
39744+
`Failed to create token for "${parsedRepositoryNames.join(",")}" (attempt ${error.attemptNumber}): ${error.message}`
3974339745
);
3974439746
},
3974539747
retries: 3
@@ -39789,15 +39791,15 @@ async function getTokenFromOwner(request2, auth5, parsedOwner) {
3978939791
async function getTokenFromRepository(request2, auth5, parsedOwner, parsedRepositoryNames) {
3979039792
const response = await request2("GET /repos/{owner}/{repo}/installation", {
3979139793
owner: parsedOwner,
39792-
repo: parsedRepositoryNames.split(",")[0],
39794+
repo: parsedRepositoryNames[0],
3979339795
request: {
3979439796
hook: auth5.hook
3979539797
}
3979639798
});
3979739799
const authentication = await auth5({
3979839800
type: "installation",
3979939801
installationId: response.data.id,
39800-
repositoryNames: parsedRepositoryNames.split(",")
39802+
repositoryNames: parsedRepositoryNames
3980139803
});
3980239804
const installationId = response.data.id;
3980339805
const appSlug = response.data["app_slug"];
@@ -39847,7 +39849,7 @@ if (!privateKey) {
3984739849
throw new Error("Input required and not supplied: private-key");
3984839850
}
3984939851
var owner = import_core2.default.getInput("owner");
39850-
var repositories = import_core2.default.getInput("repositories");
39852+
var repositories = import_core2.default.getInput("repositories").split(/[\n,]+/).map((s) => s.trim()).filter((x) => x !== "");
3985139853
var skipTokenRevoke = Boolean(
3985239854
import_core2.default.getInput("skip-token-revoke") || import_core2.default.getInput("skip_token_revoke")
3985339855
);

lib/main.js

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import pRetry from "p-retry";
55
* @param {string} appId
66
* @param {string} privateKey
77
* @param {string} owner
8-
* @param {string} repositories
8+
* @param {string[]} repositories
99
* @param {import("@actions/core")} core
1010
* @param {import("@octokit/auth-app").createAppAuth} createAppAuth
1111
* @param {import("@octokit/request").request} request
@@ -22,21 +22,23 @@ export async function main(
2222
skipTokenRevoke
2323
) {
2424
let parsedOwner = "";
25-
let parsedRepositoryNames = "";
25+
let parsedRepositoryNames = [];
2626

2727
// If neither owner nor repositories are set, default to current repository
28-
if (!owner && !repositories) {
29-
[parsedOwner, parsedRepositoryNames] = String(
28+
if (!owner && repositories.length === 0) {
29+
const [owner, repo] = String(
3030
process.env.GITHUB_REPOSITORY
3131
).split("/");
32+
parsedOwner = owner;
33+
parsedRepositoryNames = [repo];
3234

3335
core.info(
34-
`owner and repositories not set, creating token for the current repository ("${parsedRepositoryNames}")`
36+
`owner and repositories not set, creating token for the current repository ("${repo}")`
3537
);
3638
}
3739

3840
// If only an owner is set, default to all repositories from that owner
39-
if (owner && !repositories) {
41+
if (owner && repositories.length === 0) {
4042
parsedOwner = owner;
4143

4244
core.info(
@@ -45,22 +47,22 @@ export async function main(
4547
}
4648

4749
// If repositories are set, but no owner, default to `GITHUB_REPOSITORY_OWNER`
48-
if (!owner && repositories) {
50+
if (!owner && repositories.length > 0) {
4951
parsedOwner = String(process.env.GITHUB_REPOSITORY_OWNER);
5052
parsedRepositoryNames = repositories;
5153

5254
core.info(
53-
`owner not set, creating owner for given repositories "${repositories}" in current owner ("${parsedOwner}")`
55+
`owner not set, creating owner for given repositories "${repositories.join(',')}" in current owner ("${parsedOwner}")`
5456
);
5557
}
5658

5759
// If both owner and repositories are set, use those values
58-
if (owner && repositories) {
60+
if (owner && repositories.length > 0) {
5961
parsedOwner = owner;
6062
parsedRepositoryNames = repositories;
6163

6264
core.info(
63-
`owner and repositories set, creating token for repositories "${repositories}" owned by "${owner}"`
65+
`owner and repositories set, creating token for repositories "${repositories.join(',')}" owned by "${owner}"`
6466
);
6567
}
6668

@@ -73,11 +75,11 @@ export async function main(
7375
let authentication, installationId, appSlug;
7476
// If at least one repository is set, get installation ID from that repository
7577

76-
if (parsedRepositoryNames) {
78+
if (parsedRepositoryNames.length > 0) {
7779
({ authentication, installationId, appSlug } = await pRetry(() => getTokenFromRepository(request, auth, parsedOwner, parsedRepositoryNames), {
7880
onFailedAttempt: (error) => {
7981
core.info(
80-
`Failed to create token for "${parsedRepositoryNames}" (attempt ${error.attemptNumber}): ${error.message}`
82+
`Failed to create token for "${parsedRepositoryNames.join(',')}" (attempt ${error.attemptNumber}): ${error.message}`
8183
);
8284
},
8385
retries: 3,
@@ -144,7 +146,7 @@ async function getTokenFromRepository(request, auth, parsedOwner, parsedReposito
144146
// https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#get-a-repository-installation-for-the-authenticated-app
145147
const response = await request("GET /repos/{owner}/{repo}/installation", {
146148
owner: parsedOwner,
147-
repo: parsedRepositoryNames.split(",")[0],
149+
repo: parsedRepositoryNames[0],
148150
request: {
149151
hook: auth.hook,
150152
},
@@ -154,11 +156,11 @@ async function getTokenFromRepository(request, auth, parsedOwner, parsedReposito
154156
const authentication = await auth({
155157
type: "installation",
156158
installationId: response.data.id,
157-
repositoryNames: parsedRepositoryNames.split(","),
159+
repositoryNames: parsedRepositoryNames,
158160
});
159161

160162
const installationId = response.data.id;
161163
const appSlug = response.data['app_slug'];
162164

163165
return { authentication, installationId, appSlug };
164-
}
166+
}

main.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ if (!privateKey) {
2525
throw new Error("Input required and not supplied: private-key");
2626
}
2727
const owner = core.getInput("owner");
28-
const repositories = core.getInput("repositories");
28+
const repositories = core.getInput("repositories")
29+
.split(/[\n,]+/)
30+
.map(s => s.trim())
31+
.filter(x => x !== '');
2932

3033
const skipTokenRevoke = Boolean(
3134
core.getInput("skip-token-revoke") || core.getInput("skip_token_revoke")

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { test } from "./main.js";
2+
3+
// Verify `main` successfully obtains a token when the `owner` and `repositories` inputs are set (and the latter is a list of repos).
4+
await test(() => {
5+
process.env.INPUT_OWNER = process.env.GITHUB_REPOSITORY_OWNER;
6+
const currentRepoName = process.env.GITHUB_REPOSITORY.split("/")[1];
7+
// Intentional unnecessary whitespace to test parsing to array
8+
process.env.INPUT_REPOSITORIES = `\n ${currentRepoName}\ntoolkit \n\n checkout \n`;
9+
});

tests/main-token-get-owner-set-repo-set-to-many.test.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ import { test } from "./main.js";
44
await test(() => {
55
process.env.INPUT_OWNER = process.env.GITHUB_REPOSITORY_OWNER;
66
const currentRepoName = process.env.GITHUB_REPOSITORY.split("/")[1];
7-
process.env.INPUT_REPOSITORIES = `${currentRepoName},toolkit`;
7+
// Intentional unnecessary whitespace to test parsing to array
8+
process.env.INPUT_REPOSITORIES = ` ${currentRepoName}, toolkit ,checkout`;
89
});

tests/snapshots/index.js.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,25 @@ Generated by [AVA](https://avajs.dev).
134134
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
135135
::save-state name=expiresAt::2016-07-11T22:14:10Z`
136136

137+
## main-token-get-owner-set-repo-set-to-many-newline.test.js
138+
139+
> stderr
140+
141+
''
142+
143+
> stdout
144+
145+
`owner and repositories set, creating token for repositories "create-github-app-token,toolkit,checkout" owned by "actions"␊
146+
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
147+
148+
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
149+
150+
::set-output name=installation-id::123456␊
151+
152+
::set-output name=app-slug::github-actions␊
153+
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
154+
::save-state name=expiresAt::2016-07-11T22:14:10Z`
155+
137156
## main-token-get-owner-set-repo-set-to-many.test.js
138157

139158
> stderr
@@ -142,7 +161,7 @@ Generated by [AVA](https://avajs.dev).
142161

143162
> stdout
144163
145-
`owner and repositories set, creating token for repositories "create-github-app-token,toolkit" owned by "actions"␊
164+
`owner and repositories set, creating token for repositories "create-github-app-token,toolkit,checkout" owned by "actions"␊
146165
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊
147166
148167
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a␊

tests/snapshots/index.js.snap

3 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)