Skip to content

Commit ee6ea64

Browse files
authored
feat(programmatic-interface): export a function to enable running an octoherd script against a repository list (#220)
1 parent ecc8f5b commit ee6ea64

4 files changed

+151
-74
lines changed

index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ 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 { runScriptAgainstRepositories } from "./lib/run-script-against-repositories.js";
13+
import { loadRepositoriesAndRunScript } from "./lib/load-repositories-and-run-script.js";
1414
import { VERSION } from "./version.js";
1515

1616
export { Octokit } from "@octoherd/octokit";
17+
export { runScriptAgainstRepositories } from "./lib/run-script-against-repositories.js";
1718

1819
const levelColor = {
1920
debug: chalk.bgGray.black,
@@ -113,7 +114,7 @@ export async function octoherd(options) {
113114
octoherdReposPassedAsFlag: !!octoherdRepos,
114115
};
115116

116-
await runScriptAgainstRepositories(state, octoherdRepos);
117+
await loadRepositoriesAndRunScript(state, octoherdRepos);
117118

118119
console.log("");
119120
console.log(chalk.gray("-".repeat(80)));
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import enquirer from "enquirer";
2+
import chalk from "chalk";
3+
4+
import { resolveRepositories } from "./resolve-repositories.js";
5+
import { runScriptAgainstRepositories } from "./run-script-against-repositories.js";
6+
7+
export async function loadRepositoriesAndRunScript(state, octoherdRepos = []) {
8+
if (!state.octoherdReposPassedAsFlag) {
9+
console.log("");
10+
const prompt = new enquirer.List({
11+
message: "Enter repositories",
12+
separator: / +/,
13+
hint:
14+
"e.g. octoherd/cli. Use a * to load all repositories for an owner, e.g. octoherd/*. Enter nothing to exit",
15+
validate(input) {
16+
const values = typeof input === "string" ? [input] : input;
17+
18+
const invalid = values.find((value) => {
19+
if (value.trim() === "*") return;
20+
21+
if (/^!?[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.*-]+$/.test(value.trim())) {
22+
return;
23+
}
24+
25+
return true;
26+
});
27+
28+
if (!invalid) return true;
29+
30+
return (
31+
chalk.red(`"${invalid}" is not a valid repository name.`) +
32+
chalk.gray(" The format is <owner>/<repo>")
33+
);
34+
},
35+
});
36+
37+
octoherdRepos = await prompt.run();
38+
39+
if (!state.reposNoticeShown) {
40+
console.log(
41+
`${chalk.gray(
42+
"To avoid this prompt in future, pass repositories with --octoherd-repos or -R"
43+
)}\n`
44+
);
45+
}
46+
state.reposNoticeShown = true;
47+
}
48+
49+
if (octoherdRepos.length === 0) return;
50+
51+
try {
52+
state.octokit.log.info("Loading repositories ...");
53+
const repositories = await resolveRepositories(state, octoherdRepos);
54+
55+
await runScriptAgainstRepositories(state.octokit, repositories, state.script, state.userOptions);
56+
} catch (error) {
57+
state.octokit.log.error(error);
58+
process.exitCode = 1;
59+
}
60+
61+
await loadRepositoriesAndRunScript(state);
62+
}
Lines changed: 17 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,21 @@
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((value) => {
18-
if (value.trim() === "*") return;
19-
20-
if (/^!?[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.*-]+$/.test(value.trim())) {
21-
return;
22-
}
23-
24-
return true;
25-
});
26-
27-
if (!invalid) return true;
28-
29-
return (
30-
chalk.red(`"${invalid}" is not a valid repository name.`) +
31-
chalk.gray(" The format is <owner>/<repo>")
32-
);
33-
},
34-
});
35-
36-
octoherdRepos = await prompt.run();
37-
38-
if (!state.reposNoticeShown) {
39-
console.log(
40-
`${chalk.gray(
41-
"To avoid this prompt in future, pass repositories with --octoherd-repos or -R"
42-
)}\n`
43-
);
44-
}
45-
state.reposNoticeShown = true;
46-
}
47-
48-
if (octoherdRepos.length === 0) return;
49-
50-
try {
51-
state.octokit.log.info("Loading repositories ...");
52-
const repositories = await resolveRepositories(state, octoherdRepos);
53-
54-
for (const repository of repositories) {
55-
state.octokit.log.info(
56-
{ octoherd: true },
57-
"Running on %s ...",
58-
repository.full_name
59-
);
60-
61-
try {
62-
const { id, owner, name } = repository;
63-
state.octokit.log.setContext({ repository: { id, owner, name } });
64-
await state.script(state.octokit, repository, state.userOptions);
65-
} catch (error) {
66-
if (!error.cancel) throw error;
67-
state.octokit.log.debug(error.message);
1+
export async function runScriptAgainstRepositories(octokit, repositories, script, options) {
2+
for (const repository of repositories) {
3+
octokit.log.info(
4+
{ octoherd: true },
5+
"Running on %s ...",
6+
repository.full_name
7+
);
8+
9+
try {
10+
if (octokit.log.setContext) {
11+
const {id, owner, name} = repository;
12+
octokit.log.setContext({repository: {id, owner, name}});
6813
}
14+
15+
script(octokit, repository, options);
16+
} catch (error) {
17+
if (!error.cancel) throw error;
18+
octokit.log.debug(error.message);
6919
}
70-
} catch (error) {
71-
state.octokit.log.error(error);
72-
process.exitCode = 1;
7320
}
74-
75-
await runScriptAgainstRepositories(state);
7621
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { suite } from "uvu";
2+
import { equal } from "uvu/assert";
3+
import simple from "simple-mock";
4+
5+
import { runScriptAgainstRepositories } from "../lib/run-script-against-repositories.js";
6+
7+
const runAgainstRepos = suite('run script against repositories');
8+
9+
const userOptions = {};
10+
const octoherdCliRepository = {id: 123, owner: {login: 'octoherd'}, name: 'cli', full_name: 'octoherd/cli'};
11+
12+
runAgainstRepos('run the script against the provided repositories', async () => {
13+
const octokitInfoLogger = simple.spy(() => undefined);
14+
const octokit = {log: {info: octokitInfoLogger}};
15+
const octokitCoreRepository = {id: 456, owner: {login: 'octokit'}, name: 'core.js', full_name: 'octokit/core.js'};
16+
const repositories = [octoherdCliRepository, octokitCoreRepository];
17+
const script = simple.spy(() => undefined);
18+
19+
await runScriptAgainstRepositories(octokit, repositories, script, userOptions);
20+
21+
repositories.forEach((repository, index) => {
22+
equal(script.calls[index].args, [octokit, repository, userOptions]);
23+
equal(octokitInfoLogger.calls[index].args, [{octoherd: true}, "Running on %s ...", repository.full_name]);
24+
});
25+
});
26+
27+
runAgainstRepos('set logger context when available', async () => {
28+
const octokitLoggerContextSetter = simple.spy(() => undefined);
29+
const octokit = {log: {info: () => undefined, setContext: octokitLoggerContextSetter}};
30+
const octokitCoreRepository = {id: 456, owner: {login: 'octokit'}, name: 'core.js', full_name: 'octokit/core.js'};
31+
const repositories = [octoherdCliRepository, octokitCoreRepository];
32+
const script = simple.spy(() => undefined);
33+
34+
await runScriptAgainstRepositories(octokit, repositories, script, userOptions);
35+
36+
repositories.forEach((repository, index) => equal(
37+
octokitLoggerContextSetter.calls[index].args,
38+
[{repository: {id: repository.id, owner: repository.owner, name: repository.name}}]
39+
));
40+
});
41+
42+
runAgainstRepos('log cancelled routes at debug level', async () => {
43+
const repositories = [octoherdCliRepository];
44+
const errorMessage = 'error message';
45+
const cancelledRouteError = new Error(errorMessage);
46+
cancelledRouteError.cancel = true;
47+
const octokitDebugLogger = simple.spy(() => undefined);
48+
const octokit = {log: {info: () => undefined, debug: octokitDebugLogger}};
49+
const script = simple.spy(() => undefined).throwWith(cancelledRouteError);
50+
51+
await runScriptAgainstRepositories(octokit, repositories, script, userOptions);
52+
53+
equal(octokitDebugLogger.calls[0].args, [errorMessage]);
54+
});
55+
56+
runAgainstRepos('throw other errors', async () => {
57+
const repositories = [octoherdCliRepository];
58+
const error = new Error();
59+
const octokit = {log: {info: () => undefined}};
60+
const script = simple.spy(() => undefined).throwWith(error);
61+
62+
try {
63+
await runScriptAgainstRepositories(octokit, repositories, script, userOptions);
64+
} catch (err) {
65+
equal(err, error);
66+
}
67+
});
68+
69+
runAgainstRepos.run();

0 commit comments

Comments
 (0)