diff --git a/src/create/createWithOptions.ts b/src/create/createWithOptions.ts index 8b5a47a1e..114f67de8 100644 --- a/src/create/createWithOptions.ts +++ b/src/create/createWithOptions.ts @@ -1,7 +1,7 @@ import * as prompts from "@clack/prompts"; import { $ } from "execa"; -import { withSpinner } from "../shared/cli/spinners.js"; +import { withSpinner, withSpinners } from "../shared/cli/spinners.js"; import { doesRepositoryExist } from "../shared/doesRepositoryExist.js"; import { OctokitAndOptions } from "../shared/options/readOptions.js"; import { addToolAllContributors } from "../steps/addToolAllContributors.js"; @@ -15,10 +15,20 @@ export async function createWithOptions({ octokit, options, }: OctokitAndOptions) { - await withSpinner("Creating repository structure", async () => { - await writeStructure(options); - await writeReadme(options); - }); + await withSpinners("Creating repository structure", [ + [ + "Writing structure", + async () => { + await writeStructure(options); + }, + ], + [ + "Writing README.md", + async () => { + await writeReadme(options); + }, + ], + ]); if (!options.excludeContributors) { await withSpinner("Adding contributors to table", async () => { diff --git a/src/initialize/initializeWithOptions.ts b/src/initialize/initializeWithOptions.ts index 3a3f25ca4..684da0954 100644 --- a/src/initialize/initializeWithOptions.ts +++ b/src/initialize/initializeWithOptions.ts @@ -1,4 +1,4 @@ -import { withSpinner } from "../shared/cli/spinners.js"; +import { withSpinner, withSpinners } from "../shared/cli/spinners.js"; import { OctokitAndOptions } from "../shared/options/readOptions.js"; import { addOwnerAsAllContributor } from "../steps/addOwnerAsAllContributor.js"; import { clearChangelog } from "../steps/clearChangelog.js"; @@ -15,13 +15,23 @@ export async function initializeWithOptions({ octokit, options, }: OctokitAndOptions) { - await withSpinner("Initializing local files", async () => { - await updateLocalFiles(options); - await updateReadme(); - await clearChangelog(); - await updateAllContributorsTable(options); - await resetGitTags(); - }); + await withSpinners("Initializing local files", [ + [ + "Updating local files", + async () => { + await updateLocalFiles(options); + }, + ], + ["Updating README.md", updateReadme], + ["Clearing changelog", clearChangelog], + [ + "Updating all-contributors table", + async () => { + await updateAllContributorsTable(options); + }, + ], + ["Resetting Git tags", resetGitTags], + ]); if (!options.excludeContributors) { await withSpinner("Updating existing contributor details", async () => { diff --git a/src/migrate/migrateWithOptions.ts b/src/migrate/migrateWithOptions.ts index 84829bc72..24599c865 100644 --- a/src/migrate/migrateWithOptions.ts +++ b/src/migrate/migrateWithOptions.ts @@ -1,4 +1,4 @@ -import { withSpinner } from "../shared/cli/spinners.js"; +import { withSpinner, withSpinners } from "../shared/cli/spinners.js"; import { OctokitAndOptions } from "../shared/options/readOptions.js"; import { clearUnnecessaryFiles } from "../steps/clearUnnecessaryFiles.js"; import { detectExistingContributors } from "../steps/detectExistingContributors.js"; @@ -14,13 +14,33 @@ export async function migrateWithOptions({ octokit, options, }: OctokitAndOptions) { - await withSpinner("Migrating repository structure", async () => { - await clearUnnecessaryFiles(); - await writeStructure(options); - await writeReadme(options); - await updateLocalFiles(options); - await updateAllContributorsTable(options); - }); + await withSpinners("Migrating repository structure", [ + ["Clearing unnecessary files", clearUnnecessaryFiles], + [ + "Writing structure", + async () => { + await writeStructure(options); + }, + ], + [ + "Writing README.md", + async () => { + await writeReadme(options); + }, + ], + [ + "Updating local files", + async () => { + await updateLocalFiles(options); + }, + ], + [ + "Updating all-contributors table", + async () => { + await updateAllContributorsTable(options); + }, + ], + ]); if (octokit) { await withSpinner("Initializing GitHub repository", async () => { diff --git a/src/shared/cli/lines.ts b/src/shared/cli/lines.ts index 0790606a8..d1075ad4c 100644 --- a/src/shared/cli/lines.ts +++ b/src/shared/cli/lines.ts @@ -1,5 +1,14 @@ import chalk from "chalk"; export function logLine(line?: string) { - console.log([chalk.gray("│"), line].filter(Boolean).join(" ")); + console.log(makeLine(line)); +} + +export function logNewSection(line: string) { + logLine(); + console.log(`◇ ${line}`); +} + +export function makeLine(line: string | undefined) { + return [chalk.gray("│"), line].filter(Boolean).join(" "); } diff --git a/src/shared/cli/spinners.ts b/src/shared/cli/spinners.ts index ac89b96c5..adb8fa929 100644 --- a/src/shared/cli/spinners.ts +++ b/src/shared/cli/spinners.ts @@ -1,24 +1,27 @@ import * as prompts from "@clack/prompts"; import chalk from "chalk"; +import readline from "readline"; +import { logLine, logNewSection, makeLine } from "./lines.js"; import { lowerFirst } from "./lowerFirst.js"; +import { startLineWithDots } from "./startLineWithDots.js"; const s = prompts.spinner(); +export type SpinnerTask = () => Promise; + +export type LabeledSpinnerTask = [string, SpinnerTask]; + export async function withSpinner( label: string, - callback: () => Promise, + task: SpinnerTask, ) { s.start(`${label}...`); try { - const result = await callback(); + const result = await task(); - if (result === false) { - s.stop(chalk.yellow(`⚠️ Error ${lowerFirst(label)}.`)); - } else { - s.stop(chalk.green(`✅ Passed ${lowerFirst(label)}.`)); - } + s.stop(chalk.green(`✅ Passed ${lowerFirst(label)}.`)); return result; } catch (error) { @@ -27,3 +30,42 @@ export async function withSpinner( throw new Error(`Failed ${lowerFirst(label)}`, { cause: error }); } } + +export async function withSpinners( + label: string, + tasks: LabeledSpinnerTask[], +) { + logNewSection(`${label}...`); + + let currentLabel!: string; + let lastLogged!: string; + + try { + for (const [label, run] of tasks) { + currentLabel = label; + + const line = makeLine(chalk.gray(` - ${label}`)); + const stopWriting = startLineWithDots(line); + + await run(); + + const lineLength = stopWriting(); + lastLogged = chalk.gray(`${line} ✔️\n`); + + readline.clearLine(process.stdout, -1); + readline.moveCursor(process.stdout, -lineLength, 0); + process.stdout.write(lastLogged); + } + + readline.moveCursor(process.stdout, -lastLogged.length, -tasks.length - 2); + readline.clearScreenDown(process.stdout); + + logNewSection(chalk.green(`✅ Passed ${lowerFirst(label)}.`)); + } catch (error) { + const descriptor = `${lowerFirst(label)} > ${lowerFirst(currentLabel)}`; + + logLine(chalk.red(`❌ Error ${descriptor}.`)); + + throw new Error(`Failed ${descriptor}`, { cause: error }); + } +} diff --git a/src/shared/cli/startLineWithDots.ts b/src/shared/cli/startLineWithDots.ts new file mode 100644 index 000000000..9992b8745 --- /dev/null +++ b/src/shared/cli/startLineWithDots.ts @@ -0,0 +1,37 @@ +import readline from "readline"; + +export function startLineWithDots(line: string) { + let dots = 0; + let lastLogged!: string; + + function clearLine() { + readline.clearLine(process.stdout, -1); + readline.moveCursor(process.stdout, -lastLogged.length, 0); + } + + function writeLine() { + dots = (dots + 1) % 4; + + const toLog = `${line}${".".repeat(dots)}`; + + process.stdout.write(toLog); + + lastLogged = toLog; + + return toLog; + } + + writeLine(); + + const timer = setInterval(() => { + clearLine(); + writeLine(); + dots += 1; + }, 500); + + return () => { + clearLine(); + clearInterval(timer); + return lastLogged.length; + }; +}