Skip to content

Commit 6bd31ee

Browse files
fix: account for footnotes in README.md (#2130)
## PR Checklist - [x] Addresses an existing open issue: fixes #2129 - [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 🎁
1 parent e59901b commit 6bd31ee

8 files changed

+150
-15
lines changed

src/base.ts

+6
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { readPackageData } from "./options/readPackageData.js";
3131
import { readPnpm } from "./options/readPnpm.js";
3232
import { readReadmeAdditional } from "./options/readReadmeAdditional.js";
3333
import { readReadmeExplainer } from "./options/readReadmeExplainer.js";
34+
import { readReadmeFootnotes } from "./options/readReadmeFootnotes.js";
3435
import { readReadmeUsage } from "./options/readReadmeUsage.js";
3536
import { readRepository } from "./options/readRepository.js";
3637
import { readRulesetId } from "./options/readRulesetId.js";
@@ -197,6 +198,7 @@ export const base = createBase({
197198
getDevelopmentDocumentation,
198199
getReadmeAdditional,
199200
getReadmeExplainer,
201+
getReadmeFootnotes,
200202
getReadmeUsage,
201203
),
202204
);
@@ -281,6 +283,10 @@ export const base = createBase({
281283
async () => await readReadmeExplainer(getReadme),
282284
);
283285

286+
const getReadmeFootnotes = lazyValue(
287+
async () => await readReadmeFootnotes(getReadme),
288+
);
289+
284290
const getReadmeUsage = lazyValue(
285291
async () => await readReadmeUsage(getEmoji, getReadme, getRepository),
286292
);

src/blocks/blockREADME.test.ts

+44-4
Original file line numberDiff line numberDiff line change
@@ -117,15 +117,15 @@ describe("blockREADME", () => {
117117
`);
118118
});
119119

120-
test("options.explainer", () => {
120+
test("options.documentation.readme.explainer", () => {
121121
const creation = testBlock(blockREADME, {
122122
options: {
123123
...optionsBase,
124124
documentation: {
125125
...optionsBase.documentation,
126126
readme: {
127127
...optionsBase.documentation.readme,
128-
explainer: "\nAnd a one.\nAnd a two.\n",
128+
explainer: "And a one.\nAnd a two.",
129129
},
130130
},
131131
},
@@ -142,11 +142,9 @@ describe("blockREADME", () => {
142142
<img alt="💪 TypeScript: Strict" src="https://img.shields.io/badge/%F0%9F%92%AA_typescript-strict-21bb42.svg" />
143143
</p>
144144
145-
146145
And a one.
147146
And a two.
148147
149-
150148
## Usage
151149
152150
Test usage.
@@ -162,6 +160,48 @@ describe("blockREADME", () => {
162160
`);
163161
});
164162

163+
test("options.documentation.readme.footnotes", () => {
164+
const creation = testBlock(blockREADME, {
165+
options: {
166+
...optionsBase,
167+
documentation: {
168+
...optionsBase.documentation,
169+
readme: {
170+
...optionsBase.documentation.readme,
171+
footnotes: "And a one.\nAnd a two.",
172+
},
173+
},
174+
},
175+
});
176+
177+
expect(creation).toMatchInlineSnapshot(`
178+
{
179+
"files": {
180+
"README.md": "<h1 align="center">Test Title</h1>
181+
182+
<p align="center">Test description</p>
183+
184+
<p align="center">
185+
<img alt="💪 TypeScript: Strict" src="https://img.shields.io/badge/%F0%9F%92%AA_typescript-strict-21bb42.svg" />
186+
</p>
187+
188+
## Usage
189+
190+
Test usage.
191+
192+
## Development
193+
194+
See [\`.github/CONTRIBUTING.md\`](./.github/CONTRIBUTING.md), then [\`.github/DEVELOPMENT.md\`](./.github/DEVELOPMENT.md).
195+
Thanks! 💖
196+
197+
198+
And a one.
199+
And a two.",
200+
},
201+
}
202+
`);
203+
});
204+
165205
test("options.logo without sizing", () => {
166206
const creation = testBlock(blockREADME, {
167207
options: {

src/blocks/blockREADME.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ export const blockREADME = base.createBlock({
4343
options.logo &&
4444
`\n<img ${printAttributes({ align: "right", ...options.logo })}>\n`;
4545

46+
const suffixes = [
47+
...notices,
48+
options.documentation.readme.footnotes,
49+
].filter((suffix) => typeof suffix === "string");
50+
4651
return {
4752
files: {
4853
"README.md": `<h1 align="center">${options.title}</h1>
@@ -65,7 +70,7 @@ ${[...sections, options.documentation.readme.additional]
6570
.filter(Boolean)
6671
.map((section) => `\n${section}`)
6772
.join("")}
68-
${notices.length ? `\n${notices.map((notice) => notice.trim()).join("\n\n")}` : ""}`,
73+
${suffixes.length ? `\n${suffixes.map((suffix) => suffix.trim()).join("\n\n")}` : ""}`,
6974
},
7075
};
7176
},

src/options/readDocumentation.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,24 @@ export async function readDocumentation(
44
getDevelopmentDocumentation: () => Promise<string | undefined>,
55
getReadmeAdditional: () => Promise<string | undefined>,
66
getReadmeExplainer: () => Promise<string | undefined>,
7+
getReadmeFootnotes: () => Promise<string | undefined>,
78
getReadmeUsage: () => Promise<string>,
89
): Promise<Documentation> {
9-
const [additional, explainer, development, usage] = await Promise.all([
10-
getReadmeAdditional(),
11-
getReadmeExplainer(),
12-
getDevelopmentDocumentation(),
13-
getReadmeUsage(),
14-
]);
10+
const [additional, explainer, footnotes, development, usage] =
11+
await Promise.all([
12+
getReadmeAdditional(),
13+
getReadmeExplainer(),
14+
getReadmeFootnotes(),
15+
getDevelopmentDocumentation(),
16+
getReadmeUsage(),
17+
]);
1518

1619
return {
1720
development,
1821
readme: {
1922
additional,
2023
explainer,
24+
footnotes,
2125
usage,
2226
},
2327
};

src/options/readReadmeAdditional.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1+
import { indicatorTemplatedBy } from "./readReadmeFootnotes.js";
2+
13
const indicatorAfterAllContributors = /<!--\s*ALL-CONTRIBUTORS-LIST:END\s*-->/;
24
const indicatorAfterAllContributorsSpellCheck =
35
/<!--\s*spellchecker:\s*enable\s*-->/;
46

5-
const indicatorBeforeTemplatedBy =
6-
/> .* This package (?:is|was) (?:based|build|templated) (?:on|with) |<!-- You can remove this notice/;
7-
87
export async function readReadmeAdditional(getReadme: () => Promise<string>) {
98
const readme = await getReadme();
109
if (!readme) {
@@ -18,7 +17,7 @@ export async function readReadmeAdditional(getReadme: () => Promise<string>) {
1817
return undefined;
1918
}
2019

21-
const templatedByMatch = indicatorBeforeTemplatedBy.exec(readme);
20+
const templatedByMatch = indicatorTemplatedBy.exec(readme);
2221

2322
return readme
2423
.slice(
+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { describe, expect, it } from "vitest";
2+
3+
import { readReadmeFootnotes } from "./readReadmeFootnotes.js";
4+
5+
describe(readReadmeFootnotes, () => {
6+
it("resolves undefined when there is no existing readme content", async () => {
7+
const getReadme = () => Promise.resolve("");
8+
9+
const result = await readReadmeFootnotes(getReadme);
10+
11+
expect(result).toBeUndefined();
12+
});
13+
14+
it("resolves undefined when there is no templated by notice", async () => {
15+
const getReadme = () => Promise.resolve(`# My Package`);
16+
17+
const result = await readReadmeFootnotes(getReadme);
18+
19+
expect(result).toBeUndefined();
20+
});
21+
22+
it("resolves undefined when there is no content after a templated by notice", async () => {
23+
const getReadme = () =>
24+
Promise.resolve(`# My Package
25+
26+
> 💖 This package was templated with etc. etc.
27+
28+
`);
29+
30+
const result = await readReadmeFootnotes(getReadme);
31+
32+
expect(result).toBeUndefined();
33+
});
34+
35+
it("resolves the content when there plain text content after a templated by notice", async () => {
36+
const getReadme = () =>
37+
Promise.resolve(`# My Package
38+
39+
> 💖 This package was templated with etc. etc.
40+
41+
After.
42+
`);
43+
44+
const result = await readReadmeFootnotes(getReadme);
45+
46+
expect(result).toBe("After.");
47+
});
48+
49+
it("resolves the content when there are footnotes after a templated by notice", async () => {
50+
const getReadme = () =>
51+
Promise.resolve(`# My Package
52+
53+
> 💖 This package was templated with etc. etc.
54+
55+
[^1]: After.
56+
`);
57+
58+
const result = await readReadmeFootnotes(getReadme);
59+
60+
expect(result).toBe("[^1]: After.");
61+
});
62+
});

src/options/readReadmeFootnotes.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export const indicatorTemplatedBy =
2+
/> .* This package (?:is|was) (?:based|build|templated) (?:on|with) |<!-- You can remove this notice/;
3+
4+
export async function readReadmeFootnotes(getReadme: () => Promise<string>) {
5+
const readme = await getReadme();
6+
if (!readme) {
7+
return undefined;
8+
}
9+
10+
const indexOfTemplatedBy = indicatorTemplatedBy.exec(readme)?.index;
11+
if (!indexOfTemplatedBy) {
12+
return undefined;
13+
}
14+
15+
const indexOfNextLine = readme.indexOf("\n", indexOfTemplatedBy);
16+
17+
return readme.slice(indexOfNextLine).trim() || undefined;
18+
}

src/schemas.ts

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export type Contributor = z.infer<typeof zContributor>;
1313
export const zReadme = z.object({
1414
additional: z.string().optional(),
1515
explainer: z.string().optional(),
16+
footnotes: z.string().optional(),
1617
usage: z.string(),
1718
});
1819

0 commit comments

Comments
 (0)