Skip to content

Commit 4e25327

Browse files
fix: improve migration owner replacements (#1078)
## PR Checklist - [x] Addresses an existing open issue: fixes #1043 - [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 Uses a [`to` callback](https://github.com/adamreisnz/replace-in-file/#using-callbacks-for-to) to determine whether to swap out owner to `options.owner` in `"JoshuaKGoldberg"` _(always)_ or `"JoshuaKGoldberg/..."` (if the `...` is `options.repository`). Also fixes some existing edge cases around owner replacements: * Syncs up the end-of-readme attribution notice so there's only one place it's written in the templates * Uses `options.owner` and `options.repository` in `createDotGitHubFiles` instead of hardcoding `JoshuaKGoldberg/create-typescript-app`.
1 parent df82c12 commit 4e25327

10 files changed

+146
-33
lines changed

script/initialize-test-e2e.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ for (const search of [`/JoshuaKGoldberg/`, "create-typescript-app"]) {
3030
const { stdout } = await $`grep -i ${search} ${files}`;
3131
assert.equal(
3232
stdout,
33-
`README.md:> 💙 This package was templated with [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app).`,
33+
`README.md:> 💙 This package was templated with [\`create-typescript-app\`](https://github.com/JoshuaKGoldberg/create-typescript-app).`,
3434
);
3535
}
3636

src/initialize/initializeWithOptions.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ export async function initializeWithOptions({
2222
await updateLocalFiles(options);
2323
},
2424
],
25-
["Updating README.md", updateReadme],
25+
[
26+
"Updating README.md",
27+
async () => {
28+
await updateReadme(options);
29+
},
30+
],
2631
["Clearing changelog", clearChangelog],
2732
[
2833
"Updating all-contributors table",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { describe, expect, test } from "vitest";
2+
3+
import { createJoshuaKGoldbergReplacement } from "./createJoshuaKGoldbergReplacement.js";
4+
5+
const options = {
6+
owner: "NewOwner",
7+
repository: "new-repository",
8+
};
9+
10+
describe("createJoshuaKGoldbergReplacement", () => {
11+
test.each([
12+
[`JoshuaKGoldberg`, options.owner],
13+
[
14+
`JoshuaKGoldberg/${options.repository}`,
15+
`${options.owner}/${options.repository}`,
16+
],
17+
[`JoshuaKGoldberg/other-repository`, `JoshuaKGoldberg/other-repository`],
18+
])("%s", (before, expected) => {
19+
const [matcher, replacer] = createJoshuaKGoldbergReplacement(options);
20+
21+
const actual = replacer(before, matcher.exec(before)?.[1]);
22+
23+
expect(actual).toBe(expected);
24+
});
25+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Options } from "../shared/types.js";
2+
3+
/**
4+
* Creates a replace-in-file replacement for JoshuaKGoldberg/... matches,
5+
* keeping repository names not being migrated (e.g. for GitHub actions).
6+
*/
7+
export const createJoshuaKGoldbergReplacement = (
8+
options: Pick<Options, "owner" | "repository">,
9+
) =>
10+
[
11+
/JoshuaKGoldberg(?:\/(.+))?/g,
12+
(full: string, capture: string | undefined) =>
13+
capture
14+
? // If this was a "JoshuaKGoldberg/..." repository link,
15+
// swap the owner if it's the repository being migrated.
16+
capture.startsWith(options.repository)
17+
? `${options.owner}/${capture}`
18+
: full
19+
: // Otherwise it's just "JoshuaKGoldberg" standalone,
20+
// so swap to the new owner.
21+
options.owner,
22+
] as const;

src/steps/updateLocalFiles.test.ts

+22-6
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,15 @@ describe("updateLocalFiles", () => {
7575
"./.github/**/*",
7676
"./*.*",
7777
],
78-
"from": /JoshuaKGoldberg\\(\\?!\\\\/console-fail-test\\)/g,
78+
"from": /JoshuaKGoldberg\\(\\?:\\\\/\\(\\.\\+\\)\\)\\?/g,
79+
"to": [Function],
80+
},
81+
],
82+
[
83+
{
84+
"allowEmptyPaths": true,
85+
"files": "package.json",
86+
"from": /JoshuaKGoldberg/g,
7987
"to": "StubOwner",
8088
},
8189
],
@@ -200,8 +208,8 @@ describe("updateLocalFiles", () => {
200208
{
201209
"allowEmptyPaths": true,
202210
"files": "./README.md",
203-
"from": "> 💙 This package is based on [@StubOwner](https://github.com/StubOwner)'s [stub-repository](https://github.com/JoshuaKGoldberg/stub-repository).",
204-
"to": "> 💙 This package is based on [@JoshuaKGoldberg](https://github.com/JoshuaKGoldberg)'s [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app).",
211+
"from": /> 💙 This package was templated with \\.\\+\\\\\\./g,
212+
"to": "> 💙 This package was templated with [\`create-typescript-app\`](https://github.com/JoshuaKGoldberg/create-typescript-app).",
205213
},
206214
],
207215
]
@@ -234,7 +242,15 @@ describe("updateLocalFiles", () => {
234242
"./.github/**/*",
235243
"./*.*",
236244
],
237-
"from": /JoshuaKGoldberg\\(\\?!\\\\/console-fail-test\\)/g,
245+
"from": /JoshuaKGoldberg\\(\\?:\\\\/\\(\\.\\+\\)\\)\\?/g,
246+
"to": [Function],
247+
},
248+
],
249+
[
250+
{
251+
"allowEmptyPaths": true,
252+
"files": "package.json",
253+
"from": /JoshuaKGoldberg/g,
238254
"to": "StubOwner",
239255
},
240256
],
@@ -359,8 +375,8 @@ describe("updateLocalFiles", () => {
359375
{
360376
"allowEmptyPaths": true,
361377
"files": "./README.md",
362-
"from": "> 💙 This package is based on [@StubOwner](https://github.com/StubOwner)'s [stub-repository](https://github.com/JoshuaKGoldberg/stub-repository).",
363-
"to": "> 💙 This package is based on [@JoshuaKGoldberg](https://github.com/JoshuaKGoldberg)'s [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app).",
378+
"from": /> 💙 This package was templated with \\.\\+\\\\\\./g,
379+
"to": "> 💙 This package was templated with [\`create-typescript-app\`](https://github.com/JoshuaKGoldberg/create-typescript-app).",
364380
},
365381
],
366382
]

src/steps/updateLocalFiles.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import replaceInFile from "replace-in-file";
22

33
import { readFileSafeAsJson } from "../shared/readFileSafeAsJson.js";
44
import { Options } from "../shared/types.js";
5+
import { createJoshuaKGoldbergReplacement } from "./createJoshuaKGoldbergReplacement.js";
6+
import { endOfReadmeTemplateLine } from "./updateReadme.js";
57

68
interface ExistingPackageData {
79
description?: string;
@@ -14,7 +16,8 @@ export async function updateLocalFiles(options: Options) {
1416

1517
const replacements = [
1618
[/Create TypeScript App/g, options.title],
17-
[/JoshuaKGoldberg(?!\/console-fail-test)/g, options.owner],
19+
createJoshuaKGoldbergReplacement(options),
20+
[/JoshuaKGoldberg/g, options.owner, "package.json"],
1821
[/create-typescript-app/g, options.repository],
1922
[/\/\*\n.+\*\/\n\n/gs, ``, ".eslintrc.cjs"],
2023
[/"author": ".+"/g, `"author": "${options.author}"`, "./package.json"],
@@ -37,8 +40,8 @@ export async function updateLocalFiles(options: Options) {
3740
[`["src/**/*.ts!", "script/**/*.js"]`, `"src/**/*.ts!"`, "./knip.jsonc"],
3841
// Edge case: migration scripts will rewrite README.md attribution
3942
[
40-
`> 💙 This package is based on [@${options.owner}](https://github.com/${options.owner})'s [${options.repository}](https://github.com/JoshuaKGoldberg/${options.repository}).`,
41-
`> 💙 This package is based on [@JoshuaKGoldberg](https://github.com/JoshuaKGoldberg)'s [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app).`,
43+
/> 💙 This package was templated with .+\./g,
44+
endOfReadmeTemplateLine,
4245
"./README.md",
4346
],
4447
];
@@ -65,8 +68,9 @@ export async function updateLocalFiles(options: Options) {
6568
to,
6669
});
6770
} catch (error) {
71+
const toString = typeof to === "function" ? "(function)" : to;
6872
throw new Error(
69-
`Failed to replace ${from.toString()} with ${to} in ${files.toString()}`,
73+
`Failed to replace ${from.toString()} with ${toString} in ${files.toString()}`,
7074
{
7175
cause: error,
7276
},

src/steps/updateReadme.test.ts

+46-12
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import { describe, expect, it, vi } from "vitest";
22

33
import { updateReadme } from "./updateReadme.js";
44

5-
const mockAppendFile = vi.fn();
5+
const mockWriteFile = vi.fn();
66

77
vi.mock("node:fs/promises", () => ({
88
default: {
9-
get appendFile() {
10-
return mockAppendFile;
9+
get writeFile() {
10+
return mockWriteFile;
1111
},
1212
},
1313
}));
@@ -20,20 +20,26 @@ vi.mock("../shared/readFileSafe.js", () => ({
2020
},
2121
}));
2222

23+
const options = {
24+
owner: "NewOwner",
25+
};
26+
2327
describe("updateReadme", () => {
2428
it("adds a notice when the file does not contain it already", async () => {
25-
mockReadFileSafe.mockResolvedValue("");
29+
mockReadFileSafe.mockResolvedValue(
30+
"Existing JoshuaKGoldberg/create-typescript-app content.",
31+
);
2632

27-
await updateReadme();
33+
await updateReadme(options);
2834

29-
expect(mockAppendFile.mock.calls).toMatchInlineSnapshot(`
35+
expect(mockWriteFile.mock.calls).toMatchInlineSnapshot(`
3036
[
3137
[
3238
"./README.md",
33-
"
39+
"Existing NewOwner/create-typescript-app content.
3440
<!-- You can remove this notice if you don't want it 🙂 no worries! -->
3541
36-
> 💙 This package was templated with [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app).
42+
> 💙 This package was templated with [\`create-typescript-app\`](https://github.com/JoshuaKGoldberg/create-typescript-app).
3743
",
3844
],
3945
]
@@ -42,23 +48,51 @@ describe("updateReadme", () => {
4248

4349
it("doesn't add a notice when the file contains it already", async () => {
4450
mockReadFileSafe.mockResolvedValue(`
51+
Existing JoshuaKGoldberg/create-typescript-app content.
52+
4553
<!-- You can remove this notice if you don't want it 🙂 no worries! -->
4654
4755
> 💙 This package was templated using [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app).
4856
`);
4957

50-
await updateReadme();
58+
await updateReadme(options);
5159

52-
expect(mockAppendFile.mock.calls).toMatchInlineSnapshot("[]");
60+
expect(mockWriteFile.mock.calls).toMatchInlineSnapshot(`
61+
[
62+
[
63+
"./README.md",
64+
"
65+
Existing NewOwner/create-typescript-app content.
66+
67+
<!-- You can remove this notice if you don't want it 🙂 no worries! -->
68+
69+
> 💙 This package was templated using [create-typescript-app](https://github.com/NewOwner/create-typescript-app).
70+
",
71+
],
72+
]
73+
`);
5374
});
5475

5576
it("doesn't add a notice when the file contains an older version of it already", async () => {
5677
mockReadFileSafe.mockResolvedValue(`
78+
Existing JoshuaKGoldberg/create-typescript-app content.
79+
5780
💙 This package is based on [@JoshuaKGoldberg](https://github.com/JoshuaKGoldberg)'s [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app).
5881
`);
5982

60-
await updateReadme();
83+
await updateReadme(options);
6184

62-
expect(mockAppendFile.mock.calls).toMatchInlineSnapshot("[]");
85+
expect(mockWriteFile.mock.calls).toMatchInlineSnapshot(`
86+
[
87+
[
88+
"./README.md",
89+
"
90+
Existing NewOwner/create-typescript-app content.
91+
92+
💙 This package is based on [@NewOwner](https://github.com/NewOwner)'s [create-typescript-app](https://github.com/NewOwner/create-typescript-app).
93+
",
94+
],
95+
]
96+
`);
6397
});
6498
});

src/steps/updateReadme.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,29 @@ import fs from "node:fs/promises";
22
import { EOL } from "node:os";
33

44
import { readFileSafe } from "../shared/readFileSafe.js";
5+
import { Options } from "../shared/types.js";
6+
7+
export const endOfReadmeTemplateLine = `> 💙 This package was templated with [\`create-typescript-app\`](https://github.com/JoshuaKGoldberg/create-typescript-app).`;
58

69
export const endOfReadmeNotice = [
710
``,
811
`<!-- You can remove this notice if you don't want it 🙂 no worries! -->`,
912
``,
10-
`> 💙 This package was templated with [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app).`,
13+
endOfReadmeTemplateLine,
1114
``,
1215
].join(EOL);
1316

1417
export const endOfReadmeMatcher =
1518
/💙.+(?:based|built|templated).+(?:from|using|on|with).+create-typescript-app/;
1619

17-
export async function updateReadme() {
18-
const contents = await readFileSafe("./README.md", "");
20+
export async function updateReadme(options: Pick<Options, "owner">) {
21+
let contents = await readFileSafe("./README.md", "");
22+
23+
contents = contents.replaceAll("JoshuaKGoldberg", options.owner);
1924

2025
if (!endOfReadmeMatcher.test(contents)) {
21-
await fs.appendFile("./README.md", endOfReadmeNotice);
26+
contents += endOfReadmeNotice;
2227
}
28+
29+
await fs.writeFile("./README.md", contents);
2330
}

src/steps/writeReadme/index.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ describe("writeReadme", () => {
9595
9696
<!-- You can remove this notice if you don't want it 🙂 no worries! -->
9797
98-
> 💙 This package was templated with [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app).
98+
> 💙 This package was templated with [\`create-typescript-app\`](https://github.com/JoshuaKGoldberg/create-typescript-app).
9999
",
100100
],
101101
]
@@ -156,7 +156,7 @@ describe("writeReadme", () => {
156156
157157
<!-- You can remove this notice if you don't want it 🙂 no worries! -->
158158
159-
> 💙 This package was templated with [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app).
159+
> 💙 This package was templated with [\`create-typescript-app\`](https://github.com/JoshuaKGoldberg/create-typescript-app).
160160
",
161161
],
162162
]
@@ -220,7 +220,7 @@ describe("writeReadme", () => {
220220
221221
<!-- You can remove this notice if you don't want it 🙂 no worries! -->
222222
223-
> 💙 This package was templated with [create-typescript-app](https://github.com/JoshuaKGoldberg/create-typescript-app).
223+
> 💙 This package was templated with [\`create-typescript-app\`](https://github.com/JoshuaKGoldberg/create-typescript-app).
224224
",
225225
],
226226
]

src/steps/writing/creation/dotGitHub/createDotGitHubFiles.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,8 @@ There are two steps involved:
163163
164164
### Finding an Issue
165165
166-
With the exception of very small typos, all changes to this repository generally need to correspond to an [unassigned open issue marked as \`status: accepting prs\` on the issue tracker](https://github.com/JoshuaKGoldberg/create-typescript-app/issues?q=is%3Aissue+is%3Aopen+label%3A%22status%3A+accepting+prs%22+no%3Aassignee+).
167-
If this is your first time contributing, consider searching for [unassigned issues that also have the \`good first issue\` label](https://github.com/JoshuaKGoldberg/create-typescript-app/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22+label%3A%22status%3A+accepting+prs%22+no%3Aassignee+).
166+
With the exception of very small typos, all changes to this repository generally need to correspond to an [unassigned open issue marked as \`status: accepting prs\` on the issue tracker](https://github.com/${options.owner}/${options.repository}/issues?q=is%3Aissue+is%3Aopen+label%3A%22status%3A+accepting+prs%22+no%3Aassignee+).
167+
If this is your first time contributing, consider searching for [unassigned issues that also have the \`good first issue\` label](https://github.com/${options.owner}/${options.repository}/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22+label%3A%22status%3A+accepting+prs%22+no%3Aassignee+).
168168
If the issue you'd like to fix isn't found on the issue, see [Reporting Issues](#reporting-issues) for filing your own (please do!).
169169
170170
#### Issue Claiming

0 commit comments

Comments
 (0)