Skip to content

Commit

Permalink
Progress: github email, I think
Browse files Browse the repository at this point in the history
  • Loading branch information
JoshuaKGoldberg committed Aug 11, 2024
1 parent 04ec1c3 commit a7d1505
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 18 deletions.
85 changes: 69 additions & 16 deletions src/shared/options/createOptionDefaults/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";

import { createOptionDefaults } from "./index.js";

Expand Down Expand Up @@ -34,6 +34,14 @@ vi.mock("../../packages.js", () => ({
},
}));

const mockReadGitHubEmail = vi.fn();

vi.mock("./readGitHubEmail.js", () => ({
get readGitHubEmail() {
return mockReadGitHubEmail;
},
}));

describe("createOptionDefaults", () => {
describe("bin", () => {
it("returns undefined when package data does not have a bin", async () => {
Expand All @@ -56,24 +64,30 @@ describe("createOptionDefaults", () => {
});

describe("email", () => {
beforeEach(() => {
mockNpmUser.mockImplementation((username: string) => ({
email: `npm-${username}@test.com`,
}));
});

it("returns the npm whoami email from npm when only an npm exists", async () => {
mock$.mockImplementation(([command]: string[]) =>
command === "npm whoami" ? { stdout: "npm-username" } : undefined,
command === "npm whoami" ? { stdout: "username" } : undefined,
);
mockNpmUser.mockImplementation((username: string) => ({
email: `test@${username}.com`,
}));
mockReadGitHubEmail.mockResolvedValueOnce(undefined);

const actual = await createOptionDefaults().email();

expect(actual).toEqual({
github: "[email protected]",
npm: "[email protected]",
git: "[email protected]",
github: "[email protected]",
npm: "[email protected]",
});
});

it("returns the npm whoami email from npm when only a package author email exists", async () => {
mock$.mockResolvedValue({ stdout: "" });
mockReadGitHubEmail.mockResolvedValueOnce(undefined);
mockReadPackageData.mockResolvedValue({
author: {
email: "[email protected]",
Expand All @@ -83,46 +97,85 @@ describe("createOptionDefaults", () => {
const actual = await createOptionDefaults().email();

expect(actual).toEqual({
git: "[email protected]",
github: "[email protected]",
npm: "[email protected]",
});
});

it("returns the github email when only a github email exists", async () => {
mock$.mockResolvedValue({ stdout: "" });
mockReadPackageData.mockResolvedValueOnce({});
mockReadGitHubEmail.mockResolvedValueOnce("[email protected]");

const actual = await createOptionDefaults().email();

expect(actual).toEqual({
git: "[email protected]",
github: "[email protected]",
npm: "[email protected]",
});
});

it("returns the git user email when only a git user email exists", async () => {
mock$.mockImplementation(([command]: string[]) =>
command === "git config --get user.email"
? { stdout: "test@git.com" }
? { stdout: "git@test.com" }
: undefined,
);
mockReadGitHubEmail.mockResolvedValueOnce(undefined);
mockReadPackageData.mockResolvedValue({});

const actual = await createOptionDefaults().email();

expect(actual).toEqual({
git: "[email protected]",
github: "[email protected]",
npm: "[email protected]",
});
});

it("returns both the git user email and the npm user email when only those two exist", async () => {
mock$.mockImplementation(([command]: string[]) => ({
stdout:
command === "git config --get user.email"
? "[email protected]"
: "username",
}));
mockReadGitHubEmail.mockResolvedValueOnce(undefined);
mockReadPackageData.mockResolvedValue({});

const actual = await createOptionDefaults().email();

expect(actual).toEqual({
github: "[email protected]",
npm: "[email protected]",
git: "[email protected]",
github: "[email protected]",
npm: "[email protected]",
});
});

it("returns both the git user email and the npm user email when both exist", async () => {
it("returns all three emails when they all exist", async () => {
mock$.mockImplementation(([command]: string[]) => ({
stdout:
command === "git config --get user.email"
? "test@git.com"
: "npm-username",
? "git@test.com"
: "username",
}));
mockReadGitHubEmail.mockResolvedValueOnce("[email protected]");
mockReadPackageData.mockResolvedValue({});

const actual = await createOptionDefaults().email();

expect(actual).toEqual({
github: "[email protected]",
npm: "[email protected]",
git: "[email protected]",
github: "[email protected]",
npm: "[email protected]",
});
});

it("returns undefined when neither git nor npm emails exist", async () => {
it("returns undefined when none of the emails exist", async () => {
mock$.mockResolvedValue({ stdout: "" });
mockReadGitHubEmail.mockResolvedValueOnce(undefined);
mockReadPackageData.mockResolvedValue({});

const actual = await createOptionDefaults().email();
Expand Down
10 changes: 8 additions & 2 deletions src/shared/options/createOptionDefaults/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { PromptedOptions } from "../../types.js";
import { parsePackageAuthor } from "./parsePackageAuthor.js";
import { readDefaultsFromDevelopment } from "./readDefaultsFromDevelopment.js";
import { readDefaultsFromReadme } from "./readDefaultsFromReadme.js";
import { readGitHubEmail } from "./readGitHubEmail.js";

export function createOptionDefaults(promptedOptions?: PromptedOptions) {
const gitDefaults = tryCatchLazyValueAsync(async () =>
Expand All @@ -33,15 +34,20 @@ export function createOptionDefaults(promptedOptions?: PromptedOptions) {
bin: async () => (await packageData()).bin,
description: async () => (await packageData()).description,
email: async () => {
const githubEmail = await readGitHubEmail();
const gitEmail = await tryCatchAsync(
async () => (await $`git config --get user.email`).stdout,
);
const npmEmail =
(await npmDefaults())?.email ?? (await packageAuthor()).email;

/* eslint-disable @typescript-eslint/no-non-null-assertion */
return gitEmail || npmEmail
? { github: (gitEmail || npmEmail)!, npm: (npmEmail || gitEmail)! }
return gitEmail || githubEmail || npmEmail
? {
git: (gitEmail || githubEmail || npmEmail)!,
github: (githubEmail || gitEmail || npmEmail)!,
npm: (npmEmail || gitEmail || githubEmail)!,
}
: undefined;
/* eslint-enable @typescript-eslint/no-non-null-assertion */
},
Expand Down
52 changes: 52 additions & 0 deletions src/shared/options/createOptionDefaults/readGitHubEmail.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { describe, expect, it, vi } from "vitest";

import { readGitHubEmail } from "./readGitHubEmail.js";

const mockReadFileSafe = vi.fn();

vi.mock("../../readFileSafe.js", () => ({
get readFileSafe() {
return mockReadFileSafe;
},
}));

describe("readGitHubEmail", () => {
it("returns undefined when it cannot be found", async () => {
mockReadFileSafe.mockResolvedValue("nothing.");

const email = await readGitHubEmail();

expect(email).toBeUndefined();
});

it("returns undefined when a different template", async () => {
mockReadFileSafe.mockResolvedValue(
`## Other Code of Conduct
for enforcement at
[email protected].
`,
);

const email = await readGitHubEmail();

expect(email).toBeUndefined();
});

it("returns the email when it matches the template", async () => {
const expected = `[email protected]`;

mockReadFileSafe.mockResolvedValue(
`## Contributor Covenant Code of Conduct
reported to the community leaders responsible for enforcement at
${expected}.
All complaints will be reviewed and investigated promptly and fairly.
`,
);

const email = await readGitHubEmail();

expect(email).toBe(expected);
});
});
12 changes: 12 additions & 0 deletions src/shared/options/createOptionDefaults/readGitHubEmail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { readFileSafe } from "../../readFileSafe.js";

export async function readGitHubEmail() {
// The create-typescript-app template puts the GitHub email in the CoC.
// If they seem to be using the template, we can go with that.
const codeOfConduct = await readFileSafe(".github/CODE_OF_CONDUCT.md", "");
if (!codeOfConduct.includes("Contributor Covenant Code of Conduct")) {
return undefined;
}

return /for enforcement at[\r\n]+(.+)\.[\r\n]+All/.exec(codeOfConduct)?.[1];
}

0 comments on commit a7d1505

Please sign in to comment.