From 96eb226e8f4dcdc64496d05048d53c18c6578029 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 7 Oct 2025 11:47:25 +0100 Subject: [PATCH 1/7] wip --- src/Commands/StaticSiteGenerate.php | 15 ++++++++------- src/Generator.php | 29 ++++++++++++++++------------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/Commands/StaticSiteGenerate.php b/src/Commands/StaticSiteGenerate.php index b452ca3..9d4cf86 100644 --- a/src/Commands/StaticSiteGenerate.php +++ b/src/Commands/StaticSiteGenerate.php @@ -8,6 +8,7 @@ use Statamic\StaticSite\GenerationFailedException; use Statamic\StaticSite\Generator; use Wilderborn\Partyline\Facade as Partyline; +use function Laravel\Prompts\confirm; class StaticSiteGenerate extends Command { @@ -61,19 +62,19 @@ public function handle() Partyline::bind($this); if (config('statamic.editions.pro') && ! config('statamic.system.license_key')) { - $this->error('Statamic Pro is enabled but no site license was found.'); - $this->warn('Please set a valid Statamic License Key in your .env file.'); + $this->components->error('Statamic Pro is enabled but no site license was found.'); + $this->components->warn('Please set a valid Statamic License Key in your .env file.'); $confirmationText = 'By continuing you agree that this build is for testing purposes only. Do you wish to continue?'; - if (! $this->option('no-interaction') && ! $this->confirm($confirmationText)) { - $this->line('Static site generation canceled.'); + if (! $this->option('no-interaction') && ! confirm($confirmationText)) { + $this->components->error('Static site generation canceled.'); return 0; } } if (! $workers = $this->option('workers')) { - $this->comment('You may be able to speed up site generation significantly by installing spatie/fork and using multiple workers (requires PHP 8+).'); + $this->components->info('You may be able to speed up site generation significantly by installing spatie/fork and using multiple workers.'); } try { @@ -82,8 +83,8 @@ public function handle() ->disableClear($this->option('disable-clear') ?? false) ->generate($this->argument('urls') ?: '*'); } catch (GenerationFailedException $e) { - $this->line($e->getConsoleMessage()); - $this->error('Static site generation failed.'); + $this->components->error($e->getConsoleMessage()); + $this->components->error('Static site generation failed.'); return 1; } diff --git a/src/Generator.php b/src/Generator.php index a4d1593..9f679e5 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -26,6 +26,7 @@ use Statamic\Statamic; use Statamic\Support\Str; use Wilderborn\Partyline\Facade as Partyline; +use function Laravel\Prompts\spin; class Generator { @@ -154,10 +155,10 @@ public function createSymlinks() $dest = $this->config['destination'].'/'.$dest; if ($this->files->exists($dest)) { - Partyline::line("Symlink not created. $dest already exists."); + Partyline::outputComponents()->warn("Symlink not created. $dest already exists."); } else { $this->files->link($source, $dest); - Partyline::line("[✔] $source symlinked to $dest"); + Partyline::outputComponents()->success("$source symlinked to $dest"); } } @@ -175,7 +176,7 @@ public function copyFiles() $this->files->copyDirectory($source, $dest); } - Partyline::line("[✔] $source copied to $dest"); + Partyline::outputComponents()->success("$source copied to $dest"); } return $this; @@ -236,11 +237,13 @@ protected function gatherContent($urls = '*') ->reject(fn ($page) => $this->shouldRejectPage($page, true)); } - Partyline::line('Gathering content to be generated...'); - - $pages = $this->gatherAllPages(); + $pages = spin( + fn () => $this->gatherAllPages(), + 'Gathering content to be generated...', + ); - Partyline::line("\x1B[1A\x1B[2K[✔] Gathered content to be generated"); + // todo + Partyline::outputComponents()->info('Gathered content to be generated'); return $pages; } @@ -282,6 +285,7 @@ protected function makeContentGenerationClosures($pages, $request) $request->setPage($page); +// Partyline::outputComponents()->twoColumnDetail($page->url(), 'Generating...'); Partyline::line("\x1B[1A\x1B[2KGenerating ".$page->url()); try { @@ -320,24 +324,23 @@ protected function outputTasksResults() $successCount = $results['count'] - $results['errors']->count(); - Partyline::line("\x1B[1A\x1B[2K[✔] Generated {$successCount} content files"); + Partyline::outputComponents()->success("Generated {$successCount} content files"); $results['warnings']->merge($results['errors'])->each(fn ($error) => Partyline::line($error)); } protected function outputSummary() { - Partyline::info(''); - Partyline::info('Static site generated into '.$this->config['destination']); + Partyline::outputComponents()->success('Static site generated into '.$this->config['destination']); $total = $this->taskResults['count']; if ($errors = count($this->taskResults['errors'])) { - Partyline::warn("[!] {$errors}/{$total} pages not generated"); + Partyline::outputComponents()->warn("{$errors}/{$total} pages not generated"); } if ($warnings = count($this->taskResults['warnings'])) { - Partyline::warn("[!] {$warnings}/{$total} pages generated with warnings"); + Partyline::outputComponents()->warn("{$warnings}/{$total} pages generated with warnings"); } } @@ -438,7 +441,7 @@ protected function checkConcurrencySupport() return; } - throw new \RuntimeException('To use multiple workers, you must install PHP 8 and spatie/fork.'); + throw new \RuntimeException('To use multiple workers, you must install spatie/fork.'); } protected function shouldFail($item) From c932a47bb348e00c5b9f38697a724d08ad980f14 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 7 Oct 2025 11:57:05 +0100 Subject: [PATCH 2/7] wip --- src/Generator.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Generator.php b/src/Generator.php index 9f679e5..3344b91 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -196,7 +196,7 @@ protected function createContentFiles($urls = '*') $pages = $pages->shuffle(); } - Partyline::line("Generating {$pages->count()} content files..."); + Partyline::outputComponents()->info("Generating {$pages->count()} content files..."); $closures = $this->makeContentGenerationClosures($pages, $request); @@ -242,7 +242,6 @@ protected function gatherContent($urls = '*') 'Gathering content to be generated...', ); - // todo Partyline::outputComponents()->info('Gathered content to be generated'); return $pages; @@ -285,8 +284,7 @@ protected function makeContentGenerationClosures($pages, $request) $request->setPage($page); -// Partyline::outputComponents()->twoColumnDetail($page->url(), 'Generating...'); - Partyline::line("\x1B[1A\x1B[2KGenerating ".$page->url()); + Partyline::line(" Generating ".$page->url()); try { $generated = $page->generate($request); @@ -324,9 +322,11 @@ protected function outputTasksResults() $successCount = $results['count'] - $results['errors']->count(); + Partyline::newLine(); Partyline::outputComponents()->success("Generated {$successCount} content files"); - $results['warnings']->merge($results['errors'])->each(fn ($error) => Partyline::line($error)); + $results['warnings']->each(fn ($warning) => Partyline::outputComponents()->warn($warning)); + $results['errors']->each(fn ($error) => Partyline::outputComponents()->error($error)); } protected function outputSummary() From 091fe324219c7f405f90c8bfb416788907386753 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 7 Oct 2025 12:02:25 +0100 Subject: [PATCH 3/7] wip --- src/Generator.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Generator.php b/src/Generator.php index 3344b91..864d5ca 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -155,10 +155,10 @@ public function createSymlinks() $dest = $this->config['destination'].'/'.$dest; if ($this->files->exists($dest)) { - Partyline::outputComponents()->warn("Symlink not created. $dest already exists."); + Partyline::outputComponents()->twoColumnDetail("$source symlinked to $dest", 'SKIPPED. SYMLINK ALREADY EXISTS'); } else { $this->files->link($source, $dest); - Partyline::outputComponents()->success("$source symlinked to $dest"); + Partyline::outputComponents()->twoColumnDetail("$source symlinked to $dest", 'SUCCESS'); } } @@ -176,7 +176,7 @@ public function copyFiles() $this->files->copyDirectory($source, $dest); } - Partyline::outputComponents()->success("$source copied to $dest"); + Partyline::outputComponents()->twoColumnDetail("$source copied to $dest", 'SUCCESS'); } return $this; From 74d997faf6f15b8298a7ce097b1a2028edeee0bd Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 7 Oct 2025 12:41:31 +0100 Subject: [PATCH 4/7] wip --- src/Generator.php | 84 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 12 deletions(-) diff --git a/src/Generator.php b/src/Generator.php index 864d5ca..38eead0 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -224,8 +224,10 @@ protected function compileTasksResults(array $results) return [ 'count' => count($this->earlyTaskErrors) + $results->sum('count'), - 'warnings' => $results->flatMap->warnings, - 'errors' => collect($this->earlyTaskErrors)->merge($results->flatMap->errors), +// 'warnings' => $results->flatMap->warnings, + 'warnings' => 0, +// 'errors' => collect($this->earlyTaskErrors)->merge($results->flatMap->errors), + 'errors' => collect(), ]; } @@ -284,7 +286,7 @@ protected function makeContentGenerationClosures($pages, $request) $request->setPage($page); - Partyline::line(" Generating ".$page->url()); + $this->outputProgress($page->url(), 'GENERATING'); try { $generated = $page->generate($request); @@ -293,6 +295,10 @@ protected function makeContentGenerationClosures($pages, $request) return $e->consoleMessage(); } + // Clear line, then output FAILED status with newline + $this->clearLine(); + Partyline::outputComponents()->twoColumnDetail($page->url(), 'FAILED'); + $errors[] = $e->consoleMessage(); continue; @@ -308,6 +314,10 @@ protected function makeContentGenerationClosures($pages, $request) $warnings[] = $generated->consoleMessage(); } + // Clear line, then output SUCCESS status with newline + $this->clearLine(); + Partyline::outputComponents()->twoColumnDetail($page->url(), 'SUCCESS'); + Blink::flush(); } @@ -325,8 +335,8 @@ protected function outputTasksResults() Partyline::newLine(); Partyline::outputComponents()->success("Generated {$successCount} content files"); - $results['warnings']->each(fn ($warning) => Partyline::outputComponents()->warn($warning)); - $results['errors']->each(fn ($error) => Partyline::outputComponents()->error($error)); +// $results['warnings']->each(fn ($warning) => Partyline::outputComponents()->warn($warning)); +// $results['errors']->each(fn ($error) => Partyline::outputComponents()->error($error)); } protected function outputSummary() @@ -335,13 +345,13 @@ protected function outputSummary() $total = $this->taskResults['count']; - if ($errors = count($this->taskResults['errors'])) { - Partyline::outputComponents()->warn("{$errors}/{$total} pages not generated"); - } - - if ($warnings = count($this->taskResults['warnings'])) { - Partyline::outputComponents()->warn("{$warnings}/{$total} pages generated with warnings"); - } +// if ($errors = count($this->taskResults['errors'])) { +// Partyline::outputComponents()->warn("{$errors}/{$total} pages not generated"); +// } +// +// if ($warnings = count($this->taskResults['warnings'])) { +// Partyline::outputComponents()->warn("{$warnings}/{$total} pages generated with warnings"); +// } } protected function entries() @@ -503,4 +513,54 @@ protected function getToStringFormat(): string|\Closure|null return Arr::get($factory->invoke($date)->getSettings(), 'toStringFormat'); } + + /** + * Output a two-column detail line without a trailing newline. + * This allows the line to be overwritten later. + */ + protected function outputProgress(string $first, string $second): void + { + // Extract the text from color tags + preg_match('/]+);options=bold>([^<]+)<\/>/', $second, $matches); + $color = $matches[1] ?? 'white'; + $text = $matches[2] ?? $second; + + // ANSI color codes + $colors = [ + 'blue' => "\033[34;1m", + 'green' => "\033[32;1m", + 'red' => "\033[31;1m", + 'reset' => "\033[0m", + 'gray' => "\033[90m", + ]; + + // Format: " first .......... STATUS" + // The actual visible width calculation + $firstLen = strlen($first); + $textLen = strlen($text); + + // Calculate dots to match twoColumnDetail width + // Subtract a bit more to account for spacing and ensure alignment + $dotsLen = max(1, 150 - $firstLen - $textLen - 6); + + $line = sprintf(" %s %s%s%s %s%s%s", + $first, + $colors['gray'], + str_repeat('.', $dotsLen), + $colors['reset'], + $colors[$color] ?? '', + $text, + $colors['reset'] + ); + + fwrite(STDERR, $line); + } + + /** + * Clear the current line + */ + protected function clearLine(): void + { + fwrite(STDERR, "\r\x1B[2K"); + } } From 0054780b805cfaf59942db3cc2208853ae421cdb Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 7 Oct 2025 13:01:47 +0100 Subject: [PATCH 5/7] wip --- src/Generator.php | 80 ++++++++++++++--------------------------------- 1 file changed, 24 insertions(+), 56 deletions(-) diff --git a/src/Generator.php b/src/Generator.php index 38eead0..7e10ffb 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -224,10 +224,8 @@ protected function compileTasksResults(array $results) return [ 'count' => count($this->earlyTaskErrors) + $results->sum('count'), -// 'warnings' => $results->flatMap->warnings, - 'warnings' => 0, -// 'errors' => collect($this->earlyTaskErrors)->merge($results->flatMap->errors), - 'errors' => collect(), + 'warnings' => $results->flatMap->warnings, + 'errors' => collect($this->earlyTaskErrors)->merge($results->flatMap->errors), ]; } @@ -286,7 +284,7 @@ protected function makeContentGenerationClosures($pages, $request) $request->setPage($page); - $this->outputProgress($page->url(), 'GENERATING'); + $this->writeGeneratingLine($page->url()); try { $generated = $page->generate($request); @@ -295,8 +293,7 @@ protected function makeContentGenerationClosures($pages, $request) return $e->consoleMessage(); } - // Clear line, then output FAILED status with newline - $this->clearLine(); + $this->clearCurrentLine(); Partyline::outputComponents()->twoColumnDetail($page->url(), 'FAILED'); $errors[] = $e->consoleMessage(); @@ -314,8 +311,7 @@ protected function makeContentGenerationClosures($pages, $request) $warnings[] = $generated->consoleMessage(); } - // Clear line, then output SUCCESS status with newline - $this->clearLine(); + $this->clearCurrentLine(); Partyline::outputComponents()->twoColumnDetail($page->url(), 'SUCCESS'); Blink::flush(); @@ -335,8 +331,8 @@ protected function outputTasksResults() Partyline::newLine(); Partyline::outputComponents()->success("Generated {$successCount} content files"); -// $results['warnings']->each(fn ($warning) => Partyline::outputComponents()->warn($warning)); -// $results['errors']->each(fn ($error) => Partyline::outputComponents()->error($error)); + $results['warnings']->each(fn ($warning) => Partyline::outputComponents()->warn($warning)); + $results['errors']->each(fn ($error) => Partyline::outputComponents()->error($error)); } protected function outputSummary() @@ -345,13 +341,13 @@ protected function outputSummary() $total = $this->taskResults['count']; -// if ($errors = count($this->taskResults['errors'])) { -// Partyline::outputComponents()->warn("{$errors}/{$total} pages not generated"); -// } -// -// if ($warnings = count($this->taskResults['warnings'])) { -// Partyline::outputComponents()->warn("{$warnings}/{$total} pages generated with warnings"); -// } + if ($errors = count($this->taskResults['errors'])) { + Partyline::outputComponents()->warn("{$errors}/{$total} pages not generated"); + } + + if ($warnings = count($this->taskResults['warnings'])) { + Partyline::outputComponents()->warn("{$warnings}/{$total} pages generated with warnings"); + } } protected function entries() @@ -515,51 +511,23 @@ protected function getToStringFormat(): string|\Closure|null } /** - * Output a two-column detail line without a trailing newline. - * This allows the line to be overwritten later. + * Outputs a "Generating" line to the terminal. Very similar output-wise to Laravel's + * ->twoColumnDetail() method, but it allows for replacing the line later, for + * success/error statuses. */ - protected function outputProgress(string $first, string $second): void - { - // Extract the text from color tags - preg_match('/]+);options=bold>([^<]+)<\/>/', $second, $matches); - $color = $matches[1] ?? 'white'; - $text = $matches[2] ?? $second; - - // ANSI color codes - $colors = [ - 'blue' => "\033[34;1m", - 'green' => "\033[32;1m", - 'red' => "\033[31;1m", - 'reset' => "\033[0m", - 'gray' => "\033[90m", - ]; + private function writeGeneratingLine(string $url): void + { + $dotsLen = max(1, 150 - strlen($url) - strlen('GENERATING') - 6); - // Format: " first .......... STATUS" - // The actual visible width calculation - $firstLen = strlen($first); - $textLen = strlen($text); - - // Calculate dots to match twoColumnDetail width - // Subtract a bit more to account for spacing and ensure alignment - $dotsLen = max(1, 150 - $firstLen - $textLen - 6); - - $line = sprintf(" %s %s%s%s %s%s%s", - $first, - $colors['gray'], - str_repeat('.', $dotsLen), - $colors['reset'], - $colors[$color] ?? '', - $text, - $colors['reset'] + $line = sprintf(" %s \033[90m%s\033[0m \033[34;1mGENERATING\033[0m", + $url, + str_repeat('.', $dotsLen) ); fwrite(STDERR, $line); } - /** - * Clear the current line - */ - protected function clearLine(): void + private function clearCurrentLine(): void { fwrite(STDERR, "\r\x1B[2K"); } From 1c49364f496222ad5c63b70702d5da00c9f83750 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Tue, 7 Oct 2025 13:07:35 +0100 Subject: [PATCH 6/7] wip --- src/Generator.php | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Generator.php b/src/Generator.php index 7e10ffb..0fe5860 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -284,7 +284,11 @@ protected function makeContentGenerationClosures($pages, $request) $request->setPage($page); - $this->writeGeneratingLine($page->url()); + // Only write the "Generating" line when using a single worker. + // Otherwise, the output will be messed up. + if ($this->workers === 1) { + $this->writeGeneratingLine($page->url()); + } try { $generated = $page->generate($request); @@ -293,7 +297,10 @@ protected function makeContentGenerationClosures($pages, $request) return $e->consoleMessage(); } - $this->clearCurrentLine(); + if ($this->workers === 1) { + $this->clearCurrentLine(); + } + Partyline::outputComponents()->twoColumnDetail($page->url(), 'FAILED'); $errors[] = $e->consoleMessage(); @@ -311,7 +318,10 @@ protected function makeContentGenerationClosures($pages, $request) $warnings[] = $generated->consoleMessage(); } - $this->clearCurrentLine(); + if ($this->workers === 1) { + $this->clearCurrentLine(); + } + Partyline::outputComponents()->twoColumnDetail($page->url(), 'SUCCESS'); Blink::flush(); From d05dac00cfed3979cbb023d2b6ddda6a7eab7c06 Mon Sep 17 00:00:00 2001 From: duncanmcclean Date: Tue, 7 Oct 2025 12:08:10 +0000 Subject: [PATCH 7/7] Fix styling --- src/Commands/StaticSiteGenerate.php | 3 ++- src/Generator.php | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Commands/StaticSiteGenerate.php b/src/Commands/StaticSiteGenerate.php index 9d4cf86..639dbb5 100644 --- a/src/Commands/StaticSiteGenerate.php +++ b/src/Commands/StaticSiteGenerate.php @@ -8,6 +8,7 @@ use Statamic\StaticSite\GenerationFailedException; use Statamic\StaticSite\Generator; use Wilderborn\Partyline\Facade as Partyline; + use function Laravel\Prompts\confirm; class StaticSiteGenerate extends Command @@ -74,7 +75,7 @@ public function handle() } if (! $workers = $this->option('workers')) { - $this->components->info('You may be able to speed up site generation significantly by installing spatie/fork and using multiple workers.'); + $this->components->info('You may be able to speed up site generation significantly by installing spatie/fork and using multiple workers.'); } try { diff --git a/src/Generator.php b/src/Generator.php index 0fe5860..4431e6c 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -26,6 +26,7 @@ use Statamic\Statamic; use Statamic\Support\Str; use Wilderborn\Partyline\Facade as Partyline; + use function Laravel\Prompts\spin; class Generator