Compare commits

..

2 commits

7 changed files with 305 additions and 2 deletions

18
.gitignore vendored
View file

@ -14,6 +14,9 @@ Icon
# ---------------
.lock
#Remove the next two line for real project repositories
composer.lock
package-lock.json
# Editors
# (sensitive workspace files)
@ -49,3 +52,18 @@ Icon
/site/config/.license
# Vendor
# ---------------
/vendor
/node_modules
# Content
# ---------------
/content
# Compiled files
# ---------------
/public/css

View file

@ -6,7 +6,7 @@
"kirby",
"cms",
"starterkit",
"tailwindcss
"tailwindcss"
],
"authors": [
{
@ -23,7 +23,8 @@
},
"require": {
"php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0",
"getkirby/cms": "^5.0"
"getkirby/cms": "^5.0",
"ext-pcntl": "*"
},
"config": {
"allow-plugins": {

18
package.json Normal file
View file

@ -0,0 +1,18 @@
{
"name": "kirby-moin",
"version": "1.0.0",
"description": "Watch and build tailwindcss",
"scripts": {
"watch": "npx @tailwindcss/cli -i ./src/css/tailwind.css -o ./public/css/app.css --content './site/**/*.php' -w",
"build": "npx @tailwindcss/cli -i ./src/css/tailwind.css -o ./public/css/app.css --content './site/**/*.php' -m"
},
"repository": {
"type": "git",
"url": "ssh://git@deichrakete.space/deichrakete/kirby-moin.git"
},
"author": "Stephan Plöhn",
"license": "ISC",
"dependencies": {
"tailwindcss": "^4.1.12"
}
}

208
site/commands/serve.php Normal file
View file

@ -0,0 +1,208 @@
<?php
return [
'description' => 'Run Kirby dev server and Tailwind watcher',
'command' => static function ($cli): void {
$cwd = getcwd();
// Resolve paths
$router = $cwd . '/vendor/getkirby/cms/router.php';
$docroot = $cli->arg('docroot') ?? 'public';
$docrootPath = $cwd . '/' . $docroot;
// Basic checks
if (!is_file($router)) {
$cli->error('Kirby router not found. Make sure dependencies are installed: vendor/getkirby/cms/router.php');
return;
}
if (!is_dir($docrootPath)) {
$cli->error("Document root not found: {$docrootPath}");
return;
}
// Helper function to check if a port is available
$isPortAvailable = function (string $host, int $port): bool {
$socket = @fsockopen($host, $port, $errno, $errstr, 1);
if ($socket) {
fclose($socket);
return false; // Port is occupied
}
return true; // Port is available
};
$host = 'localhost';
$startPort = 8000;
$maxPort = 8100;
$port = $startPort;
while ($port <= $maxPort) {
if ($isPortAvailable($host, $port)) {
break;
}
$port++;
}
if ($port > $maxPort) {
$cli->error("No available ports found between {$startPort} and {$maxPort}");
return;
}
$cli->info("Using port {$port} for PHP dev server");
$phpCmd = sprintf(
'php -S %s:%s -t %s %s',
escapeshellarg($host),
escapeshellarg($port),
escapeshellarg($docrootPath),
escapeshellarg($router)
);
$npmCmd = 'npm run watch';
$cli->info("Starting PHP dev server on http://{$host}:{$port} (docroot: {$docroot})");
// Helper to start a process with pipes
$start = static function (string $cmd) use ($cwd) {
$descriptorSpec = [
0 => ['pipe', 'r'], // stdin
1 => ['pipe', 'w'], // stdout
2 => ['pipe', 'w'], // stderr
];
$proc = proc_open($cmd, $descriptorSpec, $pipes, $cwd);
if (!is_resource($proc)) {
return null;
}
// Non-blocking I/O
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);
return [
'proc' => $proc,
'pipes' => $pipes,
'cmd' => $cmd,
];
};
$processes = [];
$phpProc = $start($phpCmd);
if ($phpProc === null) {
$cli->error('Failed to start PHP dev server.');
return;
}
$processes['php'] = $phpProc;
$npmProc = $start($npmCmd);
if ($npmProc === null) {
$cli->warning('Could not start npm watcher. Is Node/npm installed and in your PATH?');
} else {
$processes['npm'] = $npmProc;
}
// Graceful termination
$terminateAll = static function () use (&$processes, $cli): void {
$cli->line("");
foreach ($processes as $name => $p) {
// Check if the resource is still valid before calling proc_get_status
if (is_resource($p['proc'])) {
$status = proc_get_status($p['proc']);
if ($status && $status['running']) {
$cli->line("Stopping {$name}...");
// Try graceful terminate
@proc_terminate($p['proc']);
}
}
// Close pipes regardless of process status
foreach ($p['pipes'] as $pipe) {
if (is_resource($pipe)) {
@fclose($pipe);
}
}
// Final close if the resource is still valid
if (is_resource($p['proc'])) {
@proc_close($p['proc']);
}
}
};
// Handle Ctrl+C if pcntl is available
if (function_exists('pcntl_async_signals') && function_exists('pcntl_signal')) {
pcntl_async_signals(true);
pcntl_signal(SIGINT, static function () use ($terminateAll) {
$terminateAll();
// Exit cleanly after terminating children
exit(130);
});
// Optional: also handle SIGTERM
pcntl_signal(SIGTERM, static function () use ($terminateAll) {
$terminateAll();
exit(143);
});
}
// Ensure cleanup on normal shutdown
register_shutdown_function($terminateAll);
// Stream outputs until all children exit
$prefixColor = [
'php' => fn($s) => "\033[1;34m🐘 [php]\033[0m $s",
'npm' => fn($s) => "\033[1;32m [npm]\033[0m $s",
];
while (true) {
$running = false;
foreach ($processes as $name => $p) {
// Check if the resource is still valid
if (!is_resource($p['proc'])) {
continue;
}
$status = proc_get_status($p['proc']);
if (!$status) {
continue;
}
$running = $running || $status['running'];
// Read stdout
if (is_resource($p['pipes'][1])) {
$out = @stream_get_contents($p['pipes'][1]);
if ($out !== false && $out !== '') {
foreach (preg_split('/\R/', rtrim($out)) as $line) {
if ($line !== '') {
$cli->line(($prefixColor[$name])($line));
}
}
}
}
// Read stderr
if (is_resource($p['pipes'][2])) {
$err = @stream_get_contents($p['pipes'][2]);
if ($err !== false && $err !== '') {
foreach (preg_split('/\R/', rtrim($err)) as $line) {
if ($line !== '') {
$cli->error(($prefixColor[$name])($line));
}
}
}
}
}
if (!$running) {
break;
}
// Small sleep to avoid busy-waiting
usleep(50_000);
}
$cli->success('All processes exited.');
}
];

View file

@ -0,0 +1,44 @@
<?php
/**
* @var Site $site
* @var Page $page
* @var Slot $slot
* @var Slots $slots
*/
use Kirby\Cms\Page;
use Kirby\Cms\Site;
use Kirby\Template\Slot;
use Kirby\Template\Slots;
?>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>
<?= $page->title() ?> | <?= $site->title() ?>
</title>
<?= css('css/app.css') ?>
</head>
<body>
<header>
<h1>
<a href="<?= $site->url() ?>">
<?= $site->title() ?>
</a>
</h1>
</header>
<main>
<?= $slot ?>
</main>
<footer>
by <?= $site->title() ?>
</footer>
</body>
</html>

View file

@ -1 +1,14 @@
<?php
/**
* @var Page $page
*/
use Kirby\Cms\Page;
?>
<?php snippet('layouts/main', slots: true) ?>
<?php slot() ?>
<h1><?= $page->title() ?></h1>
<?php endslot();

1
src/css/tailwind.css Normal file
View file

@ -0,0 +1 @@
@import "tailwindcss";