Skip to content

Commit 49c6156

Browse files
authored
feat: Add support for ignoring specific repositories (#97)
1 parent 9401785 commit 49c6156

5 files changed

+138
-7
lines changed

README.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ Options:
1515
ot set. [string]
1616
-R, --octoherd-repos One or multiple repositories in the form of 'repo-owner/repo-n
1717
ame'. 'repo-owner/*' will find all repositories for one owner.
18-
'*' will find all repositories the user has access to. Will p
19-
rompt for repositories if not set. [array]
18+
'*' will find all repositories the user has access to.
19+
To exclude a repository use '!repo-owner/repo'. Will prompt
20+
for repositories if not set. [array]
2021
--octoherd-cache Cache responses for debugging. Creates a ./cache folder if fla
2122
g is set. Override by passing custom path [string]
2223
--octoherd-debug Show debug logs [boolean] [default: false]
@@ -32,6 +33,8 @@ Examples:
3233
octoherd/cli
3334
octoherd run -S path/to/script.js -T $TOKEN -R Avoid any prompts
3435
octoherd/cli --octoherd-bypass-confirms
36+
octoherd run -S path/to/script.js -T $TOKEN -R Will fetch all repositories except repo-owner/hello-world
37+
'repo-owner/*' -R '!repo-owner/hello-world
3538
```
3639

3740
The `script` must export a `script` function which takes three parameters:

bin/commands/run.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const options = {
2626
},
2727
"octoherd-repos": {
2828
description:
29-
"One or multiple repositories in the form of 'repo-owner/repo-name'. 'repo-owner/*' will find all repositories for one owner. '*' will find all repositories the user has access to. Will prompt for repositories if not set.",
29+
"One or multiple repositories in the form of 'repo-owner/repo-name'. 'repo-owner/*' will find all repositories for one owner. '*' will find all repositories the user has access to. To exclude a repository use '!repo-owner/repo'. Will prompt for repositories if not set.",
3030
type: "string",
3131
array: true,
3232
alias: "R",

lib/resolve-repositories.js

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
export async function resolveRepositories(state, repositories) {
2+
const ignoreRepositories = repositories.reduce((memo, fullName) => {
3+
if (fullName.startsWith('!')) {
4+
memo.push(fullName.slice(1))
5+
}
6+
return memo;
7+
}, []);
8+
29
const repositoriesWithStars = repositories.filter((fullName) => {
310
return /^[a-z0-9_.-]+\/(\*|[a-z0-9_.-]+\*|\*[a-z0-9_.-]+|[a-z0-9_.-]+\*[a-z0-9_.-]+)$/i.test(
411
fullName
5-
);
12+
) && !fullName.startsWith('!');
613
});
714

815
const repositoriesWithoutStars = repositories.filter((fullName) => {
9-
return !/\*/i.test(fullName);
16+
return !/\*/i.test(fullName) && !fullName.startsWith('!');
1017
});
1118
const allRepositories = !!repositories.find((name) => name === "*");
1219

@@ -79,6 +86,12 @@ export async function resolveRepositories(state, repositories) {
7986

8087
process.stdout.write("\n");
8188

89+
function includesRepository(list, repo) {
90+
return list.some((x) => x.toLowerCase() === repo.full_name.toLowerCase())
91+
}
92+
8293
// return array with unique repositories based by id (https://stackoverflow.com/a/56757215)
83-
return resolvedRepositories.filter((repo, index, repoList) => repoList.findIndex(v2 => (v2.id === repo.id)) === index);
94+
return resolvedRepositories
95+
.filter((repo, index, repoList) => repoList.findIndex(v2 => (v2.id === repo.id)) === index)
96+
.filter(repo => !includesRepository(ignoreRepositories, repo))
8497
}

lib/run-script-against-repositories.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export async function runScriptAgainstRepositories(state, octoherdRepos = []) {
1717
const invalid = values.find((value) => {
1818
if (value.trim() === "*") return;
1919

20-
if (/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.*-]+$/.test(value.trim())) {
20+
if (/^!?[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.*-]+$/.test(value.trim())) {
2121
return;
2222
}
2323

tests/resolve-repositories.test.js

+115
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,88 @@ withOrg("when requesting the same repository twice", async () => {
5454
equal(resolvedRepos, [mockedResponse]);
5555
});
5656

57+
withOrg("when requesting all repositories where one repository is ignored", async () => {
58+
const octokit = new Octokit({ auth: "randomToken" });
59+
60+
const repositories = ['octoherd/*', '!octoherd/cli'];
61+
62+
const mockedResponse = [
63+
{ id: 1, name: "cli", full_name: 'octoherd/cli' },
64+
{ id: 2, name: "octokit", full_name: 'octoherd/octokit' },
65+
{ id: 3, name: "octoherd", full_name: 'octoherd/octoherd' },
66+
];
67+
68+
simple.mock(octokit, "request").resolveWith({ data: undefined });
69+
70+
simple.mock(octokit.paginate, "iterator").returnWith({
71+
async *[Symbol.asyncIterator]() {
72+
yield { data: mockedResponse };
73+
},
74+
});
75+
76+
const resolvedRepos = await resolveRepositories(
77+
{
78+
log: console,
79+
octokit,
80+
},
81+
repositories
82+
);
83+
84+
equal(resolvedRepos, [
85+
{ id: 2, name: "octokit", full_name: 'octoherd/octokit' },
86+
{ id: 3, name: "octoherd", full_name: 'octoherd/octoherd' },
87+
]);
88+
});
89+
90+
withOrg("when one of the requested repositories is ignored", async () => {
91+
const octokit = new Octokit({ auth: "randomToken" });
92+
93+
const repositories = ['!octoherd/cli', 'octoherd/octokit'];
94+
95+
const mockedResponse = [
96+
{ id: 1, name: "cli", full_name: 'octoherd/cli' },
97+
{ id: 2, name: "octokit", full_name: 'octoherd/octokit' },
98+
];
99+
100+
simple.mock(octokit, "request").resolveWith({ data: mockedResponse[0] });
101+
simple.mock(octokit, "request").resolveWith({ data: mockedResponse[1] });
102+
103+
const resolvedRepos = await resolveRepositories(
104+
{
105+
log: console,
106+
octokit,
107+
},
108+
repositories
109+
);
110+
111+
equal(resolvedRepos, [
112+
{ id: 2, name: "octokit", full_name: 'octoherd/octokit' },
113+
]);
114+
});
115+
116+
withOrg("when requested repository is ignored", async () => {
117+
const org = "octoherd";
118+
const repo = "cli";
119+
const octokit = new Octokit({
120+
auth: "randomToken",
121+
});
122+
123+
const mockedResponse = { id: 1, name: repo };
124+
const repositories = [`!${org}/${repo.toUpperCase()}`];
125+
126+
simple.mock(octokit, "request").resolveWith({ data: mockedResponse });
127+
128+
const resolvedRepos = await resolveRepositories(
129+
{
130+
log: console,
131+
octokit,
132+
},
133+
repositories
134+
);
135+
136+
equal(resolvedRepos, []);
137+
});
138+
57139
withOrg("when requesting all the repositories", async () => {
58140
const org = "octoherd";
59141
const repo = "*";
@@ -305,6 +387,39 @@ withUser("when requesting all the repositories", async () => {
305387
equal(resolvedRepos, mockedResponse);
306388
});
307389

390+
withUser("when requesting all repositories where one repository is ignored", async () => {
391+
const octokit = new Octokit({ auth: "randomToken" });
392+
393+
const repositories = ['*', '!gr2m/two'];
394+
395+
const mockedResponse = [
396+
{ id: 1, name: "one", full_name: 'gr2m/one' },
397+
{ id: 2, name: "two", full_name: 'gr2m/two' },
398+
{ id: 3, name: "three", full_name: 'gr2m/three' },
399+
];
400+
401+
simple.mock(octokit, "request").resolveWith({ data: undefined });
402+
403+
simple.mock(octokit.paginate, "iterator").returnWith({
404+
async *[Symbol.asyncIterator]() {
405+
yield { data: mockedResponse };
406+
},
407+
});
408+
409+
const resolvedRepos = await resolveRepositories(
410+
{
411+
log: console,
412+
octokit,
413+
},
414+
repositories
415+
);
416+
417+
equal(resolvedRepos, [
418+
{ id: 1, name: "one", full_name: 'gr2m/one' },
419+
{ id: 3, name: "three", full_name: 'gr2m/three' },
420+
]);
421+
});
422+
308423
resolveRepos("resolve-repositories", () => {
309424
withOrg.run();
310425
withUser.run();

0 commit comments

Comments
 (0)