Skip to content

Commit 4c6b3c0

Browse files
authored
feat: prompt for repositories if --octoherd-repositories was not used (#34)
1 parent f8e5386 commit 4c6b3c0

File tree

5 files changed

+136
-70
lines changed

5 files changed

+136
-70
lines changed

README.md

+23-19
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,33 @@
55
## Usage
66

77
```
8-
Usage: octoherd run [options] [script] [repos...]
9-
10-
Positionals:
11-
script Path to *.js script. Must be an ES Module.
12-
repos One or multiple arrays in the form of 'repo-owner/repo-name'.
13-
'repo-owner/*' will find all repositories for one owner. '*' will find
14-
all repositories the user has access to [array] [default: []]
8+
Usage: octoherd run -S path/to/script.js [options]
159
1610
Options:
17-
--help Show help [boolean]
18-
--version Show version number [boolean]
19-
--octoherd-token Requires the "public_repo" scope for public
20-
repositories, "repo" scope for private
21-
repositories. [string] [required]
22-
--octoherd-cache Cache responses for debugging. Creates a ./cache
23-
folder if flag is set. Override by passing custom
24-
path [string]
25-
--octoherd-debug Show debug logs [boolean] [default: false]
26-
--octoherd-bypass-confirms Bypass prompts to confirm mutating requests
27-
[boolean] [default: false]
11+
--help Show help [boolean]
12+
-S, --octoherd-script Path to *.js script. Must be an ES Module. [string] [required]
13+
-T, --octoherd-token Requires the "public_repo" scope for public repositories, "rep
14+
o" scope for private repositories. Creates an OAuth token if n
15+
ot set. [string]
16+
-R, --octoherd-repos One or multiple repositories in the form of 'repo-owner/repo-n
17+
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]
20+
--octoherd-cache Cache responses for debugging. Creates a ./cache folder if fla
21+
g is set. Override by passing custom path [string]
22+
--octoherd-debug Show debug logs [boolean] [default: false]
23+
--octoherd-bypass-confirms Bypass prompts to confirm mutating requests
24+
[boolean] [default: false]
25+
--version Show version number [boolean]
2826
2927
Examples:
30-
octoherd run --octoherd-token $TOKEN path/to/script.js octoherd/cli
28+
octoherd run -S path/to/script.js Minimal usage example
29+
octoherd run -S path/to/script.js -T $TOKEN -R Pass token and repos as CLI flags
30+
octoherd/cli
31+
octoherd run -S path/to/script.js -T $TOKEN -R Avoid prompts for token and repos
32+
octoherd/cli
33+
octoherd run -S path/to/script.js -T $TOKEN -R Avoid any prompts
34+
octoherd/cli --octoherd-bypass-confirms
3135
```
3236

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

bin/commands/run.js

+24-10
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,23 @@ Node.js: ${process.version}, ${process.platform} ${process.arch}`.trim
1212

1313
/** @type { {[key: string]: import("yargs").Options} } */
1414
const options = {
15-
"octoherd-token": {
16-
description:
17-
'Requires the "public_repo" scope for public repositories, "repo" scope for private repositories.',
18-
type: "string",
19-
alias: "T",
20-
},
2115
"octoherd-script": {
2216
description: "Path to *.js script. Must be an ES Module.",
2317
demandOption: true,
2418
type: "string",
2519
alias: "S",
2620
},
21+
"octoherd-token": {
22+
description:
23+
'Requires the "public_repo" scope for public repositories, "repo" scope for private repositories. Creates an OAuth token if not set.',
24+
type: "string",
25+
alias: "T",
26+
},
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",
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.",
3030
type: "string",
3131
array: true,
32-
demandOption: true,
3332
alias: "R",
3433
},
3534
"octoherd-cache": {
@@ -55,8 +54,23 @@ const runCommand = {
5554
describe: "",
5655
builder: (yargs) =>
5756
yargs
58-
.usage("Usage: $0 run [options]")
59-
.example("$0 run -T $TOKEN -S path/to/script.js -R octoherd/cli", "")
57+
.wrap(96)
58+
.usage("Usage: octoherd run -S path/to/script.js [options]")
59+
.example([
60+
["octoherd run -S path/to/script.js", "Minimal usage example"],
61+
[
62+
"octoherd run -S path/to/script.js -T $TOKEN -R octoherd/cli",
63+
"Pass token and repos as CLI flags",
64+
],
65+
[
66+
"octoherd run -S path/to/script.js -T $TOKEN -R octoherd/cli",
67+
"Avoid prompts for token and repos",
68+
],
69+
[
70+
"octoherd run -S path/to/script.js -T $TOKEN -R octoherd/cli --octoherd-bypass-confirms",
71+
"Avoid any prompts",
72+
],
73+
])
6074
.options(options)
6175
.version(VERSIONS)
6276
.coerce("octoherd-script", async (script) => {

index.js

+7-36
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import enquirer from "enquirer";
1010
import { cache as octokitCachePlugin } from "./lib/octokit-plugin-cache.js";
1111
import { requestLog } from "./lib/octokit-plugin-request-log.js";
1212
import { requestConfirm } from "./lib/octokit-plugin-request-confirm.js";
13-
import { resolveRepositories } from "./lib/resolve-repositories.js";
13+
import { runScriptAgainstRepositories } from "./lib/run-script-against-repositories.js";
1414
import { VERSION } from "./version.js";
1515

1616
const levelColor = {
@@ -29,15 +29,10 @@ const levelColor = {
2929
* @param {string} options.octoherdToken Personal Access Token: Requires the "public_repo" scope for public repositories, "repo" scope for private repositories.
3030
* @param {boolean} options.octoherdCache Array of repository names in the form of "repo-owner/repo-name". To match all repositories for an owner, pass "repo-owner/*"
3131
*/
32-
export async function octoherd(
33-
options = {
34-
octoherdCache: false,
35-
octoherdRepos: [],
36-
}
37-
) {
32+
export async function octoherd(options) {
3833
const {
3934
octoherdToken,
40-
octoherdCache,
35+
octoherdCache = false,
4136
octoherdDebug,
4237
octoherdScript,
4338
octoherdRepos,
@@ -110,43 +105,19 @@ export async function octoherd(
110105
},
111106
});
112107

113-
if (octoherdRepos.length === 0) {
114-
throw new Error("[octoherd] No repositories provided");
115-
}
116-
117108
// trigger OAuth Device Flow before loading repositories
118109
// It's not necessary, but a better UX
119110
await octokit.auth({ type: "oauth-user" });
120111

121112
const state = {
122113
log: console,
123114
octokit,
115+
script: octoherdScript,
116+
userOptions,
117+
octoherdReposPassedAsFlag: !!octoherdRepos,
124118
};
125119

126-
try {
127-
octokit.log.info("Loading repositories ...");
128-
const repositories = await resolveRepositories(state, octoherdRepos);
129-
130-
for (const repository of repositories) {
131-
octokit.log.info(
132-
{ octoherd: true },
133-
"Running on %s ...",
134-
repository.full_name
135-
);
136-
137-
try {
138-
const { id, owner, name } = repository;
139-
octokit.log.setContext({ repository: { id, owner, name } });
140-
await octoherdScript(octokit, repository, userOptions);
141-
} catch (error) {
142-
if (!error.cancel) throw error;
143-
octokit.log.debug(error.message);
144-
}
145-
}
146-
} catch (error) {
147-
octokit.log.error(error);
148-
process.exitCode = 1;
149-
}
120+
await runScriptAgainstRepositories(state, octoherdRepos);
150121

151122
console.log("");
152123
console.log(chalk.gray("-".repeat(80)));

lib/resolve-repositories.js

+11-5
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,17 @@ export async function resolveRepositories(state, repositories) {
2020

2121
for (const name of repositoriesWithoutStars) {
2222
const [owner, repo] = name.split("/");
23-
const { data } = await state.octokit.request("/repos/{owner}/{repo}", {
24-
owner,
25-
repo,
26-
});
27-
resolvedRepositories.push(data);
23+
try {
24+
const { data } = await state.octokit.request("/repos/{owner}/{repo}", {
25+
owner,
26+
repo,
27+
});
28+
resolvedRepositories.push(data);
29+
} catch (error) {
30+
if (error.status !== 404) throw error;
31+
32+
state.octokit.log.warn(`Repository ${owner}/${repo} could not be found`);
33+
}
2834
process.stdout.write(".");
2935
}
3036

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import enquirer from "enquirer";
2+
import chalk from "chalk";
3+
4+
import { resolveRepositories } from "./resolve-repositories.js";
5+
6+
export async function runScriptAgainstRepositories(state, octoherdRepos = []) {
7+
if (!state.octoherdReposPassedAsFlag) {
8+
console.log("");
9+
const prompt = new enquirer.List({
10+
message: "Enter repositories",
11+
separator: / +/,
12+
hint:
13+
"e.g. octoherd/cli. Use a * to load all repositories for an owner, e.g. octoherd/*. Enter nothing to exit",
14+
validate(input) {
15+
const values = typeof input === "string" ? [input] : input;
16+
17+
const invalid = values.find(
18+
(value) =>
19+
!/^([a-z0-9_.-]+\/([a-z0-9_.-]+|\*)|\*)$/.test(value.trim())
20+
);
21+
22+
if (!invalid) return true;
23+
24+
return (
25+
chalk.red(`"${invalid}" is not a valid repository name.`) +
26+
chalk.gray(" The format is <owner>/<repo>")
27+
);
28+
},
29+
});
30+
31+
octoherdRepos = await prompt.run();
32+
33+
if (!state.reposNoticeShown) {
34+
console.log(
35+
`${chalk.gray(
36+
"To avoid this prompt in future, pass repositories with --octoherd-repos or -R"
37+
)}\n`
38+
);
39+
}
40+
state.reposNoticeShown = true;
41+
}
42+
43+
if (octoherdRepos.length === 0) return;
44+
45+
try {
46+
state.octokit.log.info("Loading repositories ...");
47+
const repositories = await resolveRepositories(state, octoherdRepos);
48+
49+
for (const repository of repositories) {
50+
state.octokit.log.info(
51+
{ octoherd: true },
52+
"Running on %s ...",
53+
repository.full_name
54+
);
55+
56+
try {
57+
const { id, owner, name } = repository;
58+
state.octokit.log.setContext({ repository: { id, owner, name } });
59+
await state.script(state.octokit, repository, state.userOptions);
60+
} catch (error) {
61+
if (!error.cancel) throw error;
62+
state.octokit.log.debug(error.message);
63+
}
64+
}
65+
} catch (error) {
66+
state.octokit.log.error(error);
67+
process.exitCode = 1;
68+
}
69+
70+
await runScriptAgainstRepositories(state);
71+
}

0 commit comments

Comments
 (0)