Skip to content

Commit afff8fc

Browse files
feat: prompt for values in hydration too (#505)
## PR Checklist - [x] Addresses an existing open issue: fixes #500 - [x] That issue was marked as [`status: accepting prs`](https://github.com/JoshuaKGoldberg/template-typescript-node-package/issues?q=is%3Aopen+is%3Aissue+label%3A%22status%3A+accepting+prs%22) - [x] Steps in [CONTRIBUTING.md](https://github.com/JoshuaKGoldberg/template-typescript-node-package/blob/main/.github/CONTRIBUTING.md) were taken ## Overview Extracts the prompting logic from _setup_ out to a `PrefillPrompter` class, and re-uses that class in _hydration_. There was some defaults-checking (`owner`, `title`) that actually wasn't necessary anymore.
1 parent df01318 commit afff8fc

File tree

7 files changed

+157
-141
lines changed

7 files changed

+157
-141
lines changed

src/hydrate/hydrate.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import { finalizeDependencies as finalizeDependencies } from "./steps/finalizeDe
1212
import { writeReadme } from "./steps/writeReadme.js";
1313
import { writeStructure } from "./steps/writing/writeStructure.js";
1414
import { getHydrationDefaults } from "./values/getHydrationDefaults.js";
15-
import { ensureHydrationInputValues } from "./values/hydrationInputValues.js";
15+
import { augmentWithHydrationValues } from "./values/hydrationInputValues.js";
1616

1717
export async function hydrate(args: string[]) {
18-
const { values: hydrationValues } = parseArgs({
18+
const { values: hydrationSkips } = parseArgs({
1919
args,
2020
options: {
2121
"skip-install": { type: "boolean" },
@@ -30,35 +30,38 @@ export async function hydrate(args: string[]) {
3030
defaults: await getHydrationDefaults(),
3131
label: "hydration",
3232
run: async ({ octokit, values }) => {
33-
ensureHydrationInputValues(values);
33+
const hydrationValues = await augmentWithHydrationValues(values);
3434

3535
await withSpinner(clearUnnecessaryFiles, "clearing unnecessary files");
3636

3737
await withSpinner(
38-
() => writeStructure(values),
38+
() => writeStructure(hydrationValues),
3939
"writing new repository structure"
4040
);
4141

42-
await withSpinner(() => writeReadme(values), "writing README.md");
42+
await withSpinner(
43+
() => writeReadme(hydrationValues),
44+
"writing README.md"
45+
);
4346

44-
if (hydrationValues["skip-install"]) {
47+
if (hydrationSkips["skip-install"]) {
4548
skipSpinnerBlock(`Skipping package installations.`);
4649
} else {
4750
await withSpinner(
48-
() => finalizeDependencies(values),
51+
() => finalizeDependencies(hydrationValues),
4952
"finalizing dependencies"
5053
);
5154
}
5255

53-
if (hydrationValues["skip-setup"]) {
56+
if (hydrationSkips["skip-setup"]) {
5457
skipSpinnerBlock(`Done hydrating, and skipping setup command.`);
5558
} else {
5659
successSpinnerBlock("Done hydrating. Starting setup command...");
5760

5861
await setupWithInformation({
5962
octokit,
6063
values: {
61-
...values,
64+
...hydrationValues,
6265
skipUninstalls: true,
6366
},
6467
});

src/hydrate/values/getHydrationDefaults.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface PartialPackageData {
1111
name?: string;
1212
repository?: string;
1313
}
14+
1415
export async function getHydrationDefaults() {
1516
const existingReadme = await readFileSafe("./README.md", "");
1617
const existingPackage = JSON.parse(

src/hydrate/values/hydrationInputValues.test.ts

Lines changed: 0 additions & 51 deletions
This file was deleted.
Lines changed: 14 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,20 @@
11
import { InputValues } from "../../shared/inputs.js";
2-
import { HydrationInputValues } from "./types.js";
2+
import { PrefillPrompter } from "../../shared/PrefillPrompter.js";
33

4-
type NullableProperties<T> = {
5-
[K in keyof T]: T[K] | undefined;
6-
};
4+
export async function augmentWithHydrationValues(values: InputValues) {
5+
const prompter = new PrefillPrompter();
76

8-
export function ensureHydrationInputValues(
9-
values: NullableProperties<InputValues>
10-
): asserts values is HydrationInputValues {
11-
const complaints: string[] = [];
12-
13-
for (const [key, suggestion] of [
14-
["description", "add a 'description' to package.json"],
15-
["repository", "add a 'name' to package.json"],
16-
[
7+
return {
8+
...values,
9+
author: await prompter.getPrefillOrPromptedValue(
1710
"author",
18-
"add an 'author' to package.json like \"Your Name <[email protected]>\" or an { email, name } object",
19-
],
20-
[
11+
values.author,
12+
"What author will be used for the owner?"
13+
),
14+
email: await prompter.getPrefillOrPromptedValue(
2115
"email",
22-
"add an 'author' to package.json like \"Your Name <[email protected]>\" or an { email, name } object",
23-
],
24-
["owner", "add an 'origin' git remote"],
25-
["title", "add an h1 to the README.md"],
26-
] as const) {
27-
if (!values[key]) {
28-
complaints.push(
29-
`- Could not determine ${key}. Please ${suggestion}, or provide with --${key}.`
30-
);
31-
}
32-
}
33-
34-
if (complaints.length) {
35-
throw new Error(
36-
`Failed to determine repository settings:\n${complaints.join("\n")}`
37-
);
38-
}
16+
values.email,
17+
"What email will be used for the owner?"
18+
),
19+
};
3920
}

src/shared/PrefillPrompter.test.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import chalk from "chalk";
2+
import { describe, expect, it, vi } from "vitest";
3+
4+
import { PrefillPrompter } from "./PrefillPrompter.js";
5+
6+
const mockText = vi.fn();
7+
8+
vi.mock("@clack/prompts", () => ({
9+
isCancel: () => false,
10+
get text() {
11+
return mockText;
12+
},
13+
}));
14+
15+
const mockLogLine = vi.fn();
16+
17+
vi.mock("./cli/lines.js", () => ({
18+
get logLine() {
19+
return mockLogLine;
20+
},
21+
}));
22+
23+
describe("PrefillPrompter", () => {
24+
it("logs a line and a pre-fill message when a first value already exists", async () => {
25+
const prompter = new PrefillPrompter();
26+
const existing = "existing value";
27+
28+
const actual = await prompter.getPrefillOrPromptedValue(
29+
"key",
30+
existing,
31+
""
32+
);
33+
34+
expect(actual).toEqual(existing);
35+
expect(mockLogLine.mock.calls).toEqual([
36+
[],
37+
[chalk.gray(`Pre-filling key to existing value.`)],
38+
]);
39+
});
40+
41+
it("logs only a pre-fill message when a second value already exists", async () => {
42+
const prompter = new PrefillPrompter();
43+
44+
await prompter.getPrefillOrPromptedValue("key 1", "existing 1", "");
45+
46+
const actual = await prompter.getPrefillOrPromptedValue(
47+
"key 2",
48+
"existing 2",
49+
""
50+
);
51+
52+
expect(actual).toEqual("existing 2");
53+
expect(mockLogLine.mock.calls).toEqual([
54+
[],
55+
[chalk.gray(`Pre-filling key 1 to existing 1.`)],
56+
[chalk.gray(`Pre-filling key 2 to existing 2.`)],
57+
]);
58+
});
59+
60+
it("prompts for a new value when the value doesn't already exist", async () => {
61+
const prompter = new PrefillPrompter();
62+
const expected = "expected value";
63+
64+
mockText.mockResolvedValue(expected);
65+
66+
const actual = await prompter.getPrefillOrPromptedValue(
67+
"key",
68+
undefined,
69+
""
70+
);
71+
72+
expect(actual).toEqual(expected);
73+
expect(mockLogLine).not.toHaveBeenCalled();
74+
});
75+
});

src/shared/PrefillPrompter.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import * as prompts from "@clack/prompts";
2+
import chalk from "chalk";
3+
4+
import { logLine } from "./cli/lines.js";
5+
import { handlePromptCancel } from "./prompts.js";
6+
7+
export class PrefillPrompter {
8+
#shouldLogLineBeforePrefill = true;
9+
10+
async getPrefillOrPromptedValue(
11+
key: string,
12+
existingValue: string | undefined,
13+
message: string,
14+
placeholder?: string
15+
) {
16+
if (existingValue) {
17+
if (this.#shouldLogLineBeforePrefill) {
18+
logLine();
19+
this.#shouldLogLineBeforePrefill = false;
20+
}
21+
22+
logLine(chalk.gray(`Pre-filling ${key} to ${existingValue}.`));
23+
return existingValue;
24+
}
25+
26+
const value = await prompts.text({
27+
message,
28+
placeholder,
29+
validate: (val) => {
30+
if (val.length === 0) {
31+
return "Please enter a value.";
32+
}
33+
},
34+
});
35+
36+
handlePromptCancel(value);
37+
38+
return value;
39+
}
40+
41+
reset() {
42+
this.#shouldLogLineBeforePrefill = true;
43+
}
44+
}

0 commit comments

Comments
 (0)