Skip to content

Instantly share code, notes, and snippets.

@rydurham
Created September 7, 2025 03:14
Show Gist options
  • Save rydurham/7c6c66ea5fe7f4ca2a5d35bb75efcaa2 to your computer and use it in GitHub Desktop.
Save rydurham/7c6c66ea5fe7f4ca2a5d35bb75efcaa2 to your computer and use it in GitHub Desktop.
Automating Laravel Wayfinder in Docker without Vite
FROM php:8.4-alpine
LABEL maintainer="Ryan Durham <[email protected]>"
# Set up a www-data user with UID 1000 and GID 1000
ARG NAME=www-data
ENV NAME=${NAME}
RUN deluser www-data && \
adduser -s /bin/sh -D -u 1000 -g '' ${NAME} ${NAME} && \
chown -R ${NAME}:${NAME} /home/${NAME}
# Install Alpine packages
RUN apk add --no-cache --virtual build-deps autoconf g++ libtool make linux-headers && \
apk add --no-cache git libzip-dev postgresql-dev
# Install PHP Extensions
RUN docker-php-ext-install pdo pdo_pgsql pgsql zip
# Increase the default memory limit
RUN echo 'memory_limit = 1096M' >> /usr/local/etc/php/conf.d/docker-php-memlimit.ini;
# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Add Composer to the PATH
ENV PATH="$PATH:/usr/local/bin:/home/${NAME}/.composer/vendor/bin"
# Ensure ~/.composer belongs to user
RUN mkdir /home/${NAME}/.composer && chown -R ${NAME}:${NAME} /home/${NAME}
# Install XDebug extension for code coverage support
RUN pecl install xdebug && docker-php-ext-enable xdebug
COPY xdebug.ini /usr/local/etc/php/conf.d
# Clean up apk dependencies
RUN apk del build-deps
# Set the active directory
WORKDIR /var/www
# Copy utility scripts into the container
COPY watch.php /usr/local/bin/
COPY start.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/start.sh
# Set the active user
USER ${NAME}
# Set the default command
STOPSIGNAL SIGQUIT
CMD ["/usr/local/bin/start.sh"]
#!/bin/sh
# Start processes
echo "Starting services..."
php -d display_errors=1 -d error_reporting=E_ALL artisan serve --host 0.0.0.0 --no-ansi -vvv &
php /usr/local/bin/watch.php &
# Wait for any child to exit or signal
trap "exit" SIGTERM SIGINT SIGQUIT TERM; while true; do sleep 1; done
<?php
declare(strict_types=1);
/**
* Trigger Wayfinder when changes are made to controller or route files.
*/
// Initial scan
message("Initializing...");
$timestamps = scan();
$interval = 1; // Polling interval in seconds
// Watch loop
message("Watching for controller or route updates...");
while (true) {
$changes = false;
// Clear PHP's file stat cache to ensure fresh results
clearstatcache(true);
// Get current state of directory
$files = scan();
// Check for new or modified files
foreach ($files as $path => $mtime) {
if (!isset($timestamps[$path])) {
message("New file: {$path}");
$changes = true;
} elseif ($timestamps[$path] !== $mtime) {
message("File changed: {$path}");
$changes = true;
}
}
// Check for deleted files
foreach ($timestamps as $path => $mtime) {
if (!isset($files[$path])) {
message("File deleted: {$path}");
$changes = true;
}
}
// Update our list of file timestamps
$timestamps = $files;
// Update Wayfinder if changes have been detected
if ($changes) {
runWayfinder();
}
// Memory cleanup
unset($files);
gc_collect_cycles();
// Wait before next check
sleep($interval);
}
// Log messages to STDOUT with a timestamp
function message(string $message)
{
fwrite(STDOUT, " " . date('Y-m-d H:i:s') . " MONITOR: {$message}\n");
fflush(STDOUT);
}
// Run the Wayfinder artisan command with exec()
function runWayfinder()
{
exec('php artisan wayfinder:generate --with-form --skip-actions', $output, $returnCode);
message('TypeScript route definitions updated');
if ($returnCode !== 0) {
message("Task failed with code {$returnCode}");
message(json_encode($output));
}
}
// Read file modification timestamps from the file system
function scan(array $directory = ['app/Http', 'routes'])
{
$files = [];
foreach ($directory as $dir) {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir)
);
foreach ($iterator as $file) {
if ($file->isDir() || $file->getExtension() !== 'php') {
continue;
}
$files[$file->getPathname()] = $file->getMTime();
}
// Free the iterator;
unset($iterator);
}
return $files;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment