Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions bin/dev-router.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Glaze\Http\Middleware\ErrorHandlingMiddleware;
use Glaze\Http\Middleware\PublicAssetMiddleware;
use Glaze\Http\Middleware\StaticAssetMiddleware;
use Glaze\Http\StaticPageRequestHandler;

require dirname(__DIR__) . '/vendor/autoload.php';

Expand All @@ -28,6 +29,7 @@
}

$includeDrafts = getenv('GLAZE_INCLUDE_DRAFTS') === '1';
$staticMode = getenv('GLAZE_STATIC_MODE') === '1';

$application = new Application();
$application->bootstrap();
Expand All @@ -45,8 +47,14 @@
$staticAssetMiddleware = $container->get(StaticAssetMiddleware::class);
/** @var \Glaze\Http\Middleware\ContentAssetMiddleware $contentAssetMiddleware */
$contentAssetMiddleware = $container->get(ContentAssetMiddleware::class);
/** @var \Glaze\Http\DevPageRequestHandler $devPageRequestHandler */
$devPageRequestHandler = $container->get(DevPageRequestHandler::class);

if ($staticMode) {
/** @var \Glaze\Http\StaticPageRequestHandler $fallbackHandler */
$fallbackHandler = $container->get(StaticPageRequestHandler::class);
} else {
/** @var \Glaze\Http\DevPageRequestHandler $fallbackHandler */
$fallbackHandler = $container->get(DevPageRequestHandler::class);
}

$requestMethod = $_SERVER['REQUEST_METHOD'] ?? null;
if (!is_string($requestMethod) || $requestMethod === '') {
Expand Down Expand Up @@ -74,7 +82,7 @@
$queue->add($staticAssetMiddleware);
$queue->add($contentAssetMiddleware);

$response = (new Runner())->run($queue, $request, $devPageRequestHandler);
$response = (new Runner())->run($queue, $request, $fallbackHandler);

(new ResponseEmitter())->emit($response);

Expand Down
61 changes: 28 additions & 33 deletions src/Command/ServeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,15 @@ public function execute(Arguments $args, ConsoleIo $io): int

$isStaticMode = (bool)$args->getOption('static');
$includeDrafts = !$isStaticMode || (bool)$args->getOption('drafts');
$viteConfiguration = $this->resolveViteConfiguration($args);
/** @var array{enabled: bool, host: string, port: int, command: string} $viteConfiguration */

if ($viteConfiguration['enabled'] && $isStaticMode) {
if ((bool)$args->getOption('vite') && $isStaticMode) {
$io->err('<error>--vite can only be used in live mode (without --static).</error>');

return self::CODE_ERROR;
}

$docRoot = $isStaticMode ? $projectRoot . DIRECTORY_SEPARATOR . 'public' : $projectRoot;
$viteConfiguration = $this->resolveViteConfiguration($args, $isStaticMode);
/** @var array{enabled: bool, host: string, port: int, command: string} $viteConfiguration */

if ((bool)$args->getOption('build')) {
if (!$isStaticMode) {
Expand All @@ -158,8 +157,9 @@ public function execute(Arguments $args, ConsoleIo $io): int
}
}

if (!is_dir($docRoot)) {
$io->err(sprintf('<error>Public directory not found: %s</error>', $docRoot));
$staticPublicDir = $projectRoot . DIRECTORY_SEPARATOR . 'public';
if ($isStaticMode && !is_dir($staticPublicDir)) {
$io->err(sprintf('<error>Public directory not found: %s</error>', $staticPublicDir));

return self::CODE_ERROR;
}
Expand All @@ -168,11 +168,9 @@ public function execute(Arguments $args, ConsoleIo $io): int
$phpServerConfiguration = $this->resolvePhpServerConfiguration(
$args,
$projectRoot,
$docRoot,
$isStaticMode,
$verbose,
);
/** @var array{host: string, port: int, docRoot: string, projectRoot: string, staticMode: bool, streamOutput: bool} $phpServerConfiguration */
/** @var array{host: string, port: int, docRoot: string, projectRoot: string, streamOutput: bool} $phpServerConfiguration */
$this->phpServerProcess->assertCanRun($phpServerConfiguration);
} catch (InvalidArgumentException $invalidArgumentException) {
$io->err(sprintf('<error>%s</error>', $invalidArgumentException->getMessage()));
Expand All @@ -183,7 +181,7 @@ public function execute(Arguments $args, ConsoleIo $io): int
$address = $this->phpServerProcess->address($phpServerConfiguration);
if ($verbose) {
if ($isStaticMode) {
$io->out(sprintf('Serving static output from %s at http://%s', $docRoot, $address));
$io->out(sprintf('Serving static output from %s at http://%s', $staticPublicDir, $address));
} else {
$io->out(sprintf('Serving live templates/content from %s at http://%s', $projectRoot, $address));
}
Expand All @@ -197,12 +195,9 @@ public function execute(Arguments $args, ConsoleIo $io): int
$io->out(sprintf('<info>Glaze development server:</info> http://%s', $address));
}

$previousEnvironment = [];
if (!$isStaticMode) {
$previousEnvironment = $this->applyEnvironment(
$this->buildLiveEnvironment($projectRoot, $includeDrafts, $viteConfiguration),
);
}
$previousEnvironment = $this->applyEnvironment(
$this->buildRouterEnvironment($projectRoot, $includeDrafts, $viteConfiguration, $isStaticMode),
);

$viteProcess = null;
if (!$isStaticMode && $viteConfiguration['enabled']) {
Expand Down Expand Up @@ -239,30 +234,33 @@ static function (string $type, string $buffer) use ($io): void {
);
} finally {
$this->viteServeProcess->stop($viteProcess);

if (!$isStaticMode) {
$this->restoreEnvironment($previousEnvironment);
}
$this->restoreEnvironment($previousEnvironment);
}

return $exitCode;
}

/**
* Build environment variables for live router execution.
* Build environment variables for the router process.
*
* Sets all variables the dev-router reads at boot time. GLAZE_STATIC_MODE
* tells the router to use StaticPageRequestHandler instead of live rendering.
*
* @param string $projectRoot Project root directory.
* @param bool $includeDrafts Whether draft pages should be included.
* @param array{enabled: bool, host: string, port: int, command: string} $viteConfiguration Vite runtime configuration.
* @param bool $isStaticMode Whether static serving mode is active.
* @return array<string, string>
*/
protected function buildLiveEnvironment(
protected function buildRouterEnvironment(
string $projectRoot,
bool $includeDrafts,
array $viteConfiguration,
bool $isStaticMode,
): array {
return [
'GLAZE_PROJECT_ROOT' => $projectRoot,
'GLAZE_STATIC_MODE' => $isStaticMode ? '1' : '0',
'GLAZE_INCLUDE_DRAFTS' => $includeDrafts ? '1' : '0',
'GLAZE_VITE_ENABLED' => $viteConfiguration['enabled'] ? '1' : '0',
'GLAZE_VITE_URL' => $viteConfiguration['enabled'] ? $this->viteServeProcess->url($viteConfiguration) : '',
Expand All @@ -274,19 +272,21 @@ protected function buildLiveEnvironment(
*
* Reads devServer.vite values from Configure (populated by the
* ProjectConfigurationReader call in execute()) and merges with
* any CLI overrides.
* any CLI overrides. When $isStaticMode is true, vite is always disabled
* regardless of configuration, since static serving does not use Vite.
*
* @param \Cake\Console\Arguments $args Parsed CLI arguments.
* @param bool $isStaticMode Whether static mode is active.
* @return array{enabled: bool, host: string, port: int, command: string}
*/
protected function resolveViteConfiguration(Arguments $args): array
protected function resolveViteConfiguration(Arguments $args, bool $isStaticMode = false): array
{
$viteConfig = Configure::read('devServer.vite');
if (!is_array($viteConfig)) {
$viteConfig = [];
}

$enabledFromConfig = is_bool($viteConfig['enabled'] ?? null) && $viteConfig['enabled'];
$enabledFromConfig = !$isStaticMode && is_bool($viteConfig['enabled'] ?? null) && $viteConfig['enabled'];
$enabled = (bool)$args->getOption('vite') || $enabledFromConfig;

$host = Normalization::optionalString($args->getOption('vite-host'))
Expand Down Expand Up @@ -319,17 +319,13 @@ protected function resolveViteConfiguration(Arguments $args): array
* Resolve PHP server runtime configuration from Configure state and CLI options.
*
* @param \Cake\Console\Arguments $args Parsed CLI arguments.
* @param string $projectRoot Project root directory.
* @param string $docRoot PHP server document root.
* @param bool $isStaticMode Whether static mode is enabled.
* @param string $projectRoot Project root directory (used as both docRoot and projectRoot).
* @param bool $streamOutput Whether process output should be streamed.
* @return array{host: string, port: int, docRoot: string, projectRoot: string, staticMode: bool, streamOutput: bool}
* @return array{host: string, port: int, docRoot: string, projectRoot: string, streamOutput: bool}
*/
protected function resolvePhpServerConfiguration(
Arguments $args,
string $projectRoot,
string $docRoot,
bool $isStaticMode,
bool $streamOutput = false,
): array {
$phpConfig = Configure::read('devServer.php');
Expand All @@ -354,9 +350,8 @@ protected function resolvePhpServerConfiguration(
return [
'host' => $host,
'port' => $port,
'docRoot' => $docRoot,
'docRoot' => $projectRoot,
'projectRoot' => $projectRoot,
'staticMode' => $isStaticMode,
'streamOutput' => $streamOutput,
];
}
Expand Down
65 changes: 65 additions & 0 deletions src/Http/Concern/BasePathAwareTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);

namespace Glaze\Http\Concern;

/**
* Provides base-path stripping helpers for HTTP handlers and middleware.
*
* Classes that use this trait must expose a `$config` property whose
* `site->basePath` attribute holds the configured base path (or null when
* no base path is configured).
*
* Example:
*
* ```php
* final class MyHandler implements RequestHandlerInterface
* {
* use BasePathAwareTrait;
*
* public function __construct(protected BuildConfig $config) {}
* }
* ```
*/
trait BasePathAwareTrait
{
/**
* Strip the configured base path prefix from an incoming request path.
*
* Returns a normalized path that starts with `/` and has the base path
* prefix removed, or the original path when no base path is configured or
* the path does not start with the prefix.
*
* @param string $requestPath Incoming request path.
*/
protected function stripBasePathFromRequestPath(string $requestPath): string
{
$basePath = $this->config->site->basePath;
$normalizedPath = '/' . ltrim($requestPath, '/');

if ($basePath === null || $basePath === '') {
return $normalizedPath;
}

return $this->stripPathPrefix($normalizedPath, $basePath);
}

/**
* Strip a leading prefix from a path, returning the remainder or `/` when the path matches exactly.
*
* @param string $path Normalized request path.
* @param string $prefix Prefix to strip (e.g. `/docs` or `/static`).
*/
protected function stripPathPrefix(string $path, string $prefix): string
{
if ($path === $prefix) {
return '/';
}

if (str_starts_with($path, $prefix . '/')) {
return substr($path, strlen($prefix)) ?: '/';
}

return $path;
}
}
28 changes: 3 additions & 25 deletions src/Http/DevPageRequestHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Cake\Http\Response;
use Glaze\Build\SiteBuilder;
use Glaze\Config\BuildConfig;
use Glaze\Http\Concern\BasePathAwareTrait;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
Expand All @@ -15,6 +16,8 @@
*/
final class DevPageRequestHandler implements RequestHandlerInterface
{
use BasePathAwareTrait;

/**
* Per-instance cache of rendered not-found page HTML keyed by lookup path.
* Null indicates the path was attempted but yielded no rendered page.
Expand Down Expand Up @@ -163,29 +166,4 @@ protected function redirectLocation(string $path, string $query): string

return $path . '?' . $query;
}

/**
* Strip configured base path prefix from incoming request path.
*
* @param string $requestPath Incoming request path.
*/
protected function stripBasePathFromRequestPath(string $requestPath): string
{
$basePath = $this->config->site->basePath;
$normalizedPath = '/' . ltrim($requestPath, '/');

if ($basePath === null || $basePath === '') {
return $normalizedPath;
}

if ($normalizedPath === $basePath) {
return '/';
}

if (str_starts_with($normalizedPath, $basePath . '/')) {
return substr($normalizedPath, strlen($basePath)) ?: '/';
}

return $normalizedPath;
}
}
39 changes: 3 additions & 36 deletions src/Http/Middleware/AbstractAssetMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use Glaze\Config\BuildConfig;
use Glaze\Http\AssetResponder;
use Glaze\Http\Concern\BasePathAwareTrait;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
Expand All @@ -15,6 +16,8 @@
*/
abstract class AbstractAssetMiddleware implements MiddlewareInterface
{
use BasePathAwareTrait;

/**
* Constructor.
*
Expand Down Expand Up @@ -102,42 +105,6 @@ protected function urlPrefix(): ?string
return null;
}

/**
* Strip configured base path from request path for filesystem resolution.
*
* @param string $requestPath Request URI path.
*/
protected function stripBasePathFromRequestPath(string $requestPath): string
{
$basePath = $this->config->site->basePath;
$normalizedPath = '/' . ltrim($requestPath, '/');

if ($basePath === null || $basePath === '') {
return $normalizedPath;
}

return $this->stripPathPrefix($normalizedPath, $basePath);
}

/**
* Strip a leading prefix from a path, returning the remainder or `/` when the path matches exactly.
*
* @param string $path Normalized request path.
* @param string $prefix Prefix to strip (e.g. `/glaze` or `/static`).
*/
protected function stripPathPrefix(string $path, string $prefix): string
{
if ($path === $prefix) {
return '/';
}

if (str_starts_with($path, $prefix . '/')) {
return substr($path, strlen($prefix)) ?: '/';
}

return $path;
}

/**
* Resolve filesystem root path for the middleware asset scope.
*/
Expand Down
Loading
Loading