Skip to content

Commit 963df5a

Browse files
fix: refactor Base options to (mostly) properly use Bingo Inputs (#1985)
<!-- 👋 Hi, thanks for sending a PR to create-typescript-app! 🎁. Please fill out all fields below and make sure each item is true and [x] checked. Otherwise we may not be able to review your PR. --> ## PR Checklist - [x] Addresses an existing open issue: fixes #1973 - [x] That issue was marked as [`status: accepting prs`](https://github.com/JoshuaKGoldberg/create-typescript-app/issues?q=is%3Aopen+is%3Aissue+label%3A%22status%3A+accepting+prs%22) - [x] Steps in [CONTRIBUTING.md](https://github.com/JoshuaKGoldberg/create-typescript-app/blob/main/.github/CONTRIBUTING.md) were taken ## Overview Migrates the existing options over when possible. A couple are based on external packages that aren't easy to stub out external dependencies for. Those can be followup issues. Now, `base.ts` generally contains a more consistent pattern with naming: * `get*` are `lazyValue(async () => await read*(...))` functions for each value * `read*` are async functions that take in any number of `get*`s to compute a value Also removes an unused `logic` field while I'm here. This is a `fix:` because it adds better handling for running without a reliable internet connection: * `npm whoami --offline` to stop a minute of internal retries * `request: { retries: 0 }` on `inputFromOctkit` to stop its retries too 🎁
1 parent 20ab241 commit 963df5a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1376
-737
lines changed

src/base.test.ts

-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ describe("base", () => {
3636
href: "https://www.joshuakgoldberg.com/blog/contributing-to-a-create-typescript-app-repository",
3737
title: "Contributing to a create-typescript-app Repository",
3838
},
39-
login: "Josh Goldberg ✨",
4039
logo: {
4140
alt: "Project logo: the TypeScript blue square with rounded corners, but a plus sign instead of 'TS'",
4241
height: 128,

src/base.ts

+126-103
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,36 @@
11
import { BaseOptionsFor, createBase } from "bingo-stratum";
2-
import { execaCommand } from "execa";
3-
import gitRemoteOriginUrl from "git-remote-origin-url";
4-
import gitUrlParse from "git-url-parse";
52
import { inputFromFile } from "input-from-file";
63
import { inputFromScript } from "input-from-script";
74
import lazyValue from "lazy-value";
8-
import npmUser from "npm-user";
95
import { z } from "zod";
106

11-
import { inputFromOctokit } from "./inputs/inputFromOctokit.js";
12-
import { getExistingLabels } from "./options/getExistingLabels.js";
13-
import { parseEmojiFromDescription } from "./options/parseEmojiFromDescription.js";
14-
import { parsePackageAuthor } from "./options/parsePackageAuthor.js";
157
import { readAllContributors } from "./options/readAllContributors.js";
16-
import { readDefaultsFromReadme } from "./options/readDefaultsFromReadme.js";
8+
import { readAuthor } from "./options/readAuthor.js";
179
import { readDescription } from "./options/readDescription.js";
1810
import { readDocumentation } from "./options/readDocumentation.js";
11+
import { readEmailFromCodeOfConduct } from "./options/readEmailFromCodeOfConduct.js";
12+
import { readEmailFromGit } from "./options/readEmailFromGit.js";
13+
import { readEmailFromNpm } from "./options/readEmailFromNpm.js";
1914
import { readEmails } from "./options/readEmails.js";
15+
import { readEmoji } from "./options/readEmoji.js";
16+
import { readExistingLabels } from "./options/readExistingLabels.js";
17+
import { readExplainer } from "./options/readExplainer.js";
2018
import { readFileSafe } from "./options/readFileSafe.js";
2119
import { readFunding } from "./options/readFunding.js";
20+
import { readGitDefaults } from "./options/readGitDefaults.js";
2221
import { readGuide } from "./options/readGuide.js";
22+
import { readLogo } from "./options/readLogo.js";
23+
import { readNode } from "./options/readNode.js";
24+
import { readNpmDefaults } from "./options/readNpmDefaults.js";
25+
import { readOwner } from "./options/readOwner.js";
26+
import { readPackageAuthor } from "./options/readPackageAuthor.js";
2327
import { readPackageData } from "./options/readPackageData.js";
28+
import { readPackageDataFull } from "./options/readPackageDataFull.js";
2429
import { readPnpm } from "./options/readPnpm.js";
25-
import { swallowError } from "./utils/swallowError.js";
26-
import { tryCatchLazyValueAsync } from "./utils/tryCatchLazyValueAsync.js";
30+
import { readRepository } from "./options/readRepository.js";
31+
import { readRulesetId } from "./options/readRulesetId.js";
32+
import { readTitle } from "./options/readTitle.js";
33+
import { readUsage } from "./options/readUsage.js";
2734

2835
const zContributor = z.object({
2936
avatar_url: z.string(),
@@ -69,6 +76,7 @@ export const base = createBase({
6976
npm: z.string(),
7077
}),
7178
])
79+
// TODO: Test this? Is it still working?
7280
.transform((email) =>
7381
typeof email === "string" ? { github: email, npm: email } : email,
7482
)
@@ -159,131 +167,146 @@ export const base = createBase({
159167
.describe("package version to publish as and store in `package.json`"),
160168
},
161169
prepare({ options, take }) {
162-
const allContributors = lazyValue(async () => readAllContributors(take));
163-
const documentation = lazyValue(async () => readDocumentation(take));
170+
const getAllContributors = lazyValue(
171+
async () => await readAllContributors(take),
172+
);
164173

165-
const nvmrc = lazyValue(
174+
const getAuthor = lazyValue(
166175
async () =>
167-
await take(inputFromFile, {
168-
filePath: ".nvmrc",
169-
}),
176+
await readAuthor(getPackageAuthor, getNpmDefaults, options.owner),
170177
);
171178

172-
const existingLabels = lazyValue(async () =>
173-
getExistingLabels(take, await owner(), await repository()),
179+
const getBin = lazyValue(async () => (await getPackageDataFull()).bin);
180+
181+
const getEmoji = lazyValue(async () => await readEmoji(getDescription));
182+
183+
const getDescription = lazyValue(
184+
async () => await readDescription(getPackageDataFull, getReadme),
174185
);
175186

176-
const githubCliUser = lazyValue(async () => {
177-
return swallowError(
178-
await take(inputFromScript, {
179-
command: "gh config get user -h github.com",
180-
}),
181-
)?.stdout?.toString();
182-
});
187+
const getDocumentation = lazyValue(
188+
async () => await readDocumentation(take),
189+
);
190+
191+
const getEmail = lazyValue(
192+
async () =>
193+
await readEmails(
194+
getEmailFromCodeOfConduct,
195+
getEmailFromGit,
196+
getEmailFromNpm,
197+
),
198+
);
183199

184-
const readme = lazyValue(async () => await readFileSafe("README.md", ""));
200+
const getEmailFromCodeOfConduct = lazyValue(
201+
async () => await readEmailFromCodeOfConduct(take),
202+
);
185203

186-
const rulesetId = lazyValue(async () => {
187-
const rulesets = (await take(inputFromOctokit, {
188-
endpoint: "GET /repos/{owner}/{repo}/rulesets",
189-
options: {
190-
owner: await owner(),
191-
repo: await repository(),
192-
},
193-
})) as undefined | { id: string; name: string }[];
204+
const getEmailFromGit = lazyValue(async () => await readEmailFromGit(take));
194205

195-
return rulesets?.find(
196-
(ruleset) => ruleset.name === "Branch protection for main",
197-
)?.id;
198-
});
206+
const getEmailFromNpm = lazyValue(
207+
async () => await readEmailFromNpm(getNpmDefaults, getPackageAuthor),
208+
);
199209

200-
// TODO: Make these all use take
210+
const getExistingLabels = lazyValue(
211+
async () => await readExistingLabels(take, getOwner, getRepository),
212+
);
201213

202-
const emoji = async () => parseEmojiFromDescription(description);
214+
const getExplainer = lazyValue(async () => await readExplainer(getReadme));
203215

204-
const description = async () => await readDescription(packageData, readme);
216+
const getFunding = lazyValue(async () => await readFunding(take));
205217

206-
const gitDefaults = tryCatchLazyValueAsync(async () =>
207-
gitUrlParse(await gitRemoteOriginUrl()),
218+
const getGitDefaults = lazyValue(async () => await readGitDefaults());
219+
220+
const getGuide = lazyValue(async () => await readGuide(take));
221+
222+
const getLogo = lazyValue(async () => await readLogo(getReadme));
223+
224+
const getPackageData = lazyValue(
225+
async () => await readPackageData(getPackageDataFull),
208226
);
209227

210-
const npmDefaults = tryCatchLazyValueAsync(async () => {
211-
const whoami = (await execaCommand(`npm whoami`)).stdout;
212-
return whoami ? await npmUser(whoami) : undefined;
213-
});
228+
const getPackageDataFull = lazyValue(
229+
async () => await readPackageDataFull(take),
230+
);
214231

215-
const packageData = lazyValue(readPackageData);
216-
const packageAuthor = lazyValue(async () =>
217-
parsePackageAuthor(await packageData()),
232+
const getNode = lazyValue(
233+
async () => await readNode(getNvmrc, getPackageDataFull),
218234
);
219235

220-
const author = lazyValue(
236+
const getNpmDefaults = lazyValue(
237+
async () => await readNpmDefaults(getNpmWhoami),
238+
);
239+
240+
const getNpmWhoami = lazyValue(
221241
async () =>
222-
(await packageAuthor()).author ??
223-
(await npmDefaults())?.name ??
224-
options.owner,
242+
await take(inputFromScript, { command: "npm whoami --offline" }),
225243
);
226244

227-
const node = lazyValue(async () => {
228-
const { engines } = await packageData();
245+
const getNvmrc = lazyValue(
246+
async () =>
247+
await take(inputFromFile, {
248+
filePath: ".nvmrc",
249+
}),
250+
);
229251

230-
return {
231-
minimum:
232-
(engines?.node && /[\d+.]+/.exec(engines.node))?.[0] ?? "18.3.0",
233-
pinned: swallowError(await nvmrc())?.trim() ?? "20.18.0",
234-
};
235-
});
252+
const getOwner = lazyValue(
253+
async () => await readOwner(take, getGitDefaults, getPackageAuthor),
254+
);
236255

237-
const pnpm = lazyValue(async () => readPnpm(packageData));
256+
const getPackageAuthor = lazyValue(
257+
async () => await readPackageAuthor(getPackageDataFull),
258+
);
238259

239-
const version = lazyValue(async () => (await packageData()).version);
260+
const getPnpm = lazyValue(async () => await readPnpm(getPackageDataFull));
240261

241-
const owner = lazyValue(
242-
async () =>
243-
(await gitDefaults())?.organization ??
244-
(await packageAuthor()).author ??
245-
(await githubCliUser()),
262+
const getReadme = lazyValue(
263+
async () => await readFileSafe("README.md", ""),
246264
);
247265

248-
const repository = lazyValue(
266+
const getRepository = lazyValue(
249267
async () =>
250-
options.repository ??
251-
(await gitDefaults())?.name ??
252-
(await packageData()).name ??
253-
options.directory,
268+
await readRepository(getGitDefaults, getPackageDataFull, options),
269+
);
270+
271+
const getRulesetId = lazyValue(
272+
async () => await readRulesetId(take, getOwner, getRepository),
273+
);
274+
275+
const getTitle = lazyValue(
276+
async () => await readTitle(getReadme, getRepository),
277+
);
278+
279+
const getUsage = lazyValue(
280+
async () => await readUsage(getEmoji, getReadme, getRepository),
254281
);
255282

256-
const email = lazyValue(async () => readEmails(npmDefaults, packageAuthor));
283+
const getVersion = lazyValue(
284+
async () => (await getPackageDataFull()).version,
285+
);
257286

258287
return {
259288
access: "public" as const,
260-
author,
261-
bin: async () => (await packageData()).bin,
262-
contributors: allContributors,
263-
description,
264-
documentation,
265-
email,
266-
emoji,
267-
existingLabels,
268-
funding: readFunding,
269-
guide: readGuide,
270-
login: author,
271-
node,
272-
owner,
273-
packageData: async () => {
274-
const original = await packageData();
275-
276-
return {
277-
dependencies: original.dependencies,
278-
devDependencies: original.devDependencies,
279-
scripts: original.scripts,
280-
};
281-
},
282-
pnpm,
283-
repository,
284-
rulesetId,
285-
...readDefaultsFromReadme(emoji, readme, repository),
286-
version,
289+
author: getAuthor,
290+
bin: getBin,
291+
contributors: getAllContributors,
292+
description: getDescription,
293+
documentation: getDocumentation,
294+
email: getEmail,
295+
emoji: getEmoji,
296+
existingLabels: getExistingLabels,
297+
explainer: getExplainer,
298+
funding: getFunding,
299+
guide: getGuide,
300+
logo: getLogo,
301+
node: getNode,
302+
owner: getOwner,
303+
packageData: getPackageData,
304+
pnpm: getPnpm,
305+
repository: getRepository,
306+
rulesetId: getRulesetId,
307+
title: getTitle,
308+
usage: getUsage,
309+
version: getVersion,
287310
};
288311
},
289312
});

src/constants.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const defaults = {
2+
node: {
3+
minimum: "18.3.0",
4+
pinned: "20.18.0",
5+
},
6+
};

src/inputs/inputFromOctokit.ts

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const inputFromOctokit = createInput({
1515
headers: {
1616
"X-GitHub-Api-Version": "2022-11-28",
1717
},
18+
request: { retries: 0 },
1819
...args.options,
1920
});
2021

src/options/readAuthor.test.ts

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { describe, expect, it, vi } from "vitest";
2+
3+
import { readAuthor } from "./readAuthor.js";
4+
5+
describe(readAuthor, () => {
6+
it("returns package author when it exists", async () => {
7+
const author = "test-author";
8+
const getNpmDefaults = vi.fn();
9+
10+
const actual = await readAuthor(
11+
() => Promise.resolve({ author }),
12+
getNpmDefaults,
13+
undefined,
14+
);
15+
16+
expect(actual).toBe(author);
17+
expect(getNpmDefaults).not.toHaveBeenCalled();
18+
});
19+
20+
it("returns npm defaults name when only it exists", async () => {
21+
const name = "test-name";
22+
23+
const actual = await readAuthor(
24+
() => Promise.resolve({}),
25+
() => Promise.resolve({ name }),
26+
undefined,
27+
);
28+
29+
expect(actual).toBe(name);
30+
});
31+
32+
it("returns owner when only it exists", async () => {
33+
const owner = "test-owner";
34+
35+
const actual = await readAuthor(
36+
() => Promise.resolve({}),
37+
() => Promise.resolve(undefined),
38+
owner,
39+
);
40+
41+
expect(actual).toBe(owner);
42+
});
43+
44+
it("returns undefined when no sources provide a value", async () => {
45+
const actual = await readAuthor(
46+
() => Promise.resolve({}),
47+
() => Promise.resolve(undefined),
48+
undefined,
49+
);
50+
51+
expect(actual).toBeUndefined();
52+
});
53+
});

src/options/readAuthor.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export async function readAuthor(
2+
getPackageAuthor: () => Promise<{ author?: string }>,
3+
getNpmDefaults: () => Promise<undefined | { name?: string }>,
4+
owner: string | undefined,
5+
) {
6+
return (
7+
(await getPackageAuthor()).author ?? (await getNpmDefaults())?.name ?? owner
8+
);
9+
}

0 commit comments

Comments
 (0)