From 670bad55404d7a736f745f3523a3dfeae56621ff Mon Sep 17 00:00:00 2001 From: Onur Solmaz <2453968+osolmaz@users.noreply.github.com> Date: Tue, 30 Dec 2025 15:17:44 +0300 Subject: [PATCH] Add progressbar --- src/cli/check.ts | 14 ++++++++++---- src/cli/migrate.ts | 6 +++++- src/cli/ui.ts | 30 ++++++++++++++++++++++++++++++ src/migrator.ts | 12 +++++++++++- 4 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/cli/check.ts b/src/cli/check.ts index 5e4550e..6903a8d 100644 --- a/src/cli/check.ts +++ b/src/cli/check.ts @@ -7,7 +7,11 @@ import { type ReferenceUpdateAction, type RenameAction, } from "../migrator.js"; -import { MAX_STEP_FILE_PREVIEW_LINES, limitLines } from "./ui.js"; +import { + MAX_STEP_FILE_PREVIEW_LINES, + createScanProgressBarReporter, + limitLines, +} from "./ui.js"; function getErrorMessage(err: unknown): string { if (err instanceof Error) return err.message; @@ -16,7 +20,10 @@ function getErrorMessage(err: unknown): string { export async function runCheck(): Promise { try { - const plan = await planMigration(); + const scanProgress = createScanProgressBarReporter( + Boolean(process.stdin.isTTY && process.stdout.isTTY), + ); + const plan = await planMigration({ onProgress: scanProgress }); const renames = plan.actions.filter( (a): a is RenameAction => a.type === "rename", @@ -31,8 +38,7 @@ export async function runCheck(): Promise { if ( renames.length === 0 && frontmatters.length === 0 && - references.length === 0 && - true + references.length === 0 ) { process.stdout.write("OK: repo matches SimpleDoc conventions.\n"); return; diff --git a/src/cli/migrate.ts b/src/cli/migrate.ts index 26c5e45..910a1a4 100644 --- a/src/cli/migrate.ts +++ b/src/cli/migrate.ts @@ -23,6 +23,7 @@ import { import type { InstallAction } from "../installer.js"; import { MAX_STEP_FILE_PREVIEW_LINES, + createScanProgressBarReporter, limitLines, noteWrapped, promptConfirm, @@ -182,7 +183,10 @@ export async function runMigrate(options: MigrateOptions): Promise { process.stderr.write( "Planning changes (this may take a while on large repos)...\n", ); - const planAll = await planMigration(); + const scanProgress = createScanProgressBarReporter( + Boolean(process.stdin.isTTY && process.stdout.isTTY), + ); + const planAll = await planMigration({ onProgress: scanProgress }); const installStatus = await getInstallationStatus(planAll.repoRootAbs); const installActionsAll = await buildInstallationActions({ diff --git a/src/cli/ui.ts b/src/cli/ui.ts index d452032..ef2d369 100644 --- a/src/cli/ui.ts +++ b/src/cli/ui.ts @@ -128,3 +128,33 @@ export async function promptSelect( if (isCancel(value)) return null; return value as T; } + +export function createScanProgressBarReporter( + enabled: boolean, +): (info: { phase: "scan"; current: number; total: number }) => void { + if (!enabled) return () => {}; + let lastLineLength = 0; + let lastTick = 0; + return (info) => { + if (info.phase !== "scan") return; + const now = Date.now(); + if (now - lastTick < 80 && info.current !== info.total) return; + lastTick = now; + + const cols = process.stdout.columns ?? 80; + const barWidth = Math.max(10, Math.min(40, cols - 35)); + const ratio = info.total === 0 ? 1 : info.current / info.total; + const filled = Math.min(barWidth, Math.round(barWidth * ratio)); + const empty = Math.max(0, barWidth - filled); + const bar = `${"#".repeat(filled)}${"-".repeat(empty)}`; + const percent = Math.min(100, Math.round(ratio * 100)); + const line = `Scanning repo files: [${bar}] ${info.current}/${info.total} ${percent}%`; + const padded = + line.length >= lastLineLength + ? line + : `${line}${" ".repeat(lastLineLength - line.length)}`; + lastLineLength = padded.length; + process.stderr.write(`\r${padded}`); + if (info.current === info.total) process.stderr.write("\n"); + }; +} diff --git a/src/migrator.ts b/src/migrator.ts index 439cfe4..98038d5 100644 --- a/src/migrator.ts +++ b/src/migrator.ts @@ -323,6 +323,11 @@ export type MigrationPlanOptions = { forceUndatedPaths?: string[]; includeCanonicalRenames?: boolean; normalizeDatePrefixedDocs?: boolean; + onProgress?: (info: { + phase: "scan"; + current: number; + total: number; + }) => void; git?: GitClient; }; @@ -339,6 +344,7 @@ export async function planMigration( const includeCanonicalRenames = options.includeCanonicalRenames ?? true; const normalizeDatePrefixedDocs = options.normalizeDatePrefixedDocs ?? true; const git = options.git ?? createGitClient(); + const onProgress = options.onProgress; const repoRootAbs = await git.getRepoRoot(cwd); const dirty = await git.isDirty(repoRootAbs); @@ -357,8 +363,12 @@ export async function planMigration( const existingAll = await git.listRepoFiles(repoRootAbs); const existingOnDisk: string[] = []; - for (const filePath of existingAll) { + const totalFiles = existingAll.length; + for (let idx = 0; idx < existingAll.length; idx++) { + const filePath = existingAll[idx]!; if (await pathExists(repoRootAbs, filePath)) existingOnDisk.push(filePath); + if (onProgress) + onProgress({ phase: "scan", current: idx + 1, total: totalFiles }); } const existingAllSet = new Set(existingOnDisk);