Skip to content

Commit 1249f4d

Browse files
feat: add options.explainer for blockREADME (#1879)
## PR Checklist - [x] Addresses an existing open issue: fixes #1878 - [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 Also touches up some spacing and tests in `readDefaultsFromReadme`, while I'm in the area. 💖
1 parent 1fac1a1 commit 1249f4d

File tree

5 files changed

+163
-9
lines changed

5 files changed

+163
-9
lines changed

src/next/base.ts

+4
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ export const base = createBase({
7272
.describe(
7373
"email address to be listed as the point of contact in docs and packages",
7474
),
75+
explainer: z
76+
.array(z.string())
77+
.optional()
78+
.describe("additional README.md sentence(s) describing the package"),
7579
funding: z
7680
.string()
7781
.optional()

src/next/blocks/blockREADME.test.ts

+91-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ describe("blockREADME", () => {
6262
"README.md": "<h1 align="center">Test Title</h1>
6363
6464
<p align="center">
65-
First sentence.
65+
First sentence.
6666
Second sentence.
6767
</p>
6868
@@ -89,6 +89,47 @@ describe("blockREADME", () => {
8989
`);
9090
});
9191

92+
test("options.explainer", () => {
93+
const creation = testBlock(blockREADME, {
94+
options: {
95+
...options,
96+
explainer: ["And a one.", "And a two."],
97+
},
98+
});
99+
100+
expect(creation).toMatchInlineSnapshot(`
101+
{
102+
"files": {
103+
"README.md": "<h1 align="center">Test Title</h1>
104+
105+
<p align="center">Test description</p>
106+
107+
<p align="center">
108+
<a href="https://github.com/test-owner/test-repository/blob/main/.github/CODE_OF_CONDUCT.md" target="_blank"><img alt="🤝 Code of Conduct: Kept" src="https://img.shields.io/badge/%F0%9F%A4%9D_code_of_conduct-kept-21bb42" /></a>
109+
<a href="https://codecov.io/gh/test-owner/test-repository" target="_blank"><img alt="🧪 Coverage" src="https://img.shields.io/codecov/c/github/test-owner/test-repository?label=%F0%9F%A7%AA%20coverage" /></a>
110+
<a href="https://github.com/test-owner/test-repository/blob/main/LICENSE.md" target="_blank"><img alt="📝 License: MIT" src="https://img.shields.io/badge/%F0%9F%93%9D_license-MIT-21bb42.svg"></a>
111+
<a href="http://npmjs.com/package/test-repository"><img alt="📦 npm version" src="https://img.shields.io/npm/v/test-repository?color=21bb42&label=%F0%9F%93%A6%20npm" /></a>
112+
<img alt="💪 TypeScript: Strict" src="https://img.shields.io/badge/%F0%9F%92%AA_typescript-strict-21bb42.svg" />
113+
</p>
114+
115+
And a one.
116+
And a two.
117+
118+
## Usage
119+
120+
Use it.
121+
122+
## Development
123+
124+
See [\`.github/CONTRIBUTING.md\`](./.github/CONTRIBUTING.md), then [\`.github/DEVELOPMENT.md\`](./.github/DEVELOPMENT.md).
125+
Thanks! 💖
126+
127+
",
128+
},
129+
}
130+
`);
131+
});
132+
92133
test("options.logo without sizing", () => {
93134
const creation = testBlock(blockREADME, {
94135
options: {
@@ -177,6 +218,55 @@ describe("blockREADME", () => {
177218
`);
178219
});
179220

221+
test("options.explainer and options.logo", () => {
222+
const creation = testBlock(blockREADME, {
223+
options: {
224+
...options,
225+
explainer: ["And a one.", "And a two."],
226+
logo: {
227+
alt: "My logo",
228+
height: 100,
229+
src: "img.jpg",
230+
width: 128,
231+
},
232+
},
233+
});
234+
235+
expect(creation).toMatchInlineSnapshot(`
236+
{
237+
"files": {
238+
"README.md": "<h1 align="center">Test Title</h1>
239+
240+
<p align="center">Test description</p>
241+
242+
<p align="center">
243+
<a href="https://github.com/test-owner/test-repository/blob/main/.github/CODE_OF_CONDUCT.md" target="_blank"><img alt="🤝 Code of Conduct: Kept" src="https://img.shields.io/badge/%F0%9F%A4%9D_code_of_conduct-kept-21bb42" /></a>
244+
<a href="https://codecov.io/gh/test-owner/test-repository" target="_blank"><img alt="🧪 Coverage" src="https://img.shields.io/codecov/c/github/test-owner/test-repository?label=%F0%9F%A7%AA%20coverage" /></a>
245+
<a href="https://github.com/test-owner/test-repository/blob/main/LICENSE.md" target="_blank"><img alt="📝 License: MIT" src="https://img.shields.io/badge/%F0%9F%93%9D_license-MIT-21bb42.svg"></a>
246+
<a href="http://npmjs.com/package/test-repository"><img alt="📦 npm version" src="https://img.shields.io/npm/v/test-repository?color=21bb42&label=%F0%9F%93%A6%20npm" /></a>
247+
<img alt="💪 TypeScript: Strict" src="https://img.shields.io/badge/%F0%9F%92%AA_typescript-strict-21bb42.svg" />
248+
</p>
249+
250+
<img align="right" alt="My logo" height="100" src="img.jpg" width="128">
251+
252+
And a one.
253+
And a two.
254+
255+
## Usage
256+
257+
Use it.
258+
259+
## Development
260+
261+
See [\`.github/CONTRIBUTING.md\`](./.github/CONTRIBUTING.md), then [\`.github/DEVELOPMENT.md\`](./.github/DEVELOPMENT.md).
262+
Thanks! 💖
263+
264+
",
265+
},
266+
}
267+
`);
268+
});
269+
180270
test("without addons", () => {
181271
const creation = testBlock(blockREADME, {
182272
options,

src/next/blocks/blockREADME.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { base } from "../base.js";
55
function printAttributes(attributes: Record<string, number | string>) {
66
return Object.entries(attributes)
77
.map(([key, value]) => `${key}="${value}"`)
8+
.sort()
89
.join(" ");
910
}
1011

@@ -20,9 +21,12 @@ export const blockREADME = base.createBlock({
2021
produce({ addons, options }) {
2122
const { badges, notices, sections } = addons;
2223

23-
const logo = options.logo
24-
? `\n<img ${printAttributes({ align: "right", ...options.logo })}>\n`
25-
: "";
24+
const logo =
25+
options.logo &&
26+
`\n<img ${printAttributes({ align: "right", ...options.logo })}>\n`;
27+
28+
const explainer =
29+
options.explainer && `\n${options.explainer.join("\n")}\n`;
2630

2731
return {
2832
files: {
@@ -37,7 +41,7 @@ export const blockREADME = base.createBlock({
3741
<a href="http://npmjs.com/package/${options.repository}"><img alt="📦 npm version" src="https://img.shields.io/npm/v/${options.repository}?color=21bb42&label=%F0%9F%93%A6%20npm" /></a>
3842
<img alt="💪 TypeScript: Strict" src="https://img.shields.io/badge/%F0%9F%92%AA_typescript-strict-21bb42.svg" />
3943
</p>
40-
${logo}
44+
${[logo, explainer].filter(Boolean).join("")}
4145
## Usage
4246
4347
${options.usage}
@@ -58,5 +62,5 @@ function formatDescription(description: string) {
5862
return description;
5963
}
6064

61-
return "\n\t" + description.replaceAll(". ", ". \n\t") + "\n";
65+
return "\n\t" + description.replaceAll(". ", ".\n\t") + "\n";
6266
}

src/shared/options/createOptionDefaults/readDefaultsFromReadme.test.ts

+45-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,50 @@ vi.mock("./getUsageFromReadme.js", () => ({
1919
}));
2020

2121
describe("readDefaultsFromReadme", () => {
22+
describe("explainer", () => {
23+
it("defaults to undefined when it cannot be found", async () => {
24+
const explainer = await readDefaultsFromReadme(
25+
() => Promise.resolve(`nothing.`),
26+
() => Promise.resolve(undefined),
27+
).explainer();
28+
29+
expect(explainer).toBeUndefined();
30+
});
31+
32+
it("parses a line after badges", async () => {
33+
const explainer = await readDefaultsFromReadme(
34+
() =>
35+
Promise.resolve(`
36+
</p>
37+
38+
This is my project.
39+
40+
## Usage
41+
.`),
42+
() => Promise.resolve(undefined),
43+
).explainer();
44+
45+
expect(explainer).toEqual(["This is my project."]);
46+
});
47+
48+
it("parses multiple line after badges", async () => {
49+
const explainer = await readDefaultsFromReadme(
50+
() =>
51+
Promise.resolve(`
52+
</p>
53+
54+
This is my project.
55+
It is good.
56+
57+
## Usage
58+
.`),
59+
() => Promise.resolve(undefined),
60+
).explainer();
61+
62+
expect(explainer).toEqual(["This is my project.", "It is good."]);
63+
});
64+
});
65+
2266
describe("logo", () => {
2367
it("defaults to undefined when it cannot be found", async () => {
2468
const logo = await readDefaultsFromReadme(
@@ -107,7 +151,7 @@ describe("readDefaultsFromReadme", () => {
107151
const logo = await readDefaultsFromReadme(
108152
() =>
109153
Promise.resolve(`
110-
<img alt='Project logo: a fancy circle' src='abc/def.jpg'/>`),
154+
<img alt='Project logo: a fancy circle' height='117px' src='abc/def.jpg' width=' 117px'/>`),
111155
() => Promise.resolve(undefined),
112156
).logo();
113157

src/shared/options/createOptionDefaults/readDefaultsFromReadme.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ export function readDefaultsFromReadme(
1313
);
1414

1515
return {
16+
explainer: async () => {
17+
return />\n\n([\s\S]*?)\n\n## Usage/u
18+
.exec(await readme())?.[1]
19+
.split("\n")
20+
.map((line) => line.trim())
21+
.filter(Boolean);
22+
},
23+
1624
logo: async () => {
1725
const tag = await imageTag();
1826

@@ -22,18 +30,22 @@ export function readDefaultsFromReadme(
2230

2331
const src = /src\s*=(.+)['"/]>/
2432
.exec(tag)?.[1]
25-
?.replaceAll(/^['"]|['"]$/g, "");
33+
?.split(/\s*\w+=/)[0]
34+
.replaceAll(/^['"]|['"]$/g, "");
2635

2736
if (!src) {
2837
return undefined;
2938
}
3039

3140
return {
32-
alt: /alt=['"](.+)['"]\s*src=/.exec(tag)?.[1] ?? "Project logo",
41+
alt:
42+
/alt=['"](.+)['"]\s*src=/.exec(tag)?.[1].split(/['"]?\s*\w+=/)[0] ??
43+
"Project logo",
3344
src,
3445
...readLogoSizing(src),
3546
};
3647
},
48+
3749
title: async () => {
3850
const text = await readme();
3951
const fromText = (/^<h1\s+align="center">(.+)<\/h1>/.exec(text) ??

0 commit comments

Comments
 (0)