Skip to content

Commit dc634e6

Browse files
feat: added sub-spinner labels for some spinner task groups (#710)
## PR Checklist - [x] Addresses an existing open issue: fixes #708 - [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 For some longer-running spinners that had multiple sub-tasks, adds a new `withSpinners` function that takes in an array of labeled tasks. The CLI output for that one shows the list of completed tasks & running task under the parent group, then clears that list once it's done. https://github.com/JoshuaKGoldberg/template-typescript-node-package/assets/3335181/4c26d831-5d3a-438b-96ce-7afaeba3bd0b
1 parent bd6cc25 commit dc634e6

File tree

6 files changed

+157
-29
lines changed

6 files changed

+157
-29
lines changed

src/create/createWithOptions.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as prompts from "@clack/prompts";
22
import { $ } from "execa";
33

4-
import { withSpinner } from "../shared/cli/spinners.js";
4+
import { withSpinner, withSpinners } from "../shared/cli/spinners.js";
55
import { doesRepositoryExist } from "../shared/doesRepositoryExist.js";
66
import { OctokitAndOptions } from "../shared/options/readOptions.js";
77
import { addToolAllContributors } from "../steps/addToolAllContributors.js";
@@ -15,10 +15,20 @@ export async function createWithOptions({
1515
octokit,
1616
options,
1717
}: OctokitAndOptions) {
18-
await withSpinner("Creating repository structure", async () => {
19-
await writeStructure(options);
20-
await writeReadme(options);
21-
});
18+
await withSpinners("Creating repository structure", [
19+
[
20+
"Writing structure",
21+
async () => {
22+
await writeStructure(options);
23+
},
24+
],
25+
[
26+
"Writing README.md",
27+
async () => {
28+
await writeReadme(options);
29+
},
30+
],
31+
]);
2232

2333
if (!options.excludeContributors) {
2434
await withSpinner("Adding contributors to table", async () => {

src/initialize/initializeWithOptions.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { withSpinner } from "../shared/cli/spinners.js";
1+
import { withSpinner, withSpinners } from "../shared/cli/spinners.js";
22
import { OctokitAndOptions } from "../shared/options/readOptions.js";
33
import { addOwnerAsAllContributor } from "../steps/addOwnerAsAllContributor.js";
44
import { clearChangelog } from "../steps/clearChangelog.js";
@@ -15,13 +15,23 @@ export async function initializeWithOptions({
1515
octokit,
1616
options,
1717
}: OctokitAndOptions) {
18-
await withSpinner("Initializing local files", async () => {
19-
await updateLocalFiles(options);
20-
await updateReadme();
21-
await clearChangelog();
22-
await updateAllContributorsTable(options);
23-
await resetGitTags();
24-
});
18+
await withSpinners("Initializing local files", [
19+
[
20+
"Updating local files",
21+
async () => {
22+
await updateLocalFiles(options);
23+
},
24+
],
25+
["Updating README.md", updateReadme],
26+
["Clearing changelog", clearChangelog],
27+
[
28+
"Updating all-contributors table",
29+
async () => {
30+
await updateAllContributorsTable(options);
31+
},
32+
],
33+
["Resetting Git tags", resetGitTags],
34+
]);
2535

2636
if (!options.excludeContributors) {
2737
await withSpinner("Updating existing contributor details", async () => {

src/migrate/migrateWithOptions.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { withSpinner } from "../shared/cli/spinners.js";
1+
import { withSpinner, withSpinners } from "../shared/cli/spinners.js";
22
import { OctokitAndOptions } from "../shared/options/readOptions.js";
33
import { clearUnnecessaryFiles } from "../steps/clearUnnecessaryFiles.js";
44
import { detectExistingContributors } from "../steps/detectExistingContributors.js";
@@ -14,13 +14,33 @@ export async function migrateWithOptions({
1414
octokit,
1515
options,
1616
}: OctokitAndOptions) {
17-
await withSpinner("Migrating repository structure", async () => {
18-
await clearUnnecessaryFiles();
19-
await writeStructure(options);
20-
await writeReadme(options);
21-
await updateLocalFiles(options);
22-
await updateAllContributorsTable(options);
23-
});
17+
await withSpinners("Migrating repository structure", [
18+
["Clearing unnecessary files", clearUnnecessaryFiles],
19+
[
20+
"Writing structure",
21+
async () => {
22+
await writeStructure(options);
23+
},
24+
],
25+
[
26+
"Writing README.md",
27+
async () => {
28+
await writeReadme(options);
29+
},
30+
],
31+
[
32+
"Updating local files",
33+
async () => {
34+
await updateLocalFiles(options);
35+
},
36+
],
37+
[
38+
"Updating all-contributors table",
39+
async () => {
40+
await updateAllContributorsTable(options);
41+
},
42+
],
43+
]);
2444

2545
if (octokit) {
2646
await withSpinner("Initializing GitHub repository", async () => {

src/shared/cli/lines.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
import chalk from "chalk";
22

33
export function logLine(line?: string) {
4-
console.log([chalk.gray("│"), line].filter(Boolean).join(" "));
4+
console.log(makeLine(line));
5+
}
6+
7+
export function logNewSection(line: string) {
8+
logLine();
9+
console.log(`◇ ${line}`);
10+
}
11+
12+
export function makeLine(line: string | undefined) {
13+
return [chalk.gray("│"), line].filter(Boolean).join(" ");
514
}

src/shared/cli/spinners.ts

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
11
import * as prompts from "@clack/prompts";
22
import chalk from "chalk";
3+
import readline from "readline";
34

5+
import { logLine, logNewSection, makeLine } from "./lines.js";
46
import { lowerFirst } from "./lowerFirst.js";
7+
import { startLineWithDots } from "./startLineWithDots.js";
58

69
const s = prompts.spinner();
710

11+
export type SpinnerTask<Return> = () => Promise<Return>;
12+
13+
export type LabeledSpinnerTask<Return> = [string, SpinnerTask<Return>];
14+
815
export async function withSpinner<Return>(
916
label: string,
10-
callback: () => Promise<Return>,
17+
task: SpinnerTask<Return>,
1118
) {
1219
s.start(`${label}...`);
1320

1421
try {
15-
const result = await callback();
22+
const result = await task();
1623

17-
if (result === false) {
18-
s.stop(chalk.yellow(`⚠️ Error ${lowerFirst(label)}.`));
19-
} else {
20-
s.stop(chalk.green(`✅ Passed ${lowerFirst(label)}.`));
21-
}
24+
s.stop(chalk.green(`✅ Passed ${lowerFirst(label)}.`));
2225

2326
return result;
2427
} catch (error) {
@@ -27,3 +30,42 @@ export async function withSpinner<Return>(
2730
throw new Error(`Failed ${lowerFirst(label)}`, { cause: error });
2831
}
2932
}
33+
34+
export async function withSpinners(
35+
label: string,
36+
tasks: LabeledSpinnerTask<void>[],
37+
) {
38+
logNewSection(`${label}...`);
39+
40+
let currentLabel!: string;
41+
let lastLogged!: string;
42+
43+
try {
44+
for (const [label, run] of tasks) {
45+
currentLabel = label;
46+
47+
const line = makeLine(chalk.gray(` - ${label}`));
48+
const stopWriting = startLineWithDots(line);
49+
50+
await run();
51+
52+
const lineLength = stopWriting();
53+
lastLogged = chalk.gray(`${line} ✔️\n`);
54+
55+
readline.clearLine(process.stdout, -1);
56+
readline.moveCursor(process.stdout, -lineLength, 0);
57+
process.stdout.write(lastLogged);
58+
}
59+
60+
readline.moveCursor(process.stdout, -lastLogged.length, -tasks.length - 2);
61+
readline.clearScreenDown(process.stdout);
62+
63+
logNewSection(chalk.green(`✅ Passed ${lowerFirst(label)}.`));
64+
} catch (error) {
65+
const descriptor = `${lowerFirst(label)} > ${lowerFirst(currentLabel)}`;
66+
67+
logLine(chalk.red(`❌ Error ${descriptor}.`));
68+
69+
throw new Error(`Failed ${descriptor}`, { cause: error });
70+
}
71+
}

src/shared/cli/startLineWithDots.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import readline from "readline";
2+
3+
export function startLineWithDots(line: string) {
4+
let dots = 0;
5+
let lastLogged!: string;
6+
7+
function clearLine() {
8+
readline.clearLine(process.stdout, -1);
9+
readline.moveCursor(process.stdout, -lastLogged.length, 0);
10+
}
11+
12+
function writeLine() {
13+
dots = (dots + 1) % 4;
14+
15+
const toLog = `${line}${".".repeat(dots)}`;
16+
17+
process.stdout.write(toLog);
18+
19+
lastLogged = toLog;
20+
21+
return toLog;
22+
}
23+
24+
writeLine();
25+
26+
const timer = setInterval(() => {
27+
clearLine();
28+
writeLine();
29+
dots += 1;
30+
}, 500);
31+
32+
return () => {
33+
clearLine();
34+
clearInterval(timer);
35+
return lastLogged.length;
36+
};
37+
}

0 commit comments

Comments
 (0)