diff --git a/README.md b/README.md
index e2e8c06..81f79d9 100644
--- a/README.md
+++ b/README.md
@@ -100,6 +100,18 @@ class AppServiceProvider extends Provider
}
```
+
+## Triggering Command Failures
+
+If you are using the SSG in a CI environment, you may want to prevent the command from succeeding if any pages aren't generated (e.g. to prevent deployment of an incomplete site).
+
+By default, the command will finish and exit with a success code even if there were un-generated pages. You can tell configure the SSG to fail early on errors, or even on warnings.
+
+```php
+'failures' => 'errors', // or 'warnings'
+```
+
+
## Deployment Examples
These examples assume your workflow will be to author content **locally** and _not_ using the control panel in production.
diff --git a/config/ssg.php b/config/ssg.php
index 5c3d041..a8b917a 100644
--- a/config/ssg.php
+++ b/config/ssg.php
@@ -90,4 +90,17 @@
'directory' => 'img',
],
+ /*
+ |--------------------------------------------------------------------------
+ | Failures
+ |--------------------------------------------------------------------------
+ |
+ | You may configure whether the console command will exit early with a
+ | failure status code when it encounters errors or warnings. You may
+ | want to do this to prevent deployments in CI environments, etc.
+ |
+ */
+
+ 'failures' => false, // 'errors' or 'warnings'
+
];
diff --git a/src/Commands/StaticSiteGenerate.php b/src/Commands/StaticSiteGenerate.php
index 5bc61aa..a944a8d 100644
--- a/src/Commands/StaticSiteGenerate.php
+++ b/src/Commands/StaticSiteGenerate.php
@@ -5,6 +5,7 @@
use Illuminate\Console\Command;
use Statamic\StaticSite\Generator;
use Statamic\Console\RunsInPlease;
+use Statamic\StaticSite\GenerationFailedException;
use Wilderborn\Partyline\Facade as Partyline;
class StaticSiteGenerate extends Command
@@ -55,8 +56,16 @@ public function handle()
$this->comment('You may be able to speed up site generation significantly by installing spatie/fork and using multiple workers (requires PHP 8+).');
}
- $this->generator
- ->workers($workers ?? 1)
- ->generate();
+ try {
+ $this->generator
+ ->workers($workers ?? 1)
+ ->generate();
+ } catch (GenerationFailedException $e) {
+ $this->line($e->getConsoleMessage());
+ $this->error('Static site generation failed.');
+ return 1;
+ }
+
+ return 0;
}
}
diff --git a/src/GenerationFailedException.php b/src/GenerationFailedException.php
new file mode 100644
index 0000000..dce975e
--- /dev/null
+++ b/src/GenerationFailedException.php
@@ -0,0 +1,25 @@
+setConsoleMessage($message);
+ }
+
+ public function setConsoleMessage($message)
+ {
+ $this->consoleMessage = $message;
+
+ return $this;
+ }
+
+ public function getConsoleMessage()
+ {
+ return $this->consoleMessage;
+ }
+}
diff --git a/src/Generator.php b/src/Generator.php
index b53f337..fc8a807 100644
--- a/src/Generator.php
+++ b/src/Generator.php
@@ -31,12 +31,10 @@ class Generator
protected $config;
protected $request;
protected $after;
- protected $count = 0;
- protected $skips = 0;
- protected $warnings = 0;
protected $viewPaths;
protected $extraUrls;
protected $workers = 1;
+ protected $taskResults;
public function __construct(Application $app, Filesystem $files, Router $router, Tasks $tasks)
{
@@ -90,17 +88,8 @@ public function generate()
->clearDirectory()
->createContentFiles()
->createSymlinks()
- ->copyFiles();
-
- Partyline::info('Static site generated into ' . $this->config['destination']);
-
- if ($this->skips) {
- Partyline::warn("[!] {$this->skips}/{$this->count} pages not generated");
- }
-
- if ($this->warnings) {
- Partyline::warn("[!] {$this->warnings}/{$this->count} pages generated with warnings");
- }
+ ->copyFiles()
+ ->outputSummary();
if ($this->after) {
call_user_func($this->after);
@@ -167,6 +156,8 @@ public function copyFiles()
Partyline::line("[✔] $source copied to $dest");
}
+
+ return $this;
}
protected function createContentFiles()
@@ -184,11 +175,33 @@ protected function createContentFiles()
$results = $this->tasks->run(...$closures);
- $this->outputResults($results);
+ if ($this->anyTasksFailed($results)) {
+ throw GenerationFailedException::withConsoleMessage("\x1B[1A\x1B[2K");
+ }
+
+ $this->taskResults = $this->compileTasksResults($results);
+
+ $this->outputTasksResults();
return $this;
}
+ protected function anyTasksFailed($results)
+ {
+ return collect($results)->contains('');
+ }
+
+ protected function compileTasksResults(array $results)
+ {
+ $results = collect($results);
+
+ return [
+ 'count' => $results->sum('count'),
+ 'warnings' => $results->flatMap->warnings,
+ 'errors' => $results->flatMap->errors,
+ ];
+ }
+
protected function gatherContent()
{
Partyline::line('Gathering content to be generated...');
@@ -225,7 +238,8 @@ protected function makeContentGenerationClosures($pages, $request)
{
return $pages->split($this->workers)->map(function ($pages) use ($request) {
return function () use ($pages, $request) {
- $count = $skips = $warnings = 0;
+ $count = 0;
+ $warnings = [];
$errors = [];
foreach ($pages as $page) {
@@ -242,33 +256,50 @@ protected function makeContentGenerationClosures($pages, $request)
try {
$generated = $page->generate($request);
} catch (NotGeneratedException $e) {
- $skips++;
+ if ($this->shouldFail($e)) {
+ throw GenerationFailedException::withConsoleMessage("\x1B[1A\x1B[2K".$e->consoleMessage());
+ }
+
$errors[] = $e->consoleMessage();
continue;
}
if ($generated->hasWarning()) {
- $warnings++;
+ if ($this->shouldFail($generated)) {
+ throw GenerationFailedException::withConsoleMessage($generated->consoleMessage());
+ }
+
+ $warnings[] = $generated->consoleMessage();
}
}
- return compact('count', 'skips', 'warnings', 'errors');
+ return compact('count', 'warnings', 'errors');
};
})->all();
}
- protected function outputResults($results)
+ protected function outputTasksResults()
{
- $results = collect($results);
+ $results = $this->taskResults;
+
+ Partyline::line("\x1B[1A\x1B[2K[✔] Generated {$results['count']} 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']);
+
+ $total = $this->taskResults['count'];
- Partyline::line("\x1B[1A\x1B[2K[✔] Generated {$results->sum('count')} content files");
+ if ($errors = count($this->taskResults['errors'])) {
+ Partyline::warn("[!] {$errors}/{$total} pages not generated");
+ }
- if ($results->sum('skips')) {
- $results->reduce(function ($carry, $item) {
- return $carry->merge($item['errors']);
- }, collect())->each(function ($error) {
- Partyline::line($error);
- });
+ if ($warnings = count($this->taskResults['warnings'])) {
+ Partyline::warn("[!] {$warnings}/{$total} pages generated with warnings");
}
}
@@ -367,4 +398,15 @@ protected function checkConcurrencySupport()
throw new \RuntimeException('To use multiple workers, you must install PHP 8 and spatie/fork.');
}
+
+ protected function shouldFail($item)
+ {
+ $config = $this->config['failures'];
+
+ if ($item instanceof NotGeneratedException) {
+ return in_array($config, ['warnings', 'errors']);
+ }
+
+ return $config === 'warnings';
+ }
}