refactor: susun semula struktur folder — Laravel source ke src/
This commit is contained in:
92
vendor/laravel/boost/src/Concerns/DisplayHelper.php
vendored
Normal file
92
vendor/laravel/boost/src/Concerns/DisplayHelper.php
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Laravel\Boost\Concerns;
|
||||
|
||||
use Laravel\Boost\Console\Enums\Theme;
|
||||
use Laravel\Prompts\Concerns\Colors;
|
||||
|
||||
use function Laravel\Prompts\note;
|
||||
|
||||
trait DisplayHelper
|
||||
{
|
||||
use Colors;
|
||||
|
||||
protected ?Theme $theme = null;
|
||||
|
||||
protected function initTheme(?Theme $theme = null): void
|
||||
{
|
||||
$this->theme = $theme ?? Theme::random();
|
||||
}
|
||||
|
||||
protected function displayBoostHeader(string $featureName, string $projectName, ?Theme $theme = null): void
|
||||
{
|
||||
$this->initTheme($theme);
|
||||
|
||||
$this->displayGradientLogo();
|
||||
$this->displayTagline($featureName);
|
||||
$this->displayNote($projectName);
|
||||
}
|
||||
|
||||
protected function displayGradientLogo(): void
|
||||
{
|
||||
$lines = [
|
||||
' ██████╗ ██████╗ ██████╗ ███████╗ ████████╗',
|
||||
' ██╔══██╗ ██╔═══██╗ ██╔═══██╗ ██╔════╝ ╚══██╔══╝',
|
||||
' ██████╔╝ ██║ ██║ ██║ ██║ ███████╗ ██║ ',
|
||||
' ██╔══██╗ ██║ ██║ ██║ ██║ ╚════██║ ██║ ',
|
||||
' ██████╔╝ ╚██████╔╝ ╚██████╔╝ ███████║ ██║ ',
|
||||
' ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ',
|
||||
];
|
||||
|
||||
$gradient = $this->theme->gradient();
|
||||
|
||||
$this->newLine();
|
||||
|
||||
foreach ($lines as $index => $line) {
|
||||
$this->output->writeln($this->ansi256Fg($gradient[$index], $line));
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
protected function displayTagline(string $featureName): void
|
||||
{
|
||||
$tagline = " ✦ Laravel Boost :: {$featureName} :: We Must Ship ✦ ";
|
||||
$this->output->writeln(' '.$this->displayBadge($tagline));
|
||||
}
|
||||
|
||||
protected function displayNote(string $projectName): void
|
||||
{
|
||||
note(" Let's give {$this->displayBadge($projectName)} a Boost");
|
||||
}
|
||||
|
||||
protected function displayOutro(string $text, string $link = '', int $terminalWidth = 80): void
|
||||
{
|
||||
$visibleText = preg_replace('/\x1b\[[0-9;]*m|\x1b\]8;;[^\x07]*\x07|\x1b\]8;;\x1b\\\\/', '', $text.$link) ?? '';
|
||||
$visualWidth = mb_strwidth($visibleText);
|
||||
$paddingLength = (int) (floor(($terminalWidth - $visualWidth) / 2)) - 2;
|
||||
$padding = str_repeat(' ', max(0, $paddingLength));
|
||||
|
||||
$this->output->writeln(
|
||||
"\e[48;5;{$this->theme->primary()}m\033[2K{$padding}\e[30m\e[1m{$text}{$link}\e[0m"
|
||||
);
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
protected function ansi256Fg(int $color, string $text): string
|
||||
{
|
||||
return "\e[38;5;{$color}m{$text}\e[0m";
|
||||
}
|
||||
|
||||
protected function displayBadge(string $text): string
|
||||
{
|
||||
return "\e[48;5;{$this->theme->primary()}m\e[30m\e[1m{$text}\e[0m";
|
||||
}
|
||||
|
||||
protected function hyperlink(string $label, string $url): string
|
||||
{
|
||||
return "\033]8;;{$url}\007{$label}\033]8;;\033\\";
|
||||
}
|
||||
}
|
||||
39
vendor/laravel/boost/src/Concerns/MakesHttpRequests.php
vendored
Normal file
39
vendor/laravel/boost/src/Concerns/MakesHttpRequests.php
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Laravel\Boost\Concerns;
|
||||
|
||||
use Illuminate\Http\Client\PendingRequest;
|
||||
use Illuminate\Http\Client\Response;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
trait MakesHttpRequests
|
||||
{
|
||||
public function client(): PendingRequest
|
||||
{
|
||||
$client = Http::withHeaders([
|
||||
'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:140.0) Gecko/20100101 Firefox/140.0 Laravel Boost',
|
||||
]);
|
||||
|
||||
// Disable SSL verification for local development URLs and testing
|
||||
if (app()->environment(['local', 'testing']) || str_contains((string) config('boost.hosted.api_url', ''), '.test')) {
|
||||
return $client->withoutVerifying();
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
public function get(string $url): Response
|
||||
{
|
||||
return $this->client()->get($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $json
|
||||
*/
|
||||
public function json(string $url, array $json): Response
|
||||
{
|
||||
return $this->client()->asJson()->post($url, $json);
|
||||
}
|
||||
}
|
||||
260
vendor/laravel/boost/src/Concerns/ReadsLogs.php
vendored
Normal file
260
vendor/laravel/boost/src/Concerns/ReadsLogs.php
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Laravel\Boost\Concerns;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
trait ReadsLogs
|
||||
{
|
||||
/**
|
||||
* Regular expression fragments and default chunk-window sizes used when
|
||||
* scanning log files. Declaring them once keeps every consumer in sync.
|
||||
*/
|
||||
protected function getTimestampRegex(): string
|
||||
{
|
||||
return '\\[\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\]';
|
||||
}
|
||||
|
||||
protected function getEntrySplitRegex(): string
|
||||
{
|
||||
return '/(?='.$this->getTimestampRegex().')/';
|
||||
}
|
||||
|
||||
protected function getErrorEntryRegex(): string
|
||||
{
|
||||
return '/^'.$this->getTimestampRegex().'.*\\.ERROR:/';
|
||||
}
|
||||
|
||||
protected function getChunkSizeStart(): int
|
||||
{
|
||||
return 64 * 1024; // 64 kB
|
||||
}
|
||||
|
||||
protected function getChunkSizeMax(): int
|
||||
{
|
||||
return 1024 * 1024; // 1 MB
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the current log file path based on Laravel's logging configuration.
|
||||
*/
|
||||
protected function resolveLogFilePath(): string
|
||||
{
|
||||
$channel = Config::get('logging.default');
|
||||
$channelConfig = Config::get("logging.channels.{$channel}");
|
||||
|
||||
$channelConfig = $this->resolveChannelWithPath($channelConfig);
|
||||
|
||||
$baseLogPath = Arr::get($channelConfig, 'path', storage_path('logs/laravel.log'));
|
||||
|
||||
if (Arr::get($channelConfig, 'driver') === 'daily') {
|
||||
return $this->resolveDailyLogFilePath($baseLogPath);
|
||||
}
|
||||
|
||||
return $baseLogPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $channelConfig
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
protected function resolveChannelWithPath(?array $channelConfig, int $depth = 0): ?array
|
||||
{
|
||||
if ($channelConfig === null || $depth > 2) {
|
||||
return $channelConfig;
|
||||
}
|
||||
|
||||
if (isset($channelConfig['path'])) {
|
||||
return $channelConfig;
|
||||
}
|
||||
|
||||
if (($channelConfig['driver'] ?? null) !== 'stack') {
|
||||
return $channelConfig;
|
||||
}
|
||||
|
||||
$firstValidLoggerConfig = collect($channelConfig['channels'] ?? [])
|
||||
->map(fn (string $name) => Config::get("logging.channels.{$name}"))
|
||||
->filter(fn ($config): bool => is_array($config))
|
||||
->map(fn (array $config) => $this->resolveChannelWithPath($config, $depth + 1))
|
||||
->first(fn (?array $config): bool => isset($config['path']));
|
||||
|
||||
return $firstValidLoggerConfig ?? $channelConfig;
|
||||
}
|
||||
|
||||
protected function resolveDailyLogFilePath(string $basePath): string
|
||||
{
|
||||
$pathInfo = pathinfo($basePath);
|
||||
$directory = $pathInfo['dirname'];
|
||||
$filename = $pathInfo['filename'];
|
||||
$extension = isset($pathInfo['extension']) ? '.'.$pathInfo['extension'] : '';
|
||||
|
||||
$todayLogFile = $directory.DIRECTORY_SEPARATOR.$filename.'-'.date('Y-m-d').$extension;
|
||||
|
||||
if (file_exists($todayLogFile)) {
|
||||
return $todayLogFile;
|
||||
}
|
||||
|
||||
$pattern = $directory.DIRECTORY_SEPARATOR.$filename.'-*'.$extension;
|
||||
$files = glob($pattern) ?: [];
|
||||
|
||||
$datePattern = '/^'.preg_quote($filename, '/').'-\d{4}-\d{2}-\d{2}'.preg_quote($extension, '/').'$/';
|
||||
$latestFile = collect($files)
|
||||
->filter(fn ($file): int|false => preg_match($datePattern, basename($file)))
|
||||
->sortDesc()
|
||||
->first();
|
||||
|
||||
return $latestFile ?? $todayLogFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given line (or entry) is an ERROR log entry.
|
||||
*/
|
||||
protected function isErrorEntry(string $line): bool
|
||||
{
|
||||
if (str_starts_with(trim($line), '{')) {
|
||||
return $this->isJsonErrorEntry($line);
|
||||
}
|
||||
|
||||
return preg_match($this->getErrorEntryRegex(), $line) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the last $count complete PSR-3 log entries from the log file using
|
||||
* chunked reading instead of character-by-character reverse scanning.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function readLastLogEntries(string $logFile, int $count): array
|
||||
{
|
||||
$chunkSize = $this->getChunkSizeStart();
|
||||
|
||||
do {
|
||||
$entries = $this->scanLogChunkForEntries($logFile, $chunkSize);
|
||||
|
||||
if (count($entries) >= $count || $chunkSize >= $this->getChunkSizeMax()) {
|
||||
break;
|
||||
}
|
||||
|
||||
$chunkSize *= 2;
|
||||
} while (true);
|
||||
|
||||
return array_slice($entries, -$count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the most recent ERROR log entry, or null if none exists within the
|
||||
* inspected window.
|
||||
*/
|
||||
protected function readLastErrorEntry(string $logFile): ?string
|
||||
{
|
||||
$chunkSize = $this->getChunkSizeStart();
|
||||
|
||||
do {
|
||||
$entries = $this->scanLogChunkForEntries($logFile, $chunkSize);
|
||||
|
||||
for ($i = count($entries) - 1; $i >= 0; $i--) {
|
||||
if ($this->isErrorEntry($entries[$i])) {
|
||||
return trim((string) $entries[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($chunkSize >= $this->getChunkSizeMax()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$chunkSize *= 2;
|
||||
} while (true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the log content uses JSON format (one JSON object per line).
|
||||
*/
|
||||
protected function isJsonLogFormat(string $content): bool
|
||||
{
|
||||
$firstLine = strtok($content, "\n");
|
||||
|
||||
if ($firstLine === false || trim($firstLine) === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$trimmed = trim($firstLine);
|
||||
|
||||
if (! str_starts_with($trimmed, '{')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
json_decode($trimmed);
|
||||
|
||||
return json_last_error() === JSON_ERROR_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given entry is a JSON-formatted ERROR log entry.
|
||||
*/
|
||||
protected function isJsonErrorEntry(string $entry): bool
|
||||
{
|
||||
$decoded = json_decode(trim($entry), true);
|
||||
|
||||
if (! is_array($decoded)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$level = $decoded['level'] ?? $decoded['level_name'] ?? '';
|
||||
|
||||
return strtoupper((string) $level) === 'ERROR' || (int) ($decoded['level'] ?? 0) >= 400;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan the last $chunkSize bytes of the log file and return an array of
|
||||
* complete log entries (oldest ➜ newest).
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function scanLogChunkForEntries(string $logFile, int $chunkSize): array
|
||||
{
|
||||
$fileSize = filesize($logFile);
|
||||
|
||||
if ($fileSize === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$handle = fopen($logFile, 'r');
|
||||
|
||||
if (! $handle) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
$offset = max($fileSize - $chunkSize, 0);
|
||||
fseek($handle, $offset);
|
||||
|
||||
// If we started mid-line, discard the partial line to align to the next newline.
|
||||
if ($offset > 0) {
|
||||
fgets($handle);
|
||||
}
|
||||
|
||||
$content = stream_get_contents($handle);
|
||||
|
||||
if ($this->isJsonLogFormat($content)) {
|
||||
return array_values(array_filter(
|
||||
explode("\n", $content),
|
||||
fn (string $line): bool => trim($line) !== '',
|
||||
));
|
||||
}
|
||||
|
||||
// Split by beginning-of-entry look-ahead (PSR-3 timestamp pattern).
|
||||
$entries = preg_split($this->getEntrySplitRegex(), $content, -1, PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
if (! $entries) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $entries; // already in chronological order relative to chunk
|
||||
} finally {
|
||||
fclose($handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
87
vendor/laravel/boost/src/Concerns/RendersBladeGuidelines.php
vendored
Normal file
87
vendor/laravel/boost/src/Concerns/RendersBladeGuidelines.php
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Laravel\Boost\Concerns;
|
||||
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Laravel\Boost\Install\GuidelineAssist;
|
||||
|
||||
trait RendersBladeGuidelines
|
||||
{
|
||||
private array $storedSnippets = [];
|
||||
|
||||
protected function renderContent(string $content, string $path, array $data = []): string
|
||||
{
|
||||
$isBladeTemplate = str_ends_with($path, '.blade.php');
|
||||
|
||||
if (! $isBladeTemplate) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
// Temporarily replace backticks, PHP opening tags, component tags, and Volt directives
|
||||
// with placeholders before Blade processing. This prevents Blade from trying to execute
|
||||
// PHP code examples, compile component references, and supports inline code.
|
||||
$placeholders = [
|
||||
'`' => '___SINGLE_BACKTICK___',
|
||||
'<?php' => '___OPEN_PHP_TAG___',
|
||||
'@volt' => '___VOLT_DIRECTIVE___',
|
||||
'@endvolt' => '___ENDVOLT_DIRECTIVE___',
|
||||
'</x-' => '___BLADE_COMPONENT_CLOSE___',
|
||||
'<x-' => '___BLADE_COMPONENT_OPEN___',
|
||||
];
|
||||
|
||||
$content = str_replace(array_keys($placeholders), array_values($placeholders), $content);
|
||||
$rendered = Blade::render($content, [
|
||||
'assist' => $this->getGuidelineAssist(),
|
||||
...$data,
|
||||
]);
|
||||
|
||||
$rendered = html_entity_decode($rendered, ENT_QUOTES | ENT_HTML5);
|
||||
|
||||
return str_replace(array_values($placeholders), array_keys($placeholders), $rendered);
|
||||
}
|
||||
|
||||
protected function processBoostSnippets(string $content): string
|
||||
{
|
||||
return preg_replace_callback('/(?<!@)@boostsnippet\(\s*(?P<nameQuote>[\'"])(?P<name>[^\1]*?)\1(?:\s*,\s*(?P<langQuote>[\'"])(?P<lang>[^\3]*?)\3)?\s*\)(?P<content>.*?)@endboostsnippet/s', function (array $matches): string {
|
||||
$name = $matches['name'];
|
||||
$lang = empty($matches['lang']) ? 'html' : $matches['lang'];
|
||||
$snippetContent = trim($matches['content']);
|
||||
|
||||
$placeholder = '___BOOST_SNIPPET_'.count($this->storedSnippets).'___';
|
||||
|
||||
$this->storedSnippets[$placeholder] = '<!-- '.$name.' -->'."\n".'```'.$lang."\n".$snippetContent."\n".'```'."\n\n";
|
||||
|
||||
return $placeholder;
|
||||
}, $content);
|
||||
}
|
||||
|
||||
protected function renderBladeFile(string $bladePath, array $data = []): string
|
||||
{
|
||||
if (! file_exists($bladePath)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$content = file_get_contents($bladePath);
|
||||
|
||||
if ($content === false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$content = $this->processBoostSnippets($content);
|
||||
|
||||
$rendered = $this->renderContent($content, $bladePath, $data);
|
||||
|
||||
$rendered = str_replace(array_keys($this->storedSnippets), array_values($this->storedSnippets), $rendered);
|
||||
|
||||
$this->storedSnippets = [];
|
||||
|
||||
return $rendered;
|
||||
}
|
||||
|
||||
protected function getGuidelineAssist(): GuidelineAssist
|
||||
{
|
||||
return app(GuidelineAssist::class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user