Skip to content

Commit 4a8ea73

Browse files
feat: ensure running user is included in .all-contributorsrc
1 parent 81bf74f commit 4a8ea73

File tree

3 files changed

+165
-8
lines changed

3 files changed

+165
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import chalk from "chalk";
2+
import prettier from "prettier";
3+
import { SpyInstance, beforeEach, describe, expect, it, vi } from "vitest";
4+
5+
import { addOwnerAsAllContributor } from "./addOwnerAsAllContributor.js";
6+
7+
const mock$ = vi.fn();
8+
9+
vi.mock("execa", () => ({
10+
get $() {
11+
return mock$;
12+
},
13+
}));
14+
15+
const mockWriteFile = vi.fn();
16+
17+
vi.mock("node:fs/promises", () => ({
18+
get default() {
19+
return {
20+
get writeFile() {
21+
return mockWriteFile;
22+
},
23+
};
24+
},
25+
}));
26+
27+
const mockReadFileAsJson = vi.fn();
28+
29+
vi.mock("../../shared/readFileAsJson.js", () => ({
30+
get readFileAsJson() {
31+
return mockReadFileAsJson;
32+
},
33+
}));
34+
35+
let mockConsoleWarn: SpyInstance;
36+
37+
describe("addOwnerAsAllContributor", () => {
38+
beforeEach(() => {
39+
mockConsoleWarn = vi
40+
.spyOn(console, "warn")
41+
.mockImplementation(() => undefined);
42+
});
43+
44+
it("throws an error when the .all-contributorsrc fails to read", async () => {
45+
mock$.mockResolvedValueOnce({
46+
stdout: JSON.stringify({ login: "user" }),
47+
});
48+
mockReadFileAsJson.mockResolvedValue("invalid");
49+
50+
await expect(async () => {
51+
await addOwnerAsAllContributor("owner");
52+
}).rejects.toMatchInlineSnapshot(
53+
'[Error: Invalid .all-contributorsrc: "invalid"]',
54+
);
55+
});
56+
57+
it("throws an error when the .all-contributorsrc is missing expected properties", async () => {
58+
mock$.mockResolvedValueOnce({
59+
stdout: JSON.stringify({ login: "user" }),
60+
});
61+
mockReadFileAsJson.mockResolvedValue({});
62+
63+
await expect(async () => {
64+
await addOwnerAsAllContributor("owner");
65+
}).rejects.toMatchInlineSnapshot(
66+
"[Error: Invalid .all-contributorsrc: {}]",
67+
);
68+
});
69+
70+
it("adds in the user from gh api user when it succeeds and contributors is empty", async () => {
71+
const login = "gh-api-user";
72+
73+
mock$.mockResolvedValueOnce({
74+
stdout: JSON.stringify({ login }),
75+
});
76+
mockReadFileAsJson.mockResolvedValue({ contributors: [] });
77+
78+
await addOwnerAsAllContributor("owner");
79+
80+
expect(mockWriteFile).toHaveBeenCalledWith(
81+
"./.all-contributorsrc",
82+
await prettier.format(
83+
JSON.stringify({
84+
contributors: [{ contributions: ["tool"], login }],
85+
}),
86+
{ parser: "json" },
87+
),
88+
);
89+
});
90+
91+
it("adds in the provided owner when gh api user fails and contributors is empty", async () => {
92+
const owner = "owner";
93+
94+
mock$.mockRejectedValueOnce({});
95+
mockReadFileAsJson.mockResolvedValue({ contributors: [] });
96+
97+
await addOwnerAsAllContributor(owner);
98+
99+
expect(mockWriteFile).toHaveBeenCalledWith(
100+
"./.all-contributorsrc",
101+
await prettier.format(
102+
JSON.stringify({
103+
contributors: [{ contributions: ["tool"], login: owner }],
104+
}),
105+
{ parser: "json" },
106+
),
107+
);
108+
expect(mockConsoleWarn).toHaveBeenCalledWith(
109+
chalk.gray(
110+
`Couldn't authenticate GitHub user, falling back to the provided owner name '${owner}'.`,
111+
),
112+
);
113+
});
114+
115+
it("resets JoshuaKGoldberg to just tool and adds in the running user when both exist", async () => {
116+
const login = "gh-api-user";
117+
118+
mock$.mockResolvedValueOnce({
119+
stdout: JSON.stringify({ login }),
120+
});
121+
mockReadFileAsJson.mockResolvedValue({
122+
contributors: [
123+
{ contributions: ["bug", "fix"], login },
124+
{ contributions: ["bug", "fix"], login: "JoshuaKGoldberg" },
125+
],
126+
});
127+
128+
await addOwnerAsAllContributor("owner");
129+
130+
expect(mockWriteFile).toHaveBeenCalledWith(
131+
"./.all-contributorsrc",
132+
await prettier.format(
133+
JSON.stringify({
134+
contributors: [
135+
{ contributions: ["bug", "fix"], login },
136+
{ contributions: ["tool"], login: "JoshuaKGoldberg" },
137+
],
138+
}),
139+
{ parser: "json" },
140+
),
141+
);
142+
});
143+
});

src/initialize/settings/addOwnerAsAllContributor.ts

+16-7
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,27 @@ export async function addOwnerAsAllContributor(owner: string) {
4343
);
4444
}
4545

46+
const contributors = existingContributors.contributors
47+
.filter(({ login }) => ["JoshuaKGoldberg", user].includes(login))
48+
.map((contributor) =>
49+
contributor.login === "JoshuaKGoldberg"
50+
? { ...contributor, contributions: ["tool"] }
51+
: contributor,
52+
);
53+
54+
if (!contributors.some((contributor) => contributor.login === user)) {
55+
contributors.push({
56+
contributions: ["tool"],
57+
login: user,
58+
});
59+
}
60+
4661
await fs.writeFile(
4762
"./.all-contributorsrc",
4863
await prettier.format(
4964
JSON.stringify({
5065
...existingContributors,
51-
contributors: existingContributors.contributors
52-
.filter(({ login }) => ["JoshuaKGoldberg", user].includes(login))
53-
.map((contributor) =>
54-
contributor.login === "JoshuaKGoldberg"
55-
? { ...contributor, contributions: ["tool"] }
56-
: contributor,
57-
),
66+
contributors,
5867
}),
5968
{ parser: "json" },
6069
),

src/shared/types.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
export interface AllContributorContributor {
2+
contributions: string[];
3+
login: string;
4+
}
5+
16
export interface AllContributorsData {
2-
contributors: { contributions: string[]; login: string }[];
7+
contributors: AllContributorContributor[];
38
}

0 commit comments

Comments
 (0)