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
2 changes: 2 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ name: Deploy

on:
workflow_dispatch:
repository_dispatch:
types: [deploy]

jobs:
deploy:
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.tempest
node_modules/
vendor/
docs-clone/
/public/main.css
/package-lock.json
.env
Expand All @@ -18,4 +19,4 @@ tempest-access.log
vite-tempest
public/build/
database.sqlite
database-old.sqlite
database-old.sqlite
10 changes: 5 additions & 5 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@
"": {
"dependencies": {
"highlight.js": "^11.11.1",
"puppeteer": "24.15.0",
"puppeteer": "^24.11.2",
},
"devDependencies": {
"@fontsource-variable/kantumruy-pro": "^5.2.6",
"@fontsource-variable/public-sans": "^5.2.6",
"@tailwindcss/typography": "^0.5.16",
"@tailwindcss/vite": "^4.1.11",
"@vitejs/plugin-vue": "6.0.1",
"@vueuse/core": "13.6.0",
"@vitejs/plugin-vue": "^6.0.0",
"@vueuse/core": "^13.5.0",
"fuse.js": "^7.1.0",
"reka-ui": "2.4.1",
"reka-ui": "^2.3.2",
"tailwindcss": "^4.1.11",
"typescript": "^5.8.3",
"vite": "^6.3.5",
"vite-plugin-tempest": "^0.0.2",
"vue": "3.5.18",
"vue": "^3.5.17",
},
},
},
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
"bun run build",
"@php ./tempest static:generate",
"@php ./tempest static:clean",
"rm -rf public/build"
"rm -rf public/build",
"composer mago:lint"
]
},
"minimum-stability": "dev",
Expand Down
1 change: 1 addition & 0 deletions deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ php8.4 tempest migrate:up --force
php8.4 tempest static:clean --force

# Build front-end
php8.4 tempest docs:pull --no-interaction
php8.4 tempest command-palette:index
/home/forge/.bun/bin/bun run build
php8.4 tempest cache:clear --force
Expand Down
6 changes: 6 additions & 0 deletions mago.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ empty_line_after_opening_tag = false
default_plugins = true
plugins = ["symfony", "php-unit"]

# Good rule but needs some work
[[linter.rules]]
name = "best-practices/literal-named-argument"
level = "off"
# level = "error"

# MAINTENABILITY
[[linter.rules]]
name = "maintainability/too-many-enum-cases"
Expand Down
4 changes: 2 additions & 2 deletions src/Web/Analytics/StatsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
final readonly class StatsController
{
#[Get('/stats')]
public function __invoke(Clock $clock): View
public function __invoke(): View
{
$limit = 30;

Expand Down Expand Up @@ -44,7 +44,7 @@ public function __invoke(Clock $clock): View
packageDownloadsPerDay: new Chart(
labels: $packageDownloadsPerDay->map(fn (PackageDownloadsPerDay $item) => $item->date->format('Y-m-d')),
values: $packageDownloadsPerDay->map(fn (PackageDownloadsPerDay $item) => $item->total),
min: $packageDownloadsPerDay->sortByCallback(fn (PackageDownloadsPerDay $a, PackageDownloadsPerDay $b) => $a->total)->first()->total,
min: $packageDownloadsPerDay->sortByCallback(fn (PackageDownloadsPerDay $a, PackageDownloadsPerDay $_b) => $a->total)->first()->total,
),
);
}
Expand Down
3 changes: 3 additions & 0 deletions src/Web/Blog/BlogPost.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

use function Tempest\uri;

/**
* @mago-expect maintainability/too-many-properties
*/
final class BlogPost
{
public string $slug;
Expand Down
1 change: 1 addition & 0 deletions src/Web/Documentation/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
content/*
17 changes: 16 additions & 1 deletion src/Web/Documentation/Chapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,22 @@ public function __construct(

public function getUri(): string
{
return uri(DocumentationController::class, version: $this->version, category: $this->category, slug: $this->slug);
return uri(
DocumentationController::class,
version: $this->version,
category: $this->category,
slug: $this->slug,
);
}

public function getCanonicalUri(): string
{
return uri(
DocumentationController::class,
version: $this->version->default(),
category: $this->category,
slug: $this->slug,
);
}

public function getMetaUri(): string
Expand Down
13 changes: 11 additions & 2 deletions src/Web/Documentation/ChapterRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
use App\Support\HasMemoization;
use League\CommonMark\Extension\FrontMatter\Output\RenderedContentWithFrontMatter;
use League\CommonMark\MarkdownConverter;
use RuntimeException;
use Spatie\YamlFrontMatter\YamlFrontMatter;
use Tempest\Http\HttpRequestFailed;
use Tempest\Http\Status;
use Tempest\Support\Arr\ImmutableArray;

use function Tempest\root_path;
Expand All @@ -29,8 +32,14 @@ public function find(Version $version, string $category, string $slug): ?Chapter
{
$category = replace($category, '/^\d+-/', '');
$slug = replace($slug, '/^\d+-/', '');
$directory = __DIR__ . "/content/{$version->getUrlSegment()}";

if (! is_dir($directory)) {
throw new RuntimeException("Documentation for version {$version->value} has not been fetched. Run `tempest docs:pull {$version->value}`.");
}

$path = ImmutableArray::createFrom(recursive_search(
folder: __DIR__ . "/content/{$version->value}",
folder: $directory,
pattern: sprintf("#.*\/(\d+-)?%s\/(\d+-)?%s\.md#", preg_quote($category, '#'), preg_quote($slug, '#')),
))->sort()->first();

Expand Down Expand Up @@ -68,7 +77,7 @@ public function all(Version $version, string $category = '*'): ImmutableArray
{
return $this->memoize(
$version->value . $category,
fn () => arr(glob(__DIR__ . "/content/{$version->value}/*{$category}/*.md"))
fn () => arr(glob(__DIR__ . "/content/{$version->getUrlSegment()}/*{$category}/*.md"))
->map(function (string $path) use ($version) {
$content = file_get_contents($path);
$category = str($path)->beforeLast('/')->afterLast('/')->replaceRegex('/^\d+-/', '');
Expand Down
2 changes: 1 addition & 1 deletion src/Web/Documentation/ChapterView.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public function previousChapter(): ?Chapter
public function categories(): array
{
return map_iterable(
array: glob(__DIR__ . "/content/{$this->version->value}/*", flags: GLOB_ONLYDIR),
array: glob(__DIR__ . "/content/{$this->version->getUrlSegment()}/*", flags: GLOB_ONLYDIR),
map: fn (string $path) => str($path)->afterLast('/')->replaceRegex('/^\d+-/', '')->toString(),
);
}
Expand Down
26 changes: 0 additions & 26 deletions src/Web/Documentation/DefaultDocumentationDataProvider.php

This file was deleted.

35 changes: 14 additions & 21 deletions src/Web/Documentation/DocumentationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Tempest\Http\Responses\Redirect;
use Tempest\Router\Get;
use Tempest\Router\StaticPage;
use Tempest\Support\Arr\ImmutableArray;
use Tempest\Support\Str\ImmutableString;
use Tempest\View\View;

Expand All @@ -20,58 +21,50 @@
{
#[Get('/current/{path:.*}')]
#[Get('/main/{path:.*}')]
public function docsRedirect(string $path): Redirect
#[Get('/docs/{path:.*}')]
public function redirect(string $path): Redirect
{
return new Redirect(sprintf('/%s/%s', Version::default()->value, $path));
return new Redirect(sprintf('/%s/%s', Version::default()->getUrlSegment(), $path));
}

#[Get('/docs')]
#[Get('/documentation')]
#[Get('/main/framework/getting-started')]
public function index(): Redirect
#[Get('/docs')]
#[Get('/{version}')]
public function index(?string $version): Redirect
{
$version = Version::default();
$version = Version::tryFromString($version);

$category = arr(glob(__DIR__ . "/content/{$version->value}/*", flags: GLOB_ONLYDIR))
$category = arr(glob(__DIR__ . "/content/{$version->getUrlSegment()}/*", flags: GLOB_ONLYDIR))
->tap(fn (ImmutableArray $files) => $files->isEmpty() ? throw new \RuntimeException('Documentation has not been fetched. Run `tempest docs:pull`.') : null)
->sort()
->mapFirstTo(ImmutableString::class)
->basename()
->toString();

$slug = arr(glob(__DIR__ . "/content/{$version->value}/{$category}/*.md"))
$slug = arr(glob(__DIR__ . "/content/{$version->getUrlSegment()}/{$category}/*.md"))
->map(fn (string $path) => before_first(basename($path), '.'))
->sort()
->mapFirstTo(ImmutableString::class)
->basename()
->toString();

return new Redirect(uri(
[self::class, 'default'],
[self::class, '__invoke'],
version: $version,
category: str_replace('0-', '', $category),
slug: str_replace('01-', '', $slug),
));
}

#[StaticPage(DefaultDocumentationDataProvider::class)]
#[Get('/docs/{category}/{slug}')]
public function default(string $category, string $slug, ChapterRepository $chapterRepository): View|Response
{
return $this->chapterView(Version::default(), $category, $slug, $chapterRepository) ?? new NotFound();
}

#[StaticPage(DocumentationDataProvider::class)]
#[Get('/{version}/{category}/{slug}')]
public function __invoke(string $version, string $category, string $slug, ChapterRepository $chapterRepository): View|Response
{
if ($version === Version::default()->value) {
return new Redirect(uri([self::class, 'default'], category: $category, slug: $slug));
}

if (is_null($version = Version::tryFromString($version))) {
return new NotFound();
}

return $this->chapterView($version, $category, $slug, $chapterRepository) ?? new NotFound();
return $this->chapterView($version, $category, $slug, $chapterRepository) ?? new Redirect(uri(self::class, 'index'));
}

private function chapterView(Version $version, string $category, string $slug, ChapterRepository $chapterRepository): ?ChapterView
Expand Down
93 changes: 93 additions & 0 deletions src/Web/Documentation/PullDocumentationCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

namespace App\Web\Documentation;

use RuntimeException;
use Symfony\Component\Process\Process;
use Tempest\Console\Console;
use Tempest\Console\ConsoleCommand;
use Tempest\Console\ExitCode;
use Tempest\Support\Filesystem;

use function Tempest\root_path;
use function Tempest\Support\path;

final readonly class PullDocumentationCommand
{
private const string CLONE_DIRECTORY = 'clone';
private const string DOCS_DIRECTORY = 'docs';

public function __construct(
private Console $console,
) {}

#[ConsoleCommand('docs:pull')]
public function __invoke(?Version $version = null): ExitCode
{
$versions = $version ? [$version] : Version::cases();

$this->console->header('Cloning documentation');

foreach ($versions as $version) {
$success = $this->console->task(
label: "Fetching documentation for branch `{$version->getBranch()}`.",
handler: fn () => $this->fetchVersionDocumentation($version),
);

if (! $success) {
$this->console->writeln();
$this->console->error("Failed to fetch documentation for version {$version->getBranch()}.");

return ExitCode::ERROR;
}
}

$this->console->writeln();
$this->console->success('Documentation fetched successfully.');

return ExitCode::SUCCESS;
}

private function fetchVersionDocumentation(Version $version): void
{
$versionedDocsDirectory = root_path('src/Web/Documentation/content/', $version->getUrlSegment());
$temporaryDirectory = root_path('docs-clone');

Filesystem\ensure_directory_empty($versionedDocsDirectory);
Filesystem\ensure_directory_empty($temporaryDirectory);

$this->run(
command: sprintf(
'git clone --no-checkout --depth=1 --filter=tree:0 -b %s https://github.com/tempestphp/tempest-framework %s',
$version->getBranch(),
self::CLONE_DIRECTORY,
),
cwd: $temporaryDirectory,
);

$this->run(
command: 'git sparse-checkout set --no-cone /' . self::DOCS_DIRECTORY,
cwd: path($temporaryDirectory, self::CLONE_DIRECTORY),
);

$this->run(
command: 'git checkout',
cwd: path($temporaryDirectory, self::CLONE_DIRECTORY),
);

Filesystem\move(path($temporaryDirectory, self::CLONE_DIRECTORY, self::DOCS_DIRECTORY), $versionedDocsDirectory, overwrite: true);
Filesystem\delete_directory($temporaryDirectory);
}

private function run(string $command, string $cwd): string
{
$process = Process::fromShellCommandline($command, $cwd);
$process->run();

if (! $process->isSuccessful()) {
throw new RuntimeException($process->getErrorOutput());
}

return $process->getOutput();
}
}
Loading