Fix code quality violations and exclude Manifest from checks
Document application modes (development/debug/production) Add global file drop handler, order column normalization, SPA hash fix Serve CDN assets via /_vendor/ URLs instead of merging into bundles Add production minification with license preservation Improve JSON formatting for debugging and production optimization Add CDN asset caching with CSS URL inlining for production builds Add three-mode system (development, debug, production) Update Manifest CLAUDE.md to reflect helper class architecture Refactor Manifest.php into helper classes for better organization Pre-manifest-refactor checkpoint: Add app_mode documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,8 @@ APP_KEY=
|
|||||||
APP_DEBUG=true
|
APP_DEBUG=true
|
||||||
APP_URL=http://localhost
|
APP_URL=http://localhost
|
||||||
|
|
||||||
|
# RSX Application Mode: development, debug, or production
|
||||||
|
|
||||||
LOG_CHANNEL=stack
|
LOG_CHANNEL=stack
|
||||||
LOG_DEPRECATIONS_CHANNEL=null
|
LOG_DEPRECATIONS_CHANNEL=null
|
||||||
LOG_LEVEL=info
|
LOG_LEVEL=info
|
||||||
@@ -54,3 +56,4 @@ GATEKEEPER_SUBTITLE="This is a restricted development preview site. Please enter
|
|||||||
SSR_FPC_ENABLED=true
|
SSR_FPC_ENABLED=true
|
||||||
LOG_BROWSER_ERRORS=false
|
LOG_BROWSER_ERRORS=false
|
||||||
AJAX_DISABLE_BATCHING=false
|
AJAX_DISABLE_BATCHING=false
|
||||||
|
RSX_MODE=development
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -22,8 +22,11 @@ yarn-error.log
|
|||||||
._*
|
._*
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
# Entire storage directory - will be created on bootstrap
|
# Storage directory (except cdn-cache which is committed)
|
||||||
/storage/
|
/storage/*
|
||||||
|
!/storage/rsx-build/
|
||||||
|
/storage/rsx-build/*
|
||||||
|
!/storage/rsx-build/cdn-cache/
|
||||||
|
|
||||||
# Supervisor files
|
# Supervisor files
|
||||||
supervisord.log*
|
supervisord.log*
|
||||||
|
|||||||
@@ -261,6 +261,12 @@ class Migrate_Normalize_Schema_Command extends Command
|
|||||||
if (in_array($tableName, $ajaxableTables) && !Schema::hasColumn($tableName, 'version')) {
|
if (in_array($tableName, $ajaxableTables) && !Schema::hasColumn($tableName, 'version')) {
|
||||||
DB::statement("ALTER TABLE $tableName ADD COLUMN version INT(11) NOT NULL DEFAULT 1");
|
DB::statement("ALTER TABLE $tableName ADD COLUMN version INT(11) NOT NULL DEFAULT 1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Order column normalization
|
||||||
|
// Tables with `order` column get: BIGINT DEFAULT NULL, order_idx index, auto-increment triggers
|
||||||
|
if (Schema::hasColumn($tableName, 'order')) {
|
||||||
|
$this->normalizeOrderColumn($tableName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert table to utf8mb4 if needed
|
// Convert table to utf8mb4 if needed
|
||||||
@@ -407,4 +413,100 @@ class Migrate_Normalize_Schema_Command extends Command
|
|||||||
|
|
||||||
return count($indexes) > 0;
|
return count($indexes) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a trigger exists
|
||||||
|
*
|
||||||
|
* @param string $triggerName The name of the trigger
|
||||||
|
* @return bool True if the trigger exists
|
||||||
|
*/
|
||||||
|
private function triggerExists($triggerName)
|
||||||
|
{
|
||||||
|
$triggers = DB::select("SHOW TRIGGERS WHERE `Trigger` = ?", [$triggerName]);
|
||||||
|
|
||||||
|
return count($triggers) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize order column for a table
|
||||||
|
*
|
||||||
|
* Ensures:
|
||||||
|
* - Column is BIGINT DEFAULT NULL
|
||||||
|
* - Index order_idx exists on (order)
|
||||||
|
* - Triggers exist for auto-incrementing NULL values on INSERT/UPDATE
|
||||||
|
*
|
||||||
|
* @param string $tableName The name of the table
|
||||||
|
*/
|
||||||
|
private function normalizeOrderColumn($tableName)
|
||||||
|
{
|
||||||
|
// 1. Ensure column type is BIGINT DEFAULT NULL
|
||||||
|
$column_info = DB::selectOne(
|
||||||
|
"SELECT COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
|
AND TABLE_NAME = ?
|
||||||
|
AND COLUMN_NAME = 'order'",
|
||||||
|
[$tableName]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($column_info) {
|
||||||
|
$needs_modify = false;
|
||||||
|
|
||||||
|
// Check if type is BIGINT (case-insensitive, may include display width)
|
||||||
|
if (stripos($column_info->COLUMN_TYPE, 'bigint') === false) {
|
||||||
|
$needs_modify = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if nullable
|
||||||
|
if ($column_info->IS_NULLABLE !== 'YES') {
|
||||||
|
$needs_modify = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if default is NULL
|
||||||
|
if ($column_info->COLUMN_DEFAULT !== null) {
|
||||||
|
$needs_modify = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($needs_modify) {
|
||||||
|
DB::statement("ALTER TABLE `$tableName` MODIFY COLUMN `order` BIGINT DEFAULT NULL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Ensure order_idx index exists
|
||||||
|
if (!$this->indexExists($tableName, 'order_idx')) {
|
||||||
|
DB::statement("ALTER TABLE `$tableName` ADD INDEX order_idx(`order`)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Ensure triggers exist for auto-incrementing NULL values
|
||||||
|
$insert_trigger_name = "{$tableName}_order_insert";
|
||||||
|
$update_trigger_name = "{$tableName}_order_update";
|
||||||
|
|
||||||
|
// Create INSERT trigger if not exists
|
||||||
|
if (!$this->triggerExists($insert_trigger_name)) {
|
||||||
|
DB::unprepared("
|
||||||
|
CREATE TRIGGER `{$insert_trigger_name}`
|
||||||
|
BEFORE INSERT ON `{$tableName}`
|
||||||
|
FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
IF NEW.`order` IS NULL THEN
|
||||||
|
SET NEW.`order` = (SELECT COALESCE(MAX(`order`), 0) + 1 FROM `{$tableName}`);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create UPDATE trigger if not exists
|
||||||
|
if (!$this->triggerExists($update_trigger_name)) {
|
||||||
|
DB::unprepared("
|
||||||
|
CREATE TRIGGER `{$update_trigger_name}`
|
||||||
|
BEFORE UPDATE ON `{$tableName}`
|
||||||
|
FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
IF NEW.`order` IS NULL THEN
|
||||||
|
SET NEW.`order` = (SELECT COALESCE(MAX(`order`), 0) + 1 FROM `{$tableName}`);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
241
app/RSpade/Commands/Rsx/Mode_Set_Command.php
Executable file
241
app/RSpade/Commands/Rsx/Mode_Set_Command.php
Executable file
@@ -0,0 +1,241 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\RSpade\Commands\Rsx;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use App\RSpade\Core\Mode\Rsx_Mode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the application mode (development, debug, or production)
|
||||||
|
*
|
||||||
|
* Changes the RSX_MODE in .env, clears caches, and rebuilds as appropriate.
|
||||||
|
*/
|
||||||
|
class Mode_Set_Command extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'rsx:mode:set
|
||||||
|
{mode : Mode to set (dev|development|debug|prod|production)}';
|
||||||
|
|
||||||
|
protected $description = 'Set the application mode (development, debug, or production)';
|
||||||
|
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
$mode = $this->argument('mode');
|
||||||
|
|
||||||
|
// Normalize mode aliases
|
||||||
|
$normalized = match (strtolower($mode)) {
|
||||||
|
'dev', 'development' => Rsx_Mode::DEVELOPMENT,
|
||||||
|
'debug' => Rsx_Mode::DEBUG,
|
||||||
|
'prod', 'production' => Rsx_Mode::PRODUCTION,
|
||||||
|
default => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($normalized === null) {
|
||||||
|
$this->error("Invalid mode: {$mode}");
|
||||||
|
$this->line('');
|
||||||
|
$this->line('Valid modes:');
|
||||||
|
$this->line(' dev, development - Auto-rebuild, full debugging');
|
||||||
|
$this->line(' debug - Production optimizations with sourcemaps');
|
||||||
|
$this->line(' prod, production - Full optimization');
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check current mode (for display purposes only - always rebuild)
|
||||||
|
$current_mode = $this->_get_current_env_mode();
|
||||||
|
if ($current_mode === $normalized) {
|
||||||
|
$this->info("Rebuilding {$normalized} mode...");
|
||||||
|
} else {
|
||||||
|
$this->info("Switching to {$normalized} mode...");
|
||||||
|
}
|
||||||
|
$this->newLine();
|
||||||
|
|
||||||
|
// Step 1: Update .env file
|
||||||
|
$this->line(' [1/3] Updating .env...');
|
||||||
|
$this->_update_env_mode($normalized);
|
||||||
|
|
||||||
|
// Clear the cached mode so subsequent calls see the new value
|
||||||
|
Rsx_Mode::clear_cache();
|
||||||
|
|
||||||
|
// Step 2: Clear all caches
|
||||||
|
$this->line(' [2/3] Clearing caches...');
|
||||||
|
|
||||||
|
// Clear Laravel caches by deleting files directly (avoids triggering Manifest::init)
|
||||||
|
$this->_clear_laravel_caches();
|
||||||
|
|
||||||
|
// Clear RSX caches directly (avoids triggering Manifest::init via artisan boot)
|
||||||
|
$this->_clear_rsx_caches();
|
||||||
|
|
||||||
|
// Step 3: Build appropriate assets
|
||||||
|
$this->line(' [3/3] Building assets...');
|
||||||
|
|
||||||
|
if ($normalized === Rsx_Mode::DEVELOPMENT) {
|
||||||
|
// In development, pre-warm the bundle cache
|
||||||
|
// Explicitly pass RSX_MODE to ensure subprocess uses correct mode
|
||||||
|
passthru("RSX_MODE={$normalized} php artisan rsx:bundle:compile", $exit_code);
|
||||||
|
if ($exit_code !== 0) {
|
||||||
|
$this->warn('Bundle compilation had warnings, but continuing...');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// In debug/production, run the production build
|
||||||
|
// RSX_FORCE_BUILD allows manifest rebuild even in production mode
|
||||||
|
// Explicitly pass RSX_MODE to ensure subprocess uses correct mode
|
||||||
|
passthru("RSX_MODE={$normalized} RSX_FORCE_BUILD=1 php artisan rsx:prod:build", $exit_code);
|
||||||
|
if ($exit_code !== 0) {
|
||||||
|
$this->error('Production build failed');
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->newLine();
|
||||||
|
$this->info("[OK] Switched to {$normalized} mode");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current RSX_MODE from .env file (not from env() which may be cached)
|
||||||
|
*/
|
||||||
|
private function _get_current_env_mode(): string
|
||||||
|
{
|
||||||
|
$env_path = base_path('.env');
|
||||||
|
if (!file_exists($env_path)) {
|
||||||
|
return Rsx_Mode::DEVELOPMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contents = file_get_contents($env_path);
|
||||||
|
if (preg_match('/^RSX_MODE=(.*)$/m', $contents, $matches)) {
|
||||||
|
return trim($matches[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Rsx_Mode::DEVELOPMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update RSX_MODE in .env file
|
||||||
|
*/
|
||||||
|
private function _update_env_mode(string $mode): void
|
||||||
|
{
|
||||||
|
$env_path = base_path('.env');
|
||||||
|
|
||||||
|
if (!file_exists($env_path)) {
|
||||||
|
// Create .env with just RSX_MODE
|
||||||
|
file_put_contents($env_path, "RSX_MODE={$mode}\n");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contents = file_get_contents($env_path);
|
||||||
|
|
||||||
|
if (preg_match('/^RSX_MODE=.*$/m', $contents)) {
|
||||||
|
// Replace existing RSX_MODE
|
||||||
|
$contents = preg_replace('/^RSX_MODE=.*$/m', "RSX_MODE={$mode}", $contents);
|
||||||
|
} else {
|
||||||
|
// Add RSX_MODE after APP_DEBUG or APP_URL if they exist, otherwise at end
|
||||||
|
if (preg_match('/^APP_DEBUG=.*$/m', $contents)) {
|
||||||
|
$contents = preg_replace(
|
||||||
|
'/^(APP_DEBUG=.*)$/m',
|
||||||
|
"$1\nRSX_MODE={$mode}",
|
||||||
|
$contents,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
} elseif (preg_match('/^APP_URL=.*$/m', $contents)) {
|
||||||
|
$contents = preg_replace(
|
||||||
|
'/^(APP_URL=.*)$/m',
|
||||||
|
"$1\nRSX_MODE={$mode}",
|
||||||
|
$contents,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Append to end
|
||||||
|
$contents = rtrim($contents) . "\nRSX_MODE={$mode}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents($env_path, $contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear Laravel caches by deleting files directly
|
||||||
|
* (Avoids running artisan commands which trigger Manifest::init)
|
||||||
|
*/
|
||||||
|
private function _clear_laravel_caches(): void
|
||||||
|
{
|
||||||
|
$bootstrap_cache = base_path('bootstrap/cache');
|
||||||
|
|
||||||
|
// Config cache
|
||||||
|
$config_cache = "{$bootstrap_cache}/config.php";
|
||||||
|
if (file_exists($config_cache)) {
|
||||||
|
unlink($config_cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route cache
|
||||||
|
$route_cache = "{$bootstrap_cache}/routes-v7.php";
|
||||||
|
if (file_exists($route_cache)) {
|
||||||
|
unlink($route_cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Services cache
|
||||||
|
$services_cache = "{$bootstrap_cache}/services.php";
|
||||||
|
if (file_exists($services_cache)) {
|
||||||
|
unlink($services_cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Packages cache
|
||||||
|
$packages_cache = "{$bootstrap_cache}/packages.php";
|
||||||
|
if (file_exists($packages_cache)) {
|
||||||
|
unlink($packages_cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compiled views
|
||||||
|
$views_path = storage_path('framework/views');
|
||||||
|
if (is_dir($views_path)) {
|
||||||
|
$files = glob("{$views_path}/*.php");
|
||||||
|
foreach ($files as $file) {
|
||||||
|
unlink($file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear RSX caches by deleting directories directly
|
||||||
|
* (Avoids running artisan commands which trigger Manifest::init)
|
||||||
|
*/
|
||||||
|
private function _clear_rsx_caches(): void
|
||||||
|
{
|
||||||
|
// Clear rsx-build directory
|
||||||
|
$build_path = storage_path('rsx-build');
|
||||||
|
if (is_dir($build_path)) {
|
||||||
|
$this->_clear_directory_contents($build_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear rsx-tmp directory
|
||||||
|
$tmp_path = storage_path('rsx-tmp');
|
||||||
|
if (is_dir($tmp_path)) {
|
||||||
|
$this->_clear_directory_contents($tmp_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively clear directory contents
|
||||||
|
*/
|
||||||
|
private function _clear_directory_contents(string $path): void
|
||||||
|
{
|
||||||
|
if (!is_dir($path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = new \RecursiveIteratorIterator(
|
||||||
|
new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS),
|
||||||
|
\RecursiveIteratorIterator::CHILD_FIRST
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if ($file->isDir()) {
|
||||||
|
rmdir($file->getRealPath());
|
||||||
|
} else {
|
||||||
|
unlink($file->getRealPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
180
app/RSpade/Commands/Rsx/Prod_Build_Command.php
Executable file
180
app/RSpade/Commands/Rsx/Prod_Build_Command.php
Executable file
@@ -0,0 +1,180 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\RSpade\Commands\Rsx;
|
||||||
|
|
||||||
|
use App\RSpade\Core\Bundle\BundleCompiler;
|
||||||
|
use App\RSpade\Core\Bundle\Minifier;
|
||||||
|
use App\RSpade\Core\Manifest\Manifest;
|
||||||
|
use App\RSpade\Core\Mode\Rsx_Mode;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build all production assets
|
||||||
|
*
|
||||||
|
* Rebuilds manifest and compiles all bundles for production/debug mode.
|
||||||
|
* Must be run before the application can serve pages in production-like modes.
|
||||||
|
*/
|
||||||
|
class Prod_Build_Command extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'rsx:prod:build
|
||||||
|
{--force : Force rebuild even if assets exist}
|
||||||
|
{--skip-laravel-cache : Skip Laravel config/route/view caching}';
|
||||||
|
|
||||||
|
protected $description = 'Build all production assets (manifest, bundles, caches)';
|
||||||
|
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
$mode = Rsx_Mode::get();
|
||||||
|
|
||||||
|
if ($mode === Rsx_Mode::DEVELOPMENT) {
|
||||||
|
$this->warn('Warning: Building production assets in development mode.');
|
||||||
|
$this->line('Assets will be built but will not be used until you switch modes:');
|
||||||
|
$this->line(' php artisan rsx:mode:set debug');
|
||||||
|
$this->line(' php artisan rsx:mode:set production');
|
||||||
|
$this->newLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('Building production assets...');
|
||||||
|
$this->newLine();
|
||||||
|
|
||||||
|
$start_time = microtime(true);
|
||||||
|
|
||||||
|
// Step 1: Rebuild manifest
|
||||||
|
$this->line('[1/3] Building manifest...');
|
||||||
|
try {
|
||||||
|
// Enable force build mode to allow rebuilding in production-like modes
|
||||||
|
Manifest::$_force_build = true;
|
||||||
|
|
||||||
|
// Force a fresh manifest rebuild by clearing and re-initializing
|
||||||
|
Manifest::clear();
|
||||||
|
Manifest::init();
|
||||||
|
$this->line(' Manifest built successfully');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->error(' Failed to build manifest: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
} finally {
|
||||||
|
Manifest::$_force_build = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Compile all bundles
|
||||||
|
$this->line('[2/3] Compiling bundles...');
|
||||||
|
|
||||||
|
// Force restart minify server to pick up any code changes
|
||||||
|
if (Rsx_Mode::is_production()) {
|
||||||
|
Minifier::force_restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
$manifest_data = Manifest::get_all();
|
||||||
|
$bundle_classes = [];
|
||||||
|
|
||||||
|
foreach ($manifest_data as $file_info) {
|
||||||
|
$class_name = $file_info['class'] ?? null;
|
||||||
|
if ($class_name && Manifest::php_is_subclass_of($class_name, 'Rsx_Module_Bundle_Abstract')) {
|
||||||
|
$fqcn = $file_info['fqcn'] ?? $class_name;
|
||||||
|
$bundle_classes[$fqcn] = $class_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($bundle_classes)) {
|
||||||
|
$this->warn(' No bundles found in manifest');
|
||||||
|
} else {
|
||||||
|
// Ensure storage directory exists
|
||||||
|
$bundle_dir = storage_path('rsx-build/bundles');
|
||||||
|
if (!is_dir($bundle_dir)) {
|
||||||
|
mkdir($bundle_dir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$compiled_count = 0;
|
||||||
|
$failed_bundles = [];
|
||||||
|
|
||||||
|
foreach ($bundle_classes as $fqcn => $class_name) {
|
||||||
|
$this->line(" Compiling: {$class_name}");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$compiler = new BundleCompiler();
|
||||||
|
$compiled = $compiler->compile($fqcn, ['force_build' => true]);
|
||||||
|
|
||||||
|
// Get output file info (always vendor/app split)
|
||||||
|
$js_size = 0;
|
||||||
|
$css_size = 0;
|
||||||
|
|
||||||
|
if (isset($compiled['vendor_js_bundle_path'])) {
|
||||||
|
$js_size += filesize("{$bundle_dir}/{$compiled['vendor_js_bundle_path']}");
|
||||||
|
}
|
||||||
|
if (isset($compiled['app_js_bundle_path'])) {
|
||||||
|
$js_size += filesize("{$bundle_dir}/{$compiled['app_js_bundle_path']}");
|
||||||
|
}
|
||||||
|
if (isset($compiled['vendor_css_bundle_path'])) {
|
||||||
|
$css_size += filesize("{$bundle_dir}/{$compiled['vendor_css_bundle_path']}");
|
||||||
|
}
|
||||||
|
if (isset($compiled['app_css_bundle_path'])) {
|
||||||
|
$css_size += filesize("{$bundle_dir}/{$compiled['app_css_bundle_path']}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->line(' JS: ' . $this->_format_size($js_size) . ', CSS: ' . $this->_format_size($css_size));
|
||||||
|
$compiled_count++;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->error(" Failed: {$e->getMessage()}");
|
||||||
|
$failed_bundles[$class_name] = $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($failed_bundles)) {
|
||||||
|
$this->error(" {$compiled_count} compiled, " . count($failed_bundles) . ' failed');
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->line(" {$compiled_count} bundles compiled");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Laravel caches
|
||||||
|
if (!$this->option('skip-laravel-cache')) {
|
||||||
|
$this->line('[3/3] Building Laravel caches...');
|
||||||
|
|
||||||
|
passthru('php artisan config:cache 2>/dev/null', $exit_code);
|
||||||
|
if ($exit_code === 0) {
|
||||||
|
$this->line(' Config cached');
|
||||||
|
}
|
||||||
|
|
||||||
|
passthru('php artisan route:cache 2>/dev/null', $exit_code);
|
||||||
|
if ($exit_code === 0) {
|
||||||
|
$this->line(' Routes cached');
|
||||||
|
}
|
||||||
|
|
||||||
|
passthru('php artisan view:cache 2>/dev/null', $exit_code);
|
||||||
|
if ($exit_code === 0) {
|
||||||
|
$this->line(' Views cached');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->line('[3/3] Skipping Laravel caches (--skip-laravel-cache)');
|
||||||
|
}
|
||||||
|
|
||||||
|
$elapsed = round(microtime(true) - $start_time, 2);
|
||||||
|
|
||||||
|
$this->newLine();
|
||||||
|
$this->info("[OK] Production build complete ({$elapsed}s)");
|
||||||
|
|
||||||
|
if ($mode === Rsx_Mode::DEVELOPMENT) {
|
||||||
|
$this->newLine();
|
||||||
|
$this->line('To use these assets, switch to production mode:');
|
||||||
|
$this->line(' php artisan rsx:mode:set production');
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _format_size(int $bytes): string
|
||||||
|
{
|
||||||
|
if ($bytes < 1024) {
|
||||||
|
return "{$bytes} B";
|
||||||
|
}
|
||||||
|
if ($bytes < 1048576) {
|
||||||
|
return round($bytes / 1024, 1) . ' KB';
|
||||||
|
}
|
||||||
|
|
||||||
|
return round($bytes / 1048576, 2) . ' MB';
|
||||||
|
}
|
||||||
|
}
|
||||||
231
app/RSpade/Commands/Rsx/Prod_Export_Command.php
Executable file
231
app/RSpade/Commands/Rsx/Prod_Export_Command.php
Executable file
@@ -0,0 +1,231 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\RSpade\Commands\Rsx;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use RecursiveDirectoryIterator;
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export the application for deployment
|
||||||
|
*
|
||||||
|
* Runs rsx:prod:build then copies all necessary files to an export directory.
|
||||||
|
* The export can be deployed to production servers.
|
||||||
|
*/
|
||||||
|
class Prod_Export_Command extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'rsx:prod:export
|
||||||
|
{--path=./rsx-export : Export destination directory}
|
||||||
|
{--skip-build : Skip running rsx:prod:build (use existing assets)}';
|
||||||
|
|
||||||
|
protected $description = 'Export application for deployment';
|
||||||
|
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
$export_path = $this->option('path');
|
||||||
|
|
||||||
|
// Resolve relative path from base_path parent (since we're in /system)
|
||||||
|
if (str_starts_with($export_path, './')) {
|
||||||
|
$export_path = dirname(base_path()) . '/' . substr($export_path, 2);
|
||||||
|
} elseif (!str_starts_with($export_path, '/')) {
|
||||||
|
$export_path = dirname(base_path()) . '/' . $export_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('Exporting application for deployment...');
|
||||||
|
$this->line(" Destination: {$export_path}");
|
||||||
|
$this->newLine();
|
||||||
|
|
||||||
|
// Step 1: Run production build (unless --skip-build)
|
||||||
|
if (!$this->option('skip-build')) {
|
||||||
|
$this->line('[1/3] Running production build...');
|
||||||
|
passthru('php artisan rsx:prod:build', $exit_code);
|
||||||
|
if ($exit_code !== 0) {
|
||||||
|
$this->error('Production build failed');
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
$this->newLine();
|
||||||
|
} else {
|
||||||
|
$this->line('[1/3] Skipping production build (--skip-build)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Prepare export directory
|
||||||
|
$this->line('[2/3] Preparing export directory...');
|
||||||
|
|
||||||
|
// Clear existing export if it exists
|
||||||
|
if (is_dir($export_path)) {
|
||||||
|
$this->line(' Clearing existing export...');
|
||||||
|
$this->_clear_directory($export_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create export directory
|
||||||
|
if (!mkdir($export_path, 0755, true) && !is_dir($export_path)) {
|
||||||
|
$this->error("Failed to create export directory: {$export_path}");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Copy files
|
||||||
|
$this->line('[3/3] Copying files...');
|
||||||
|
|
||||||
|
$base = dirname(base_path()); // Parent of /system
|
||||||
|
$copied_files = 0;
|
||||||
|
$copied_dirs = 0;
|
||||||
|
|
||||||
|
// Directories to copy
|
||||||
|
$dirs_to_copy = [
|
||||||
|
'system' => 'system',
|
||||||
|
'rsx' => 'rsx',
|
||||||
|
'node_modules' => 'node_modules',
|
||||||
|
'vendor' => 'vendor',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Also include rsx-build from storage
|
||||||
|
$storage_rsx_build = base_path('storage/rsx-build');
|
||||||
|
if (is_dir($storage_rsx_build)) {
|
||||||
|
$dirs_to_copy['system/storage/rsx-build'] = 'system/storage/rsx-build';
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($dirs_to_copy as $src_rel => $dest_rel) {
|
||||||
|
$src = "{$base}/{$src_rel}";
|
||||||
|
$dest = "{$export_path}/{$dest_rel}";
|
||||||
|
|
||||||
|
if (!is_dir($src)) {
|
||||||
|
$this->warn(" Skipping {$src_rel} (not found)");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->line(" Copying {$src_rel}/...");
|
||||||
|
$count = $this->_copy_directory($src, $dest, $src_rel);
|
||||||
|
$copied_files += $count;
|
||||||
|
$copied_dirs++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy *.json files from root
|
||||||
|
$json_files = glob("{$base}/*.json");
|
||||||
|
foreach ($json_files as $json_file) {
|
||||||
|
$filename = basename($json_file);
|
||||||
|
copy($json_file, "{$export_path}/{$filename}");
|
||||||
|
$copied_files++;
|
||||||
|
}
|
||||||
|
if (!empty($json_files)) {
|
||||||
|
$this->line(' Copying *.json files...');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create .gitignore for export directory
|
||||||
|
file_put_contents(
|
||||||
|
"{$export_path}/.gitignore",
|
||||||
|
"# This export should not be committed to version control\n*\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->newLine();
|
||||||
|
$this->info("[OK] Export complete");
|
||||||
|
$this->line(" {$copied_dirs} directories, {$copied_files} files");
|
||||||
|
$this->line(" Location: {$export_path}");
|
||||||
|
$this->newLine();
|
||||||
|
$this->line('Next steps:');
|
||||||
|
$this->line(' 1. Copy the export directory to your production server');
|
||||||
|
$this->line(' 2. Configure .env on the production server');
|
||||||
|
$this->line(' 3. Point your web server to the deployment');
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy a directory recursively, excluding certain paths
|
||||||
|
*/
|
||||||
|
private function _copy_directory(string $src, string $dest, string $rel_path): int
|
||||||
|
{
|
||||||
|
// Paths to exclude (relative to source)
|
||||||
|
$exclude_patterns = [
|
||||||
|
'.env',
|
||||||
|
'.env.example',
|
||||||
|
'storage/app',
|
||||||
|
'storage/logs',
|
||||||
|
'storage/framework/cache',
|
||||||
|
'storage/framework/sessions',
|
||||||
|
'storage/framework/views',
|
||||||
|
'storage/rsx-tmp',
|
||||||
|
'rsx-export',
|
||||||
|
'.git',
|
||||||
|
'.idea',
|
||||||
|
'.vscode',
|
||||||
|
'tests',
|
||||||
|
];
|
||||||
|
|
||||||
|
// For system directory, exclude additional paths
|
||||||
|
if ($rel_path === 'system') {
|
||||||
|
$exclude_patterns[] = 'storage/rsx-tmp';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_dir($dest)) {
|
||||||
|
mkdir($dest, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$copied = 0;
|
||||||
|
$iterator = new RecursiveIteratorIterator(
|
||||||
|
new RecursiveDirectoryIterator($src, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||||
|
RecursiveIteratorIterator::SELF_FIRST
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($iterator as $item) {
|
||||||
|
$sub_path = $iterator->getSubPathname();
|
||||||
|
|
||||||
|
// Check exclusions
|
||||||
|
$skip = false;
|
||||||
|
foreach ($exclude_patterns as $pattern) {
|
||||||
|
if (str_starts_with($sub_path, $pattern) || str_contains($sub_path, "/{$pattern}")) {
|
||||||
|
$skip = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($skip) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dest_path = "{$dest}/{$sub_path}";
|
||||||
|
|
||||||
|
if ($item->isDir()) {
|
||||||
|
if (!is_dir($dest_path)) {
|
||||||
|
mkdir($dest_path, 0755, true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Ensure parent directory exists
|
||||||
|
$parent = dirname($dest_path);
|
||||||
|
if (!is_dir($parent)) {
|
||||||
|
mkdir($parent, 0755, true);
|
||||||
|
}
|
||||||
|
copy($item->getPathname(), $dest_path);
|
||||||
|
$copied++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $copied;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear a directory recursively
|
||||||
|
*/
|
||||||
|
private function _clear_directory(string $path): void
|
||||||
|
{
|
||||||
|
if (!is_dir($path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$iterator = new RecursiveIteratorIterator(
|
||||||
|
new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||||
|
RecursiveIteratorIterator::CHILD_FIRST
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($iterator as $item) {
|
||||||
|
if ($item->isDir()) {
|
||||||
|
rmdir($item->getPathname());
|
||||||
|
} else {
|
||||||
|
unlink($item->getPathname());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rmdir($path);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,10 +7,13 @@ use RecursiveCallbackFilterIterator;
|
|||||||
use RecursiveDirectoryIterator;
|
use RecursiveDirectoryIterator;
|
||||||
use RecursiveIteratorIterator;
|
use RecursiveIteratorIterator;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
use App\RSpade\Core\Bundle\Cdn_Cache;
|
||||||
|
use App\RSpade\Core\Bundle\Minifier;
|
||||||
use App\RSpade\Core\Bundle\Rsx_Asset_Bundle_Abstract;
|
use App\RSpade\Core\Bundle\Rsx_Asset_Bundle_Abstract;
|
||||||
use App\RSpade\Core\Bundle\Rsx_Module_Bundle_Abstract;
|
use App\RSpade\Core\Bundle\Rsx_Module_Bundle_Abstract;
|
||||||
use App\RSpade\Core\Locks\RsxLocks;
|
use App\RSpade\Core\Locks\RsxLocks;
|
||||||
use App\RSpade\Core\Manifest\Manifest;
|
use App\RSpade\Core\Manifest\Manifest;
|
||||||
|
use App\RSpade\Core\Mode\Rsx_Mode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BundleCompiler - Compiles RSX bundles into JS and CSS files
|
* BundleCompiler - Compiles RSX bundles into JS and CSS files
|
||||||
@@ -121,18 +124,25 @@ class BundleCompiler
|
|||||||
public function compile(string $bundle_class, array $options = []): array
|
public function compile(string $bundle_class, array $options = []): array
|
||||||
{
|
{
|
||||||
$this->bundle_name = $this->_get_bundle_name($bundle_class);
|
$this->bundle_name = $this->_get_bundle_name($bundle_class);
|
||||||
$this->is_production = app()->environment('production');
|
$this->is_production = Rsx_Mode::is_production_like();
|
||||||
|
$force_build = $options['force_build'] ?? false;
|
||||||
|
|
||||||
console_debug('BUNDLE', "Compiling {$this->bundle_name} (production: " . ($this->is_production ? 'yes' : 'no') . ')');
|
console_debug('BUNDLE', "Compiling {$this->bundle_name} (mode: " . Rsx_Mode::get() . ')');
|
||||||
|
|
||||||
// Step 1: Check production cache
|
// Step 1: In production-like modes, require pre-built bundles (unless force_build)
|
||||||
if ($this->is_production) {
|
if ($this->is_production && !$force_build) {
|
||||||
$existing = $this->_check_production_cache();
|
$existing = $this->_check_production_cache();
|
||||||
if ($existing) {
|
if ($existing) {
|
||||||
console_debug('BUNDLE', 'Using existing production bundle');
|
console_debug('BUNDLE', 'Using existing production bundle');
|
||||||
|
|
||||||
return $existing;
|
return $existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In production-like modes, don't auto-rebuild - error instead
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Bundle '{$this->bundle_name}' not compiled for production mode. " .
|
||||||
|
'Run: php artisan rsx:prod:build'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Mark the bundle we're compiling as already resolved
|
// Step 2: Mark the bundle we're compiling as already resolved
|
||||||
@@ -148,9 +158,18 @@ class BundleCompiler
|
|||||||
// Step 5: Always split into vendor/app
|
// Step 5: Always split into vendor/app
|
||||||
$this->_split_vendor_app();
|
$this->_split_vendor_app();
|
||||||
|
|
||||||
// Step 6: Check individual bundle caches
|
// Step 6: Check individual bundle caches (or force all if force_build)
|
||||||
|
if ($force_build) {
|
||||||
|
$need_compile = ['vendor', 'app'];
|
||||||
|
$this->cache_keys = [];
|
||||||
|
foreach ($need_compile as $type) {
|
||||||
|
$this->cache_keys[$type] = $this->_get_cache_key($type);
|
||||||
|
}
|
||||||
|
console_debug('BUNDLE', 'Force build - compiling all types');
|
||||||
|
} else {
|
||||||
$need_compile = $this->_check_bundle_caches();
|
$need_compile = $this->_check_bundle_caches();
|
||||||
console_debug('BUNDLE', 'Need compile: ' . json_encode($need_compile));
|
console_debug('BUNDLE', 'Need compile: ' . json_encode($need_compile));
|
||||||
|
}
|
||||||
|
|
||||||
// Step 7-10: Process bundles that need compilation
|
// Step 7-10: Process bundles that need compilation
|
||||||
if (!empty($need_compile)) {
|
if (!empty($need_compile)) {
|
||||||
@@ -205,15 +224,16 @@ class BundleCompiler
|
|||||||
|
|
||||||
// Step 13: Return data for render() method (not CLI)
|
// Step 13: Return data for render() method (not CLI)
|
||||||
// Include CDN assets and proper file paths for HTML generation
|
// Include CDN assets and proper file paths for HTML generation
|
||||||
$bundle_dir = storage_path('rsx-build/bundles');
|
|
||||||
$result = [];
|
$result = [];
|
||||||
|
|
||||||
// Add CDN assets
|
// CDN assets are always output as separate tags
|
||||||
|
// In development mode: loaded directly from CDN URLs
|
||||||
|
// In production-like modes: served from /_vendor/ (cached locally)
|
||||||
if (!empty($this->cdn_assets['js'])) {
|
if (!empty($this->cdn_assets['js'])) {
|
||||||
$result['cdn_js'] = $this->cdn_assets['js'];
|
$result['cdn_js'] = $this->_prepare_cdn_assets($this->cdn_assets['js'], 'js');
|
||||||
}
|
}
|
||||||
if (!empty($this->cdn_assets['css'])) {
|
if (!empty($this->cdn_assets['css'])) {
|
||||||
$result['cdn_css'] = $this->cdn_assets['css'];
|
$result['cdn_css'] = $this->_prepare_cdn_assets($this->cdn_assets['css'], 'css');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add public directory assets
|
// Add public directory assets
|
||||||
@@ -224,8 +244,7 @@ class BundleCompiler
|
|||||||
$result['public_css'] = $this->public_assets['css'];
|
$result['public_css'] = $this->public_assets['css'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add bundle file paths for development
|
// Always return vendor/app split paths
|
||||||
if (!$this->is_production) {
|
|
||||||
if (isset($outputs['vendor_js'])) {
|
if (isset($outputs['vendor_js'])) {
|
||||||
$result['vendor_js_bundle_path'] = $outputs['vendor_js'];
|
$result['vendor_js_bundle_path'] = $outputs['vendor_js'];
|
||||||
}
|
}
|
||||||
@@ -238,47 +257,6 @@ class BundleCompiler
|
|||||||
if (isset($outputs['app_css'])) {
|
if (isset($outputs['app_css'])) {
|
||||||
$result['app_css_bundle_path'] = $outputs['app_css'];
|
$result['app_css_bundle_path'] = $outputs['app_css'];
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Production mode - simple concatenation of vendor + app
|
|
||||||
$js_content = '';
|
|
||||||
if (isset($outputs['vendor_js'])) {
|
|
||||||
$js_content = file_get_contents("{$bundle_dir}/{$outputs['vendor_js']}");
|
|
||||||
}
|
|
||||||
if (isset($outputs['app_js'])) {
|
|
||||||
// Simple concatenation with newline separator
|
|
||||||
if ($js_content) {
|
|
||||||
$js_content .= "\n";
|
|
||||||
}
|
|
||||||
$js_content .= file_get_contents("{$bundle_dir}/{$outputs['app_js']}");
|
|
||||||
}
|
|
||||||
|
|
||||||
$css_content = '';
|
|
||||||
if (isset($outputs['vendor_css'])) {
|
|
||||||
$css_content = file_get_contents("{$bundle_dir}/{$outputs['vendor_css']}");
|
|
||||||
}
|
|
||||||
if (isset($outputs['app_css'])) {
|
|
||||||
// Simple concatenation with newline separator
|
|
||||||
if ($css_content) {
|
|
||||||
$css_content .= "\n";
|
|
||||||
}
|
|
||||||
$css_content .= file_get_contents("{$bundle_dir}/{$outputs['app_css']}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write combined files with content hash
|
|
||||||
if ($js_content) {
|
|
||||||
$js_hash = substr(md5($js_content), 0, 16);
|
|
||||||
$js_file = "app.{$js_hash}.js";
|
|
||||||
file_put_contents("{$bundle_dir}/{$js_file}", $js_content);
|
|
||||||
$result['js_bundle_path'] = $js_file;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($css_content) {
|
|
||||||
$css_hash = substr(md5($css_content), 0, 16);
|
|
||||||
$css_file = "app.{$css_hash}.css";
|
|
||||||
file_put_contents("{$bundle_dir}/{$css_file}", $css_content);
|
|
||||||
$result['css_bundle_path'] = $css_file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add config if present
|
// Add config if present
|
||||||
if (!empty($this->config)) {
|
if (!empty($this->config)) {
|
||||||
@@ -441,28 +419,137 @@ class BundleCompiler
|
|||||||
return end($parts);
|
return end($parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get bundle FQCN from simple class name
|
||||||
|
*/
|
||||||
|
protected function _get_bundle_fqcn(string $bundle_name): string
|
||||||
|
{
|
||||||
|
// If already a FQCN, return as-is
|
||||||
|
if (str_contains($bundle_name, '\\')) {
|
||||||
|
return $bundle_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up in Manifest
|
||||||
|
$metadata = Manifest::php_get_metadata_by_class($bundle_name);
|
||||||
|
|
||||||
|
return $metadata['fqcn'];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if production bundle already exists
|
* Check if production bundle already exists
|
||||||
*/
|
*/
|
||||||
protected function _check_production_cache(): ?array
|
protected function _check_production_cache(): ?array
|
||||||
{
|
{
|
||||||
$bundle_dir = storage_path('rsx-build/bundles');
|
$bundle_dir = storage_path('rsx-build/bundles');
|
||||||
$js_pattern = "{$bundle_dir}/app.*.js";
|
|
||||||
$css_pattern = "{$bundle_dir}/app.*.css";
|
|
||||||
|
|
||||||
$js_files = glob($js_pattern);
|
// Look for split vendor/app files (current output format)
|
||||||
$css_files = glob($css_pattern);
|
// Future: support merged files when Rsx_Mode::should_merge_bundles()
|
||||||
|
$vendor_js_pattern = "{$bundle_dir}/{$this->bundle_name}__vendor.*.js";
|
||||||
|
$app_js_pattern = "{$bundle_dir}/{$this->bundle_name}__app.*.js";
|
||||||
|
$vendor_css_pattern = "{$bundle_dir}/{$this->bundle_name}__vendor.*.css";
|
||||||
|
$app_css_pattern = "{$bundle_dir}/{$this->bundle_name}__app.*.css";
|
||||||
|
|
||||||
if (!empty($js_files) || !empty($css_files)) {
|
$vendor_js_files = glob($vendor_js_pattern);
|
||||||
return [
|
$app_js_files = glob($app_js_pattern);
|
||||||
'js_bundle_path' => !empty($js_files) ? basename($js_files[0]) : null,
|
$vendor_css_files = glob($vendor_css_pattern);
|
||||||
'css_bundle_path' => !empty($css_files) ? basename($css_files[0]) : null,
|
$app_css_files = glob($app_css_pattern);
|
||||||
|
|
||||||
|
// Need at least one app file (JS is typically required)
|
||||||
|
if (!empty($app_js_files)) {
|
||||||
|
$result = [
|
||||||
|
'vendor_js_bundle_path' => !empty($vendor_js_files) ? basename($vendor_js_files[0]) : null,
|
||||||
|
'app_js_bundle_path' => !empty($app_js_files) ? basename($app_js_files[0]) : null,
|
||||||
|
'vendor_css_bundle_path' => !empty($vendor_css_files) ? basename($vendor_css_files[0]) : null,
|
||||||
|
'app_css_bundle_path' => !empty($app_css_files) ? basename($app_css_files[0]) : null,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Also resolve CDN assets - they're served separately via /_vendor/ URLs
|
||||||
|
// We need to resolve the bundle includes to get CDN asset definitions
|
||||||
|
$this->_resolve_cdn_assets_only();
|
||||||
|
|
||||||
|
if (!empty($this->cdn_assets['js'])) {
|
||||||
|
$result['cdn_js'] = $this->_prepare_cdn_assets($this->cdn_assets['js'], 'js');
|
||||||
|
}
|
||||||
|
if (!empty($this->cdn_assets['css'])) {
|
||||||
|
$result['cdn_css'] = $this->_prepare_cdn_assets($this->cdn_assets['css'], 'css');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve only CDN assets without full bundle compilation
|
||||||
|
*
|
||||||
|
* Used when serving from production cache to get CDN asset URLs
|
||||||
|
* without re-resolving and re-compiling all bundle files.
|
||||||
|
*/
|
||||||
|
protected function _resolve_cdn_assets_only(): void
|
||||||
|
{
|
||||||
|
// Process required bundles first (they may have CDN assets)
|
||||||
|
$required_bundles = config('rsx.required_bundles', []);
|
||||||
|
$bundle_aliases = config('rsx.bundle_aliases', []);
|
||||||
|
|
||||||
|
foreach ($required_bundles as $alias) {
|
||||||
|
if (isset($bundle_aliases[$alias])) {
|
||||||
|
$this->_collect_cdn_assets_from_include($bundle_aliases[$alias]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the bundle's own CDN assets
|
||||||
|
$fqcn = $this->_get_bundle_fqcn($this->bundle_name);
|
||||||
|
$definition = $fqcn::define();
|
||||||
|
|
||||||
|
// Add CDN assets from the bundle definition (same format as main resolution)
|
||||||
|
if (!empty($definition['cdn_assets'])) {
|
||||||
|
if (!empty($definition['cdn_assets']['js'])) {
|
||||||
|
$this->cdn_assets['js'] = array_merge($this->cdn_assets['js'], $definition['cdn_assets']['js']);
|
||||||
|
}
|
||||||
|
if (!empty($definition['cdn_assets']['css'])) {
|
||||||
|
$this->cdn_assets['css'] = array_merge($this->cdn_assets['css'], $definition['cdn_assets']['css']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check for Asset Bundle includes that may have CDN assets
|
||||||
|
foreach ($definition['include'] ?? [] as $include) {
|
||||||
|
$this->_collect_cdn_assets_from_include($include);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect CDN assets from a bundle include without full resolution
|
||||||
|
*/
|
||||||
|
protected function _collect_cdn_assets_from_include($include): void
|
||||||
|
{
|
||||||
|
// Handle config array format (from bundle aliases like jquery, lodash)
|
||||||
|
if (is_array($include) && isset($include['cdn'])) {
|
||||||
|
foreach ($include['cdn'] as $cdn_item) {
|
||||||
|
// Determine type from URL extension
|
||||||
|
$url = $cdn_item['url'] ?? '';
|
||||||
|
$type = str_ends_with($url, '.css') ? 'css' : 'js';
|
||||||
|
$this->cdn_assets[$type][] = $cdn_item;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle class name includes (could be Asset Bundles with CDN assets)
|
||||||
|
if (is_string($include) && Manifest::php_find_class($include)) {
|
||||||
|
if (Manifest::php_is_subclass_of($include, 'Rsx_Asset_Bundle_Abstract')) {
|
||||||
|
$asset_def = $include::define();
|
||||||
|
if (!empty($asset_def['cdn_assets'])) {
|
||||||
|
if (!empty($asset_def['cdn_assets']['js'])) {
|
||||||
|
$this->cdn_assets['js'] = array_merge($this->cdn_assets['js'], $asset_def['cdn_assets']['js']);
|
||||||
|
}
|
||||||
|
if (!empty($asset_def['cdn_assets']['css'])) {
|
||||||
|
$this->cdn_assets['css'] = array_merge($this->cdn_assets['css'], $asset_def['cdn_assets']['css']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process required bundles (jquery, lodash, jqhtml)
|
* Process required bundles (jquery, lodash, jqhtml)
|
||||||
*/
|
*/
|
||||||
@@ -1856,7 +1943,18 @@ implode("\n", array_map(fn ($f) => ' - ' . str_replace(base_path() . '/', '',
|
|||||||
|
|
||||||
// Compile JS
|
// Compile JS
|
||||||
if (!empty($files['js'])) {
|
if (!empty($files['js'])) {
|
||||||
$js_content = $this->_compile_js_files($files['js']);
|
$js_files = $files['js'];
|
||||||
|
|
||||||
|
// CDN assets are served separately via /_vendor/ URLs, not merged into bundle
|
||||||
|
// This avoids complex concatenation issues with third-party code
|
||||||
|
|
||||||
|
$js_content = $this->_compile_js_files($js_files);
|
||||||
|
|
||||||
|
// Minify JS in production mode only (strips sourcemaps)
|
||||||
|
if (Rsx_Mode::is_production()) {
|
||||||
|
$js_content = Minifier::minify_js($js_content, "{$this->bundle_name}__{$type}.js");
|
||||||
|
}
|
||||||
|
|
||||||
$js_file = "{$this->bundle_name}__{$type}.{$hash}.js";
|
$js_file = "{$this->bundle_name}__{$type}.{$hash}.js";
|
||||||
file_put_contents("{$bundle_dir}/{$js_file}", $js_content);
|
file_put_contents("{$bundle_dir}/{$js_file}", $js_content);
|
||||||
$outputs["{$type}_js"] = $js_file;
|
$outputs["{$type}_js"] = $js_file;
|
||||||
@@ -1864,7 +1962,18 @@ implode("\n", array_map(fn ($f) => ' - ' . str_replace(base_path() . '/', '',
|
|||||||
|
|
||||||
// Compile CSS
|
// Compile CSS
|
||||||
if (!empty($files['css'])) {
|
if (!empty($files['css'])) {
|
||||||
$css_content = $this->_compile_css_files($files['css']);
|
$css_files = $files['css'];
|
||||||
|
|
||||||
|
// CDN assets are served separately via /_vendor/ URLs, not merged into bundle
|
||||||
|
// This avoids complex concatenation issues with third-party code
|
||||||
|
|
||||||
|
$css_content = $this->_compile_css_files($css_files);
|
||||||
|
|
||||||
|
// Minify CSS in production mode only (strips sourcemaps)
|
||||||
|
if (Rsx_Mode::is_production()) {
|
||||||
|
$css_content = Minifier::minify_css($css_content, "{$this->bundle_name}__{$type}.css");
|
||||||
|
}
|
||||||
|
|
||||||
$css_file = "{$this->bundle_name}__{$type}.{$hash}.css";
|
$css_file = "{$this->bundle_name}__{$type}.{$hash}.css";
|
||||||
file_put_contents("{$bundle_dir}/{$css_file}", $css_content);
|
file_put_contents("{$bundle_dir}/{$css_file}", $css_content);
|
||||||
$outputs["{$type}_css"] = $css_file;
|
$outputs["{$type}_css"] = $css_file;
|
||||||
@@ -2189,8 +2298,11 @@ implode("\n", array_map(fn ($f) => ' - ' . str_replace(base_path() . '/', '',
|
|||||||
// This preserves dependency sort order - we substitute babel versions during concat
|
// This preserves dependency sort order - we substitute babel versions during concat
|
||||||
|
|
||||||
foreach ($files as $file) {
|
foreach ($files as $file) {
|
||||||
// Skip temp files and already processed files
|
// Skip temp files, already processed files, and CDN cache files
|
||||||
if (str_contains($file, 'storage/rsx-tmp/') || str_contains($file, 'storage/rsx-build/')) {
|
// CDN files are third-party production code - don't transform them
|
||||||
|
if (str_contains($file, 'storage/rsx-tmp/') ||
|
||||||
|
str_contains($file, 'storage/rsx-build/') ||
|
||||||
|
str_contains($file, '.cdn-cache/')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2276,11 +2388,6 @@ implode("\n", array_map(fn ($f) => ' - ' . str_replace(base_path() . '/', '',
|
|||||||
}
|
}
|
||||||
@unlink($output_file);
|
@unlink($output_file);
|
||||||
|
|
||||||
// Minify in production (TODO: preserve source maps when minifying)
|
|
||||||
if ($this->is_production) {
|
|
||||||
// TODO: Add minification that preserves source maps
|
|
||||||
}
|
|
||||||
|
|
||||||
return $js;
|
return $js;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2332,12 +2439,103 @@ implode("\n", array_map(fn ($f) => ' - ' . str_replace(base_path() . '/', '',
|
|||||||
// Clean up temp file
|
// Clean up temp file
|
||||||
@unlink($output_file);
|
@unlink($output_file);
|
||||||
|
|
||||||
// Minify in production
|
return $css;
|
||||||
if ($this->is_production) {
|
|
||||||
// TODO: Add minification that preserves source maps
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $css;
|
/**
|
||||||
|
* Prepare CDN assets for rendering
|
||||||
|
*
|
||||||
|
* In development mode: returns assets as-is (loaded from CDN URLs)
|
||||||
|
* In production-like modes: ensures assets are cached and adds cached_filename
|
||||||
|
* so rendering can use /_vendor/{filename} URLs
|
||||||
|
*
|
||||||
|
* @param array $assets CDN assets array
|
||||||
|
* @param string $type 'js' or 'css'
|
||||||
|
* @return array Prepared assets with cached_filename in production modes
|
||||||
|
*/
|
||||||
|
protected function _prepare_cdn_assets(array $assets, string $type): array
|
||||||
|
{
|
||||||
|
// In development mode, return as-is (use CDN URLs directly)
|
||||||
|
if (!$this->is_production) {
|
||||||
|
return $assets;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In production-like modes, ensure cached and add filename
|
||||||
|
$prepared = [];
|
||||||
|
foreach ($assets as $asset) {
|
||||||
|
$url = $asset['url'] ?? '';
|
||||||
|
if (empty($url)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the asset is cached (downloads if not already)
|
||||||
|
Cdn_Cache::get($url, $type);
|
||||||
|
|
||||||
|
// Add cached filename for /_vendor/ URL generation
|
||||||
|
$asset['cached_filename'] = Cdn_Cache::get_cache_filename($url, $type);
|
||||||
|
$prepared[] = $asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $prepared;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get local file paths for cached CDN assets (DEPRECATED)
|
||||||
|
*
|
||||||
|
* Used in production-like modes to include CDN assets in concat scripts
|
||||||
|
* for proper sourcemap handling.
|
||||||
|
*
|
||||||
|
* @param string $type 'js' or 'css'
|
||||||
|
* @return array Array of local file paths to cached CDN files
|
||||||
|
* @deprecated CDN assets are now served via /_vendor/ URLs, not merged into bundles
|
||||||
|
*/
|
||||||
|
protected function _get_cdn_cache_file_paths(string $type): array
|
||||||
|
{
|
||||||
|
$file_paths = [];
|
||||||
|
$assets = $this->cdn_assets[$type] ?? [];
|
||||||
|
|
||||||
|
if (empty($assets)) {
|
||||||
|
return $file_paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort assets: jQuery first, then others alphabetically
|
||||||
|
$jquery_assets = [];
|
||||||
|
$other_assets = [];
|
||||||
|
|
||||||
|
foreach ($assets as $asset) {
|
||||||
|
$url = $asset['url'] ?? '';
|
||||||
|
if (stripos($url, 'jquery') !== false) {
|
||||||
|
$jquery_assets[] = $asset;
|
||||||
|
} else {
|
||||||
|
$other_assets[] = $asset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
usort($other_assets, function ($a, $b) {
|
||||||
|
return strcmp($a['url'] ?? '', $b['url'] ?? '');
|
||||||
|
});
|
||||||
|
|
||||||
|
$sorted_assets = array_merge($jquery_assets, $other_assets);
|
||||||
|
|
||||||
|
// Get cache file path for each asset (downloads if not cached)
|
||||||
|
// If download fails, let it throw - CDN assets are required
|
||||||
|
foreach ($sorted_assets as $asset) {
|
||||||
|
$url = $asset['url'] ?? '';
|
||||||
|
if (empty($url)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will download and cache if not already cached
|
||||||
|
Cdn_Cache::get($url, $type);
|
||||||
|
|
||||||
|
// Get the cache file path
|
||||||
|
$cache_path = Cdn_Cache::get_cache_path($url, $type);
|
||||||
|
if (file_exists($cache_path)) {
|
||||||
|
$file_paths[] = $cache_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $file_paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
291
app/RSpade/Core/Bundle/Cdn_Cache.php
Executable file
291
app/RSpade/Core/Bundle/Cdn_Cache.php
Executable file
@@ -0,0 +1,291 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\RSpade\Core\Bundle;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CDN Asset Cache
|
||||||
|
*
|
||||||
|
* Downloads and caches CDN assets locally for inclusion in bundles.
|
||||||
|
* Cache is stored in storage/rsx-build/cdn-cache/ and committed to git
|
||||||
|
* so builds don't depend on CDN availability.
|
||||||
|
*/
|
||||||
|
class Cdn_Cache
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Cache directory path
|
||||||
|
*
|
||||||
|
* Located in rsx/resource/.cdn-cache/ so it:
|
||||||
|
* - Gets committed to git (not in storage/)
|
||||||
|
* - Survives rsx:clean and mode switches
|
||||||
|
*/
|
||||||
|
private static function _get_cache_dir(): string
|
||||||
|
{
|
||||||
|
return base_path('rsx/resource/.cdn-cache');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cached content for a CDN URL
|
||||||
|
*
|
||||||
|
* Downloads and caches if not already cached.
|
||||||
|
*
|
||||||
|
* @param string $url The CDN URL
|
||||||
|
* @param string $type 'js' or 'css'
|
||||||
|
* @return string The file content
|
||||||
|
*/
|
||||||
|
public static function get(string $url, string $type): string
|
||||||
|
{
|
||||||
|
$cache_path = self::_get_cache_path($url, $type);
|
||||||
|
|
||||||
|
// Return cached content if exists
|
||||||
|
if (file_exists($cache_path)) {
|
||||||
|
return file_get_contents($cache_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download and cache
|
||||||
|
return self::_download_and_cache($url, $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the local cache path for a URL
|
||||||
|
*/
|
||||||
|
public static function get_cache_path(string $url, string $type): string
|
||||||
|
{
|
||||||
|
return self::_get_cache_path($url, $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get just the cache filename (without directory) for a URL
|
||||||
|
*/
|
||||||
|
public static function get_cache_filename(string $url, string $type): string
|
||||||
|
{
|
||||||
|
return basename(self::_get_cache_path($url, $type));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cache directory path (public for route handler)
|
||||||
|
*/
|
||||||
|
public static function get_cache_directory(): string
|
||||||
|
{
|
||||||
|
return self::_get_cache_dir();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a URL is cached
|
||||||
|
*/
|
||||||
|
public static function is_cached(string $url, string $type): bool
|
||||||
|
{
|
||||||
|
return file_exists(self::_get_cache_path($url, $type));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache path for a URL
|
||||||
|
*
|
||||||
|
* Uses URL hash for filename to handle any URL structure.
|
||||||
|
*/
|
||||||
|
private static function _get_cache_path(string $url, string $type): string
|
||||||
|
{
|
||||||
|
$cache_dir = self::_get_cache_dir();
|
||||||
|
|
||||||
|
// Create hash-based filename
|
||||||
|
// Include enough of the original name for human readability
|
||||||
|
$url_parts = parse_url($url);
|
||||||
|
$path = $url_parts['path'] ?? '';
|
||||||
|
$basename = pathinfo($path, PATHINFO_FILENAME);
|
||||||
|
|
||||||
|
// Clean basename for filesystem
|
||||||
|
$clean_basename = preg_replace('/[^a-zA-Z0-9_-]/', '_', $basename);
|
||||||
|
$clean_basename = substr($clean_basename, 0, 50); // Limit length
|
||||||
|
|
||||||
|
// Hash for uniqueness
|
||||||
|
$hash = substr(md5($url), 0, 12);
|
||||||
|
|
||||||
|
return "{$cache_dir}/{$clean_basename}_{$hash}.{$type}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download a CDN asset and cache it
|
||||||
|
*/
|
||||||
|
private static function _download_and_cache(string $url, string $type): string
|
||||||
|
{
|
||||||
|
$cache_dir = self::_get_cache_dir();
|
||||||
|
|
||||||
|
// Ensure cache directory exists
|
||||||
|
if (!is_dir($cache_dir)) {
|
||||||
|
mkdir($cache_dir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download content
|
||||||
|
$content = self::_download($url);
|
||||||
|
|
||||||
|
if ($content === false || $content === '') {
|
||||||
|
throw new RuntimeException("Failed to download CDN asset: {$url}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// For CSS files, inline all url() references (fonts, images, etc.)
|
||||||
|
if ($type === 'css') {
|
||||||
|
$content = self::_inline_css_urls($content, $url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add source comment header
|
||||||
|
$header = "/* CDN Source: {$url} */\n";
|
||||||
|
$content = $header . $content;
|
||||||
|
|
||||||
|
// Ensure content ends with newline for clean concatenation
|
||||||
|
if (!str_ends_with($content, "\n")) {
|
||||||
|
$content .= "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to cache
|
||||||
|
$cache_path = self::_get_cache_path($url, $type);
|
||||||
|
file_put_contents($cache_path, $content);
|
||||||
|
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inline CSS url() references as base64 data URIs
|
||||||
|
*
|
||||||
|
* Uses Node.js postcss-url to properly parse CSS and resolve/inline
|
||||||
|
* all url() references (fonts, images, etc.) from the CDN.
|
||||||
|
*
|
||||||
|
* @param string $css_content The raw CSS content
|
||||||
|
* @param string $base_url The CDN URL the CSS was downloaded from
|
||||||
|
* @return string CSS with all url() references inlined as data URIs
|
||||||
|
* @throws RuntimeException if any referenced asset fails to download
|
||||||
|
*/
|
||||||
|
private static function _inline_css_urls(string $css_content, string $base_url): string
|
||||||
|
{
|
||||||
|
$cache_dir = self::_get_cache_dir();
|
||||||
|
|
||||||
|
// Write CSS to temp file for processing
|
||||||
|
$temp_input = $cache_dir . '/temp_css_input_' . md5($base_url) . '.css';
|
||||||
|
$temp_output = $cache_dir . '/temp_css_output_' . md5($base_url) . '.css';
|
||||||
|
|
||||||
|
file_put_contents($temp_input, $css_content);
|
||||||
|
|
||||||
|
// Run the inline-css-urls.js script
|
||||||
|
$script = base_path('app/RSpade/Core/Bundle/resource/inline-css-urls.js');
|
||||||
|
|
||||||
|
$cmd = sprintf(
|
||||||
|
'node %s %s %s %s 2>&1',
|
||||||
|
escapeshellarg($script),
|
||||||
|
escapeshellarg($base_url),
|
||||||
|
escapeshellarg($temp_input),
|
||||||
|
escapeshellarg($temp_output)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Use shell_exec with exit code capture
|
||||||
|
$full_cmd = "({$cmd}); echo \$?";
|
||||||
|
$result = shell_exec($full_cmd);
|
||||||
|
|
||||||
|
// Clean up temp input
|
||||||
|
@unlink($temp_input);
|
||||||
|
|
||||||
|
// Parse exit code from last line
|
||||||
|
$lines = explode("\n", trim($result ?? ''));
|
||||||
|
$exit_code = (int) array_pop($lines);
|
||||||
|
$output = implode("\n", $lines);
|
||||||
|
|
||||||
|
if ($exit_code !== 0) {
|
||||||
|
// Clean up temp output if it exists
|
||||||
|
@unlink($temp_output);
|
||||||
|
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Failed to inline CSS URLs for {$base_url}:\n{$output}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read processed CSS
|
||||||
|
if (!file_exists($temp_output)) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"CSS URL inlining did not produce output for {$base_url}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = file_get_contents($temp_output);
|
||||||
|
|
||||||
|
// Clean up temp output
|
||||||
|
@unlink($temp_output);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download URL content using curl
|
||||||
|
*/
|
||||||
|
private static function _download(string $url): string|false
|
||||||
|
{
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_FOLLOWLOCATION => true,
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
CURLOPT_USERAGENT => 'RSpade/1.0',
|
||||||
|
CURLOPT_SSL_VERIFYPEER => true,
|
||||||
|
]);
|
||||||
|
$content = curl_exec($ch);
|
||||||
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($http_code !== 200 || $content === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the CDN cache
|
||||||
|
*/
|
||||||
|
public static function clear(): void
|
||||||
|
{
|
||||||
|
$cache_dir = self::_get_cache_dir();
|
||||||
|
|
||||||
|
if (!is_dir($cache_dir)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = glob("{$cache_dir}/*.js") + glob("{$cache_dir}/*.css");
|
||||||
|
foreach ($files as $file) {
|
||||||
|
unlink($file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all cached files
|
||||||
|
*
|
||||||
|
* @return array Array of ['path' => string, 'url' => string, 'type' => string]
|
||||||
|
*/
|
||||||
|
public static function get_cached_files(): array
|
||||||
|
{
|
||||||
|
$cache_dir = self::_get_cache_dir();
|
||||||
|
|
||||||
|
if (!is_dir($cache_dir)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = [];
|
||||||
|
foreach (glob("{$cache_dir}/*") as $file) {
|
||||||
|
if (is_file($file)) {
|
||||||
|
$ext = pathinfo($file, PATHINFO_EXTENSION);
|
||||||
|
if (in_array($ext, ['js', 'css'])) {
|
||||||
|
// Try to extract URL from file header
|
||||||
|
$content = file_get_contents($file, false, null, 0, 500);
|
||||||
|
$url = '';
|
||||||
|
if (preg_match('/CDN Source: (.+?) \*/', $content, $matches)) {
|
||||||
|
$url = $matches[1];
|
||||||
|
}
|
||||||
|
$files[] = [
|
||||||
|
'path' => $file,
|
||||||
|
'url' => $url,
|
||||||
|
'type' => $ext,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
}
|
||||||
306
app/RSpade/Core/Bundle/Minifier.php
Executable file
306
app/RSpade/Core/Bundle/Minifier.php
Executable file
@@ -0,0 +1,306 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* JavaScript and CSS Minifier
|
||||||
|
*
|
||||||
|
* Uses a persistent RPC server for efficient minification of multiple files.
|
||||||
|
* Terser for JavaScript, cssnano for CSS.
|
||||||
|
* Only used in production mode - debug mode retains readable code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\RSpade\Core\Bundle;
|
||||||
|
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class Minifier
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* RPC server script path
|
||||||
|
*/
|
||||||
|
protected const RPC_SERVER_SCRIPT = 'app/RSpade/Core/Bundle/resource/minify-server.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RPC server socket path
|
||||||
|
*/
|
||||||
|
protected const RPC_SOCKET = 'storage/rsx-tmp/minify-server.sock';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RPC server process
|
||||||
|
*/
|
||||||
|
protected static $rpc_server_process = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RPC request ID counter
|
||||||
|
*/
|
||||||
|
protected static int $request_id = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether server has been started
|
||||||
|
*/
|
||||||
|
protected static bool $server_started = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minify JavaScript content
|
||||||
|
*
|
||||||
|
* @param string $content JavaScript content to minify
|
||||||
|
* @param string $filename Filename for error reporting
|
||||||
|
* @return string Minified content
|
||||||
|
*/
|
||||||
|
public static function minify_js(string $content, string $filename = 'bundle.js'): string
|
||||||
|
{
|
||||||
|
return static::_minify_via_rpc($content, 'js', $filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minify CSS content
|
||||||
|
*
|
||||||
|
* @param string $content CSS content to minify
|
||||||
|
* @param string $filename Filename for error reporting
|
||||||
|
* @return string Minified content
|
||||||
|
*/
|
||||||
|
public static function minify_css(string $content, string $filename = 'bundle.css'): string
|
||||||
|
{
|
||||||
|
return static::_minify_via_rpc($content, 'css', $filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minify content via RPC server
|
||||||
|
*/
|
||||||
|
protected static function _minify_via_rpc(string $content, string $type, string $filename): string
|
||||||
|
{
|
||||||
|
// Ensure server is running
|
||||||
|
if (!static::$server_started) {
|
||||||
|
static::start_rpc_server();
|
||||||
|
}
|
||||||
|
|
||||||
|
$socket_path = base_path(self::RPC_SOCKET);
|
||||||
|
|
||||||
|
$socket = @stream_socket_client('unix://' . $socket_path, $errno, $errstr, 30);
|
||||||
|
if (!$socket) {
|
||||||
|
throw new RuntimeException("Failed to connect to minify RPC server: {$errstr}");
|
||||||
|
}
|
||||||
|
|
||||||
|
stream_set_blocking($socket, true);
|
||||||
|
|
||||||
|
static::$request_id++;
|
||||||
|
$request = json_encode([
|
||||||
|
'id' => static::$request_id,
|
||||||
|
'method' => 'minify',
|
||||||
|
'files' => [
|
||||||
|
[
|
||||||
|
'type' => $type,
|
||||||
|
'content' => $content,
|
||||||
|
'filename' => $filename
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]) . "\n";
|
||||||
|
|
||||||
|
fwrite($socket, $request);
|
||||||
|
|
||||||
|
$response = fgets($socket);
|
||||||
|
fclose($socket);
|
||||||
|
|
||||||
|
if (!$response) {
|
||||||
|
throw new RuntimeException("No response from minify RPC server");
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = json_decode($response, true);
|
||||||
|
|
||||||
|
if (!isset($result['results'][$filename])) {
|
||||||
|
throw new RuntimeException("Invalid response from minify RPC server");
|
||||||
|
}
|
||||||
|
|
||||||
|
$file_result = $result['results'][$filename];
|
||||||
|
|
||||||
|
if ($file_result['status'] === 'error') {
|
||||||
|
$error = $file_result['error'];
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Minification failed for {$filename}: {$error['message']}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $file_result['result'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force restart the RPC server
|
||||||
|
*
|
||||||
|
* Call this before production builds to ensure code changes take effect.
|
||||||
|
*/
|
||||||
|
public static function force_restart(): void
|
||||||
|
{
|
||||||
|
static::stop_rpc_server(force: true);
|
||||||
|
static::$server_started = false;
|
||||||
|
static::start_rpc_server();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start RPC server for minification
|
||||||
|
*/
|
||||||
|
public static function start_rpc_server(): void
|
||||||
|
{
|
||||||
|
$socket_path = base_path(self::RPC_SOCKET);
|
||||||
|
$server_script = base_path(self::RPC_SERVER_SCRIPT);
|
||||||
|
|
||||||
|
if (!file_exists($server_script)) {
|
||||||
|
throw new RuntimeException("Minify RPC server script not found at {$server_script}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If socket exists, check if it's stale
|
||||||
|
if (file_exists($socket_path)) {
|
||||||
|
if (static::ping_rpc_server()) {
|
||||||
|
static::$server_started = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
static::stop_rpc_server(force: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure socket directory exists
|
||||||
|
$socket_dir = dirname($socket_path);
|
||||||
|
if (!is_dir($socket_dir)) {
|
||||||
|
mkdir($socket_dir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start RPC server
|
||||||
|
$process = new Process([
|
||||||
|
'node',
|
||||||
|
$server_script,
|
||||||
|
'--socket=' . $socket_path
|
||||||
|
]);
|
||||||
|
|
||||||
|
$process->setWorkingDirectory(base_path());
|
||||||
|
$process->setTimeout(null);
|
||||||
|
$process->start();
|
||||||
|
|
||||||
|
static::$rpc_server_process = $process;
|
||||||
|
|
||||||
|
// Wait for server to be ready
|
||||||
|
$max_wait_ms = 10000;
|
||||||
|
$wait_interval_ms = 50;
|
||||||
|
$iterations = $max_wait_ms / $wait_interval_ms;
|
||||||
|
|
||||||
|
for ($i = 0; $i < $iterations; $i++) {
|
||||||
|
usleep($wait_interval_ms * 1000);
|
||||||
|
|
||||||
|
if (static::ping_rpc_server()) {
|
||||||
|
static::$server_started = true;
|
||||||
|
register_shutdown_function([self::class, 'stop_rpc_server']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Minify RPC server failed to start within {$max_wait_ms}ms.\n" .
|
||||||
|
"Check that Node.js, Terser, and cssnano are installed."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ping the RPC server
|
||||||
|
*/
|
||||||
|
public static function ping_rpc_server(): bool
|
||||||
|
{
|
||||||
|
$socket_path = base_path(self::RPC_SOCKET);
|
||||||
|
|
||||||
|
if (!file_exists($socket_path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$socket = @stream_socket_client('unix://' . $socket_path, $errno, $errstr, 1);
|
||||||
|
if (!$socket) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream_set_blocking($socket, true);
|
||||||
|
|
||||||
|
static::$request_id++;
|
||||||
|
$request = json_encode([
|
||||||
|
'id' => static::$request_id,
|
||||||
|
'method' => 'ping'
|
||||||
|
]) . "\n";
|
||||||
|
|
||||||
|
fwrite($socket, $request);
|
||||||
|
$response = fgets($socket);
|
||||||
|
fclose($socket);
|
||||||
|
|
||||||
|
if (!$response) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = json_decode($response, true);
|
||||||
|
return isset($result['result']) && $result['result'] === 'pong';
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the RPC server
|
||||||
|
*/
|
||||||
|
public static function stop_rpc_server(bool $force = false): void
|
||||||
|
{
|
||||||
|
$socket_path = base_path(self::RPC_SOCKET);
|
||||||
|
|
||||||
|
if ($force) {
|
||||||
|
if (file_exists($socket_path)) {
|
||||||
|
try {
|
||||||
|
$socket = @stream_socket_client('unix://' . $socket_path, $errno, $errstr, 1);
|
||||||
|
if ($socket) {
|
||||||
|
stream_set_blocking($socket, true);
|
||||||
|
|
||||||
|
static::$request_id++;
|
||||||
|
$request = json_encode([
|
||||||
|
'id' => static::$request_id,
|
||||||
|
'method' => 'shutdown'
|
||||||
|
]) . "\n";
|
||||||
|
|
||||||
|
fwrite($socket, $request);
|
||||||
|
fclose($socket);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Ignore errors during force shutdown
|
||||||
|
}
|
||||||
|
|
||||||
|
usleep(100000); // 100ms
|
||||||
|
|
||||||
|
if (file_exists($socket_path)) {
|
||||||
|
@unlink($socket_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (static::$rpc_server_process) {
|
||||||
|
if (static::$rpc_server_process->isRunning()) {
|
||||||
|
static::$rpc_server_process->stop(1, SIGTERM);
|
||||||
|
}
|
||||||
|
static::$rpc_server_process = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static::$server_started = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Graceful shutdown
|
||||||
|
if (file_exists($socket_path)) {
|
||||||
|
try {
|
||||||
|
$socket = @stream_socket_client('unix://' . $socket_path, $errno, $errstr, 1);
|
||||||
|
if ($socket) {
|
||||||
|
stream_set_blocking($socket, true);
|
||||||
|
|
||||||
|
static::$request_id++;
|
||||||
|
$request = json_encode([
|
||||||
|
'id' => static::$request_id,
|
||||||
|
'method' => 'shutdown'
|
||||||
|
]) . "\n";
|
||||||
|
|
||||||
|
fwrite($socket, $request);
|
||||||
|
fclose($socket);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Ignore errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static::$server_started = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ use RuntimeException;
|
|||||||
use App\RSpade\CodeQuality\RuntimeChecks\BundleErrors;
|
use App\RSpade\CodeQuality\RuntimeChecks\BundleErrors;
|
||||||
use App\RSpade\Core\Bundle\BundleCompiler;
|
use App\RSpade\Core\Bundle\BundleCompiler;
|
||||||
use App\RSpade\Core\Manifest\Manifest;
|
use App\RSpade\Core\Manifest\Manifest;
|
||||||
|
use App\RSpade\Core\Mode\Rsx_Mode;
|
||||||
use App\RSpade\Core\Session\Session;
|
use App\RSpade\Core\Session\Session;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,7 +128,7 @@ abstract class Rsx_Bundle_Abstract
|
|||||||
\App\RSpade\Core\Debug\Debugger::dump_console_debug_messages_to_html();
|
\App\RSpade\Core\Debug\Debugger::dump_console_debug_messages_to_html();
|
||||||
|
|
||||||
// In development mode, validate path coverage
|
// In development mode, validate path coverage
|
||||||
if (!app()->environment('production')) {
|
if (Rsx_Mode::is_development()) {
|
||||||
static::__validate_path_coverage($bundle_class);
|
static::__validate_path_coverage($bundle_class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,24 +228,14 @@ abstract class Rsx_Bundle_Abstract
|
|||||||
throw new RuntimeException('BundleCompiler returned non-array: ' . gettype($compiled));
|
throw new RuntimeException('BundleCompiler returned non-array: ' . gettype($compiled));
|
||||||
}
|
}
|
||||||
|
|
||||||
// In development, we should have vendor/app split files
|
// Expect vendor/app split files (merging not yet implemented)
|
||||||
// In production, we should have combined files
|
// Future: check Rsx_Mode::should_merge_bundles() for combined files
|
||||||
$is_production = app()->environment('production');
|
|
||||||
|
|
||||||
if (!$is_production) {
|
|
||||||
// Development mode - expect vendor/app bundle paths
|
|
||||||
if (!isset($compiled['vendor_js_bundle_path']) &&
|
if (!isset($compiled['vendor_js_bundle_path']) &&
|
||||||
!isset($compiled['app_js_bundle_path']) &&
|
!isset($compiled['app_js_bundle_path']) &&
|
||||||
!isset($compiled['vendor_css_bundle_path']) &&
|
!isset($compiled['vendor_css_bundle_path']) &&
|
||||||
!isset($compiled['app_css_bundle_path'])) {
|
!isset($compiled['app_css_bundle_path'])) {
|
||||||
throw new RuntimeException('BundleCompiler missing expected vendor/app bundle paths. Got keys: ' . implode(', ', array_keys($compiled)));
|
throw new RuntimeException('BundleCompiler missing expected vendor/app bundle paths. Got keys: ' . implode(', ', array_keys($compiled)));
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Production mode - expect combined bundle paths
|
|
||||||
if (!isset($compiled['js_bundle_path']) && !isset($compiled['css_bundle_path'])) {
|
|
||||||
throw new RuntimeException('BundleCompiler missing expected js/css bundle paths. Got keys: ' . implode(', ', array_keys($compiled)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$html = [];
|
$html = [];
|
||||||
$manifest_hash = Manifest::get_build_key();
|
$manifest_hash = Manifest::get_build_key();
|
||||||
@@ -269,12 +260,16 @@ abstract class Rsx_Bundle_Abstract
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add runtime data
|
// Add runtime data
|
||||||
$rsxapp_data['debug'] = !app()->environment('production');
|
$rsxapp_data['debug'] = Rsx_Mode::is_development();
|
||||||
$rsxapp_data['current_controller'] = \App\RSpade\Core\Rsx::get_current_controller();
|
$rsxapp_data['current_controller'] = \App\RSpade\Core\Rsx::get_current_controller();
|
||||||
$rsxapp_data['current_action'] = \App\RSpade\Core\Rsx::get_current_action();
|
$rsxapp_data['current_action'] = \App\RSpade\Core\Rsx::get_current_action();
|
||||||
$rsxapp_data['is_auth'] = Session::is_logged_in();
|
$rsxapp_data['is_auth'] = Session::is_logged_in();
|
||||||
$rsxapp_data['is_spa'] = \App\RSpade\Core\Rsx::is_spa();
|
$rsxapp_data['is_spa'] = \App\RSpade\Core\Rsx::is_spa();
|
||||||
|
|
||||||
|
// Only include ajax_disable_batching in development mode
|
||||||
|
if (Rsx_Mode::is_development()) {
|
||||||
$rsxapp_data['ajax_disable_batching'] = config('rsx.development.ajax_disable_batching', false);
|
$rsxapp_data['ajax_disable_batching'] = config('rsx.development.ajax_disable_batching', false);
|
||||||
|
}
|
||||||
|
|
||||||
// Add current params (always set to reduce state variations)
|
// Add current params (always set to reduce state variations)
|
||||||
$current_params = \App\RSpade\Core\Rsx::get_current_params();
|
$current_params = \App\RSpade\Core\Rsx::get_current_params();
|
||||||
@@ -305,8 +300,8 @@ abstract class Rsx_Bundle_Abstract
|
|||||||
$rsxapp_data['server_time'] = \App\RSpade\Core\Time\Rsx_Time::now_iso();
|
$rsxapp_data['server_time'] = \App\RSpade\Core\Time\Rsx_Time::now_iso();
|
||||||
$rsxapp_data['user_timezone'] = \App\RSpade\Core\Time\Rsx_Time::get_user_timezone();
|
$rsxapp_data['user_timezone'] = \App\RSpade\Core\Time\Rsx_Time::get_user_timezone();
|
||||||
|
|
||||||
// Add console_debug config in non-production mode
|
// Add console_debug config only in development mode
|
||||||
if (!app()->environment('production')) {
|
if (Rsx_Mode::should_include_debug_info()) {
|
||||||
$console_debug_config = config('rsx.console_debug', []);
|
$console_debug_config = config('rsx.console_debug', []);
|
||||||
|
|
||||||
// Build console_debug settings
|
// Build console_debug settings
|
||||||
@@ -371,10 +366,8 @@ abstract class Rsx_Bundle_Abstract
|
|||||||
// Filter out keys starting with single underscore (but allow double underscore like __MODEL)
|
// Filter out keys starting with single underscore (but allow double underscore like __MODEL)
|
||||||
$rsxapp_data = static::__filter_underscore_keys($rsxapp_data);
|
$rsxapp_data = static::__filter_underscore_keys($rsxapp_data);
|
||||||
|
|
||||||
// Pretty print JSON in non-production environments
|
// Always pretty print rsxapp for debuggability in browser dev tools
|
||||||
$rsxapp_json = app()->environment('production')
|
$rsxapp_json = json_encode($rsxapp_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||||
? json_encode($rsxapp_data, JSON_UNESCAPED_SLASHES)
|
|
||||||
: json_encode($rsxapp_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
|
||||||
|
|
||||||
$html[] = '<script>window.rsxapp = ' . $rsxapp_json . ';</script>';
|
$html[] = '<script>window.rsxapp = ' . $rsxapp_json . ';</script>';
|
||||||
|
|
||||||
@@ -413,6 +406,12 @@ abstract class Rsx_Bundle_Abstract
|
|||||||
|
|
||||||
// Add CSS: jQuery first, then others
|
// Add CSS: jQuery first, then others
|
||||||
foreach (array_merge($jquery_css, $other_css) as $asset) {
|
foreach (array_merge($jquery_css, $other_css) as $asset) {
|
||||||
|
// In production modes, use /_vendor/{filename} URL for cached assets
|
||||||
|
if (!empty($asset['cached_filename'])) {
|
||||||
|
$url = '/_vendor/' . $asset['cached_filename'];
|
||||||
|
$html[] = '<link rel="stylesheet" href="' . htmlspecialchars($url) . '">';
|
||||||
|
} else {
|
||||||
|
// Development mode: use CDN URL directly
|
||||||
$tag = '<link rel="stylesheet" href="' . htmlspecialchars($asset['url']) . '"';
|
$tag = '<link rel="stylesheet" href="' . htmlspecialchars($asset['url']) . '"';
|
||||||
if (!empty($asset['integrity'])) {
|
if (!empty($asset['integrity'])) {
|
||||||
$tag .= ' integrity="' . htmlspecialchars($asset['integrity']) . '"';
|
$tag .= ' integrity="' . htmlspecialchars($asset['integrity']) . '"';
|
||||||
@@ -421,6 +420,7 @@ abstract class Rsx_Bundle_Abstract
|
|||||||
$tag .= '>';
|
$tag .= '>';
|
||||||
$html[] = $tag;
|
$html[] = $tag;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add public directory CSS (with filemtime cache-busting)
|
// Add public directory CSS (with filemtime cache-busting)
|
||||||
$public_css = $compiled['public_css'] ?? [];
|
$public_css = $compiled['public_css'] ?? [];
|
||||||
@@ -431,6 +431,12 @@ abstract class Rsx_Bundle_Abstract
|
|||||||
|
|
||||||
// Add JS: jQuery first, then others
|
// Add JS: jQuery first, then others
|
||||||
foreach (array_merge($jquery_js, $other_js) as $asset) {
|
foreach (array_merge($jquery_js, $other_js) as $asset) {
|
||||||
|
// In production modes, use /_vendor/{filename} URL for cached assets
|
||||||
|
if (!empty($asset['cached_filename'])) {
|
||||||
|
$url = '/_vendor/' . $asset['cached_filename'];
|
||||||
|
$html[] = '<script src="' . htmlspecialchars($url) . '" defer></script>';
|
||||||
|
} else {
|
||||||
|
// Development mode: use CDN URL directly
|
||||||
$tag = '<script src="' . htmlspecialchars($asset['url']) . '" defer';
|
$tag = '<script src="' . htmlspecialchars($asset['url']) . '" defer';
|
||||||
if (!empty($asset['integrity'])) {
|
if (!empty($asset['integrity'])) {
|
||||||
$tag .= ' integrity="' . htmlspecialchars($asset['integrity']) . '"';
|
$tag .= ' integrity="' . htmlspecialchars($asset['integrity']) . '"';
|
||||||
@@ -439,6 +445,7 @@ abstract class Rsx_Bundle_Abstract
|
|||||||
$tag .= '></script>';
|
$tag .= '></script>';
|
||||||
$html[] = $tag;
|
$html[] = $tag;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add public directory JS (with filemtime cache-busting and defer)
|
// Add public directory JS (with filemtime cache-busting and defer)
|
||||||
$public_js = $compiled['public_js'] ?? [];
|
$public_js = $compiled['public_js'] ?? [];
|
||||||
|
|||||||
215
app/RSpade/Core/Bundle/resource/inline-css-urls.js
Executable file
215
app/RSpade/Core/Bundle/resource/inline-css-urls.js
Executable file
@@ -0,0 +1,215 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSS URL Inliner
|
||||||
|
*
|
||||||
|
* Resolves relative url() references in CSS to absolute CDN URLs,
|
||||||
|
* downloads the assets, and inlines them as base64 data URIs.
|
||||||
|
*
|
||||||
|
* Usage: node inline-css-urls.js <base-url> <input-file> <output-file>
|
||||||
|
*
|
||||||
|
* - base-url: The CDN URL the CSS was downloaded from (for resolving relative paths)
|
||||||
|
* - input-file: Path to the downloaded CSS file
|
||||||
|
* - output-file: Path to write the processed CSS with inlined assets
|
||||||
|
*
|
||||||
|
* Exit codes:
|
||||||
|
* - 0: Success
|
||||||
|
* - 1: Error (failed to download asset, invalid arguments, etc.)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const https = require('https');
|
||||||
|
const http = require('http');
|
||||||
|
const postcss = require('postcss');
|
||||||
|
const postcssUrl = require('postcss-url');
|
||||||
|
|
||||||
|
// Parse arguments
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
if (args.length !== 3) {
|
||||||
|
console.error('Usage: node inline-css-urls.js <base-url> <input-file> <output-file>');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [baseUrl, inputFile, outputFile] = args;
|
||||||
|
|
||||||
|
// Track failed downloads to report all at once
|
||||||
|
const failedUrls = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get MIME type from file extension
|
||||||
|
*/
|
||||||
|
function getMimeType(url) {
|
||||||
|
const ext = path.extname(url).toLowerCase().split('?')[0]; // Remove query string
|
||||||
|
const mimeTypes = {
|
||||||
|
'.woff2': 'font/woff2',
|
||||||
|
'.woff': 'font/woff',
|
||||||
|
'.ttf': 'font/ttf',
|
||||||
|
'.otf': 'font/otf',
|
||||||
|
'.eot': 'application/vnd.ms-fontobject',
|
||||||
|
'.svg': 'image/svg+xml',
|
||||||
|
'.png': 'image/png',
|
||||||
|
'.jpg': 'image/jpeg',
|
||||||
|
'.jpeg': 'image/jpeg',
|
||||||
|
'.gif': 'image/gif',
|
||||||
|
'.webp': 'image/webp',
|
||||||
|
'.ico': 'image/x-icon',
|
||||||
|
};
|
||||||
|
return mimeTypes[ext] || 'application/octet-stream';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a relative URL against a base URL
|
||||||
|
*/
|
||||||
|
function resolveUrl(relativeUrl, baseUrl) {
|
||||||
|
// Already absolute
|
||||||
|
if (relativeUrl.startsWith('http://') || relativeUrl.startsWith('https://')) {
|
||||||
|
return relativeUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data URI - already inline
|
||||||
|
if (relativeUrl.startsWith('data:')) {
|
||||||
|
return null; // Skip, already inline
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse base URL
|
||||||
|
const base = new URL(baseUrl);
|
||||||
|
|
||||||
|
// Handle different relative path formats
|
||||||
|
if (relativeUrl.startsWith('//')) {
|
||||||
|
// Protocol-relative
|
||||||
|
return base.protocol + relativeUrl;
|
||||||
|
} else if (relativeUrl.startsWith('/')) {
|
||||||
|
// Absolute path
|
||||||
|
return base.origin + relativeUrl;
|
||||||
|
} else {
|
||||||
|
// Relative path - resolve against base directory
|
||||||
|
const baseDir = base.href.substring(0, base.href.lastIndexOf('/') + 1);
|
||||||
|
return new URL(relativeUrl, baseDir).href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download a URL and return as Buffer
|
||||||
|
*/
|
||||||
|
function downloadUrl(url) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const protocol = url.startsWith('https://') ? https : http;
|
||||||
|
|
||||||
|
const request = protocol.get(url, {
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'RSpade/1.0',
|
||||||
|
'Accept': '*/*',
|
||||||
|
},
|
||||||
|
timeout: 30000,
|
||||||
|
}, (response) => {
|
||||||
|
// Handle redirects
|
||||||
|
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
||||||
|
downloadUrl(response.headers.location).then(resolve).catch(reject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.statusCode !== 200) {
|
||||||
|
reject(new Error(`HTTP ${response.statusCode}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunks = [];
|
||||||
|
response.on('data', chunk => chunks.push(chunk));
|
||||||
|
response.on('end', () => resolve(Buffer.concat(chunks)));
|
||||||
|
response.on('error', reject);
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on('error', reject);
|
||||||
|
request.on('timeout', () => {
|
||||||
|
request.destroy();
|
||||||
|
reject(new Error('Request timeout'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process CSS and inline all url() references
|
||||||
|
*/
|
||||||
|
async function processCSS() {
|
||||||
|
// Read input CSS
|
||||||
|
if (!fs.existsSync(inputFile)) {
|
||||||
|
console.error(`Error: Input file not found: ${inputFile}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const css = fs.readFileSync(inputFile, 'utf-8');
|
||||||
|
|
||||||
|
// Cache for downloaded assets (avoid re-downloading same URL)
|
||||||
|
const assetCache = new Map();
|
||||||
|
|
||||||
|
// Process with PostCSS
|
||||||
|
const result = await postcss()
|
||||||
|
.use(postcssUrl({
|
||||||
|
url: async (asset) => {
|
||||||
|
const originalUrl = asset.url;
|
||||||
|
|
||||||
|
// Skip data URIs
|
||||||
|
if (originalUrl.startsWith('data:')) {
|
||||||
|
return originalUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve to absolute URL
|
||||||
|
const absoluteUrl = resolveUrl(originalUrl, baseUrl);
|
||||||
|
if (!absoluteUrl) {
|
||||||
|
return originalUrl; // Skip if couldn't resolve
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check cache
|
||||||
|
if (assetCache.has(absoluteUrl)) {
|
||||||
|
return assetCache.get(absoluteUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download and convert to data URI
|
||||||
|
try {
|
||||||
|
console.log(` Downloading: ${absoluteUrl}`);
|
||||||
|
const buffer = await downloadUrl(absoluteUrl);
|
||||||
|
const mimeType = getMimeType(absoluteUrl);
|
||||||
|
const base64 = buffer.toString('base64');
|
||||||
|
const dataUri = `data:${mimeType};base64,${base64}`;
|
||||||
|
|
||||||
|
// Cache for reuse
|
||||||
|
assetCache.set(absoluteUrl, dataUri);
|
||||||
|
|
||||||
|
console.log(` OK: ${buffer.length} bytes -> ${base64.length} chars base64`);
|
||||||
|
return dataUri;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(` FAILED: ${absoluteUrl} - ${err.message}`);
|
||||||
|
failedUrls.push({ url: absoluteUrl, error: err.message });
|
||||||
|
return originalUrl; // Keep original URL (will fail at runtime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.process(css, { from: inputFile, to: outputFile });
|
||||||
|
|
||||||
|
// Check for failures
|
||||||
|
if (failedUrls.length > 0) {
|
||||||
|
console.error('\nFATAL: Failed to download the following URLs:');
|
||||||
|
for (const { url, error } of failedUrls) {
|
||||||
|
console.error(` - ${url}: ${error}`);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write output
|
||||||
|
const outputDir = path.dirname(outputFile);
|
||||||
|
if (!fs.existsSync(outputDir)) {
|
||||||
|
fs.mkdirSync(outputDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(outputFile, result.css, 'utf-8');
|
||||||
|
|
||||||
|
console.log(`\nSuccess: Processed CSS written to ${outputFile}`);
|
||||||
|
console.log(` Assets inlined: ${assetCache.size}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run
|
||||||
|
processCSS().catch(err => {
|
||||||
|
console.error(`Error: ${err.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
298
app/RSpade/Core/Bundle/resource/minify-server.js
Executable file
298
app/RSpade/Core/Bundle/resource/minify-server.js
Executable file
@@ -0,0 +1,298 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minification RPC Server
|
||||||
|
*
|
||||||
|
* Handles JavaScript (Terser) and CSS (cssnano) minification for production builds.
|
||||||
|
* Runs as a persistent server to avoid Node.js startup overhead for multiple files.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* Server mode: node minify-server.js --socket=/path/to/socket
|
||||||
|
*
|
||||||
|
* RPC Methods:
|
||||||
|
* - ping: Health check
|
||||||
|
* - minify: Minify JS or CSS content
|
||||||
|
* - shutdown: Graceful server termination
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const net = require('net');
|
||||||
|
const { minify: terserMinify } = require('terser');
|
||||||
|
const postcss = require('postcss');
|
||||||
|
const cssnano = require('cssnano');
|
||||||
|
|
||||||
|
// Parse command line arguments
|
||||||
|
let socketPath = null;
|
||||||
|
|
||||||
|
for (let i = 2; i < process.argv.length; i++) {
|
||||||
|
const arg = process.argv[i];
|
||||||
|
if (arg.startsWith('--socket=')) {
|
||||||
|
socketPath = arg.substring('--socket='.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!socketPath) {
|
||||||
|
console.error('Usage: node minify-server.js --socket=/path/to/socket');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove socket if exists
|
||||||
|
if (fs.existsSync(socketPath)) {
|
||||||
|
fs.unlinkSync(socketPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minify JavaScript content using Terser
|
||||||
|
* Strips sourcemaps, preserves license comments, produces minimal output
|
||||||
|
*/
|
||||||
|
async function minifyJs(content, filename) {
|
||||||
|
try {
|
||||||
|
// Extract all license comments BEFORE minification
|
||||||
|
// These can be lost when Terser transforms/inlines IIFEs
|
||||||
|
const licenseComments = [];
|
||||||
|
const licenseRegex = /\/\*![\s\S]*?\*\//g;
|
||||||
|
let match;
|
||||||
|
while ((match = licenseRegex.exec(content)) !== null) {
|
||||||
|
const license = match[0];
|
||||||
|
// Only keep unique licenses (avoid duplicates)
|
||||||
|
if (!licenseComments.some(l => l === license)) {
|
||||||
|
licenseComments.push(license);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove existing sourcemap comments before minification
|
||||||
|
const cleanContent = content.replace(/\/\/[#@]\s*sourceMappingURL=.*/g, '');
|
||||||
|
|
||||||
|
const result = await terserMinify(cleanContent, {
|
||||||
|
compress: {
|
||||||
|
dead_code: true,
|
||||||
|
drop_console: false, // Keep console.log for now
|
||||||
|
drop_debugger: true,
|
||||||
|
passes: 2
|
||||||
|
},
|
||||||
|
mangle: {
|
||||||
|
reserved: ['$', 'jQuery', '_', 'Rsx', 'rsxapp'] // Don't mangle these
|
||||||
|
},
|
||||||
|
format: {
|
||||||
|
// Don't try to preserve comments - we'll prepend them manually
|
||||||
|
comments: false,
|
||||||
|
semicolons: true
|
||||||
|
},
|
||||||
|
sourceMap: false // No sourcemap in production
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.code === undefined) {
|
||||||
|
throw new Error('Terser produced no output');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepend extracted license comments to output
|
||||||
|
let finalCode = result.code;
|
||||||
|
if (licenseComments.length > 0) {
|
||||||
|
finalCode = licenseComments.join('\n') + '\n' + result.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 'success',
|
||||||
|
result: finalCode,
|
||||||
|
originalSize: content.length,
|
||||||
|
minifiedSize: finalCode.length
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
status: 'error',
|
||||||
|
error: {
|
||||||
|
type: error.constructor.name,
|
||||||
|
message: error.message,
|
||||||
|
file: filename
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minify CSS content using cssnano
|
||||||
|
* Strips sourcemaps, preserves license comments, produces minimal output
|
||||||
|
*/
|
||||||
|
async function minifyCss(content, filename) {
|
||||||
|
try {
|
||||||
|
// Extract all license comments BEFORE minification
|
||||||
|
const licenseComments = [];
|
||||||
|
const licenseRegex = /\/\*![\s\S]*?\*\//g;
|
||||||
|
let match;
|
||||||
|
while ((match = licenseRegex.exec(content)) !== null) {
|
||||||
|
const license = match[0];
|
||||||
|
if (!licenseComments.some(l => l === license)) {
|
||||||
|
licenseComments.push(license);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove existing sourcemap comments before minification
|
||||||
|
const cleanContent = content.replace(/\/\*[#@]\s*sourceMappingURL=.*?\*\//g, '');
|
||||||
|
|
||||||
|
const result = await postcss([
|
||||||
|
cssnano({
|
||||||
|
preset: ['default', {
|
||||||
|
// Strip all comments - we'll prepend licenses manually
|
||||||
|
discardComments: { removeAll: true },
|
||||||
|
normalizeWhitespace: true,
|
||||||
|
minifySelectors: true,
|
||||||
|
minifyParams: true,
|
||||||
|
minifyFontValues: true
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
]).process(cleanContent, {
|
||||||
|
from: filename,
|
||||||
|
map: false // No sourcemap in production
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prepend extracted license comments to output
|
||||||
|
let finalCss = result.css;
|
||||||
|
if (licenseComments.length > 0) {
|
||||||
|
finalCss = licenseComments.join('\n') + '\n' + result.css;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 'success',
|
||||||
|
result: finalCss,
|
||||||
|
originalSize: content.length,
|
||||||
|
minifiedSize: finalCss.length
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
status: 'error',
|
||||||
|
error: {
|
||||||
|
type: error.constructor.name,
|
||||||
|
message: error.message,
|
||||||
|
file: filename
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle incoming RPC requests
|
||||||
|
*/
|
||||||
|
async function handleRequest(data) {
|
||||||
|
try {
|
||||||
|
const request = JSON.parse(data);
|
||||||
|
|
||||||
|
switch (request.method) {
|
||||||
|
case 'ping':
|
||||||
|
return JSON.stringify({
|
||||||
|
id: request.id,
|
||||||
|
result: 'pong'
|
||||||
|
}) + '\n';
|
||||||
|
|
||||||
|
case 'minify':
|
||||||
|
const results = {};
|
||||||
|
|
||||||
|
for (const file of request.files) {
|
||||||
|
const type = file.type; // 'js' or 'css'
|
||||||
|
const content = file.content;
|
||||||
|
const filename = file.filename || 'unknown';
|
||||||
|
|
||||||
|
if (type === 'js') {
|
||||||
|
results[filename] = await minifyJs(content, filename);
|
||||||
|
} else if (type === 'css') {
|
||||||
|
results[filename] = await minifyCss(content, filename);
|
||||||
|
} else {
|
||||||
|
results[filename] = {
|
||||||
|
status: 'error',
|
||||||
|
error: {
|
||||||
|
type: 'InvalidType',
|
||||||
|
message: `Unknown file type: ${type}`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify({
|
||||||
|
id: request.id,
|
||||||
|
results: results
|
||||||
|
}) + '\n';
|
||||||
|
|
||||||
|
case 'shutdown':
|
||||||
|
return JSON.stringify({
|
||||||
|
id: request.id,
|
||||||
|
result: 'shutting down'
|
||||||
|
}) + '\n';
|
||||||
|
|
||||||
|
default:
|
||||||
|
return JSON.stringify({
|
||||||
|
id: request.id,
|
||||||
|
error: 'Unknown method: ' + request.method
|
||||||
|
}) + '\n';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return JSON.stringify({
|
||||||
|
error: 'Invalid JSON request: ' + error.message
|
||||||
|
}) + '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the server
|
||||||
|
const server = net.createServer((socket) => {
|
||||||
|
let buffer = '';
|
||||||
|
|
||||||
|
socket.on('data', async (data) => {
|
||||||
|
buffer += data.toString();
|
||||||
|
|
||||||
|
let newlineIndex;
|
||||||
|
while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
|
||||||
|
const line = buffer.substring(0, newlineIndex);
|
||||||
|
buffer = buffer.substring(newlineIndex + 1);
|
||||||
|
|
||||||
|
if (line.trim()) {
|
||||||
|
const response = await handleRequest(line);
|
||||||
|
socket.write(response);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const request = JSON.parse(line);
|
||||||
|
if (request.method === 'shutdown') {
|
||||||
|
socket.end();
|
||||||
|
server.close(() => {
|
||||||
|
if (fs.existsSync(socketPath)) {
|
||||||
|
fs.unlinkSync(socketPath);
|
||||||
|
}
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore parse errors for shutdown check
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('error', (err) => {
|
||||||
|
console.error('Socket error:', err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(socketPath, () => {
|
||||||
|
console.log('Minify RPC server listening on ' + socketPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on('error', (err) => {
|
||||||
|
console.error('Server error:', err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Graceful shutdown handlers
|
||||||
|
process.on('SIGTERM', () => {
|
||||||
|
server.close(() => {
|
||||||
|
if (fs.existsSync(socketPath)) {
|
||||||
|
fs.unlinkSync(socketPath);
|
||||||
|
}
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('SIGINT', () => {
|
||||||
|
server.close(() => {
|
||||||
|
if (fs.existsSync(socketPath)) {
|
||||||
|
fs.unlinkSync(socketPath);
|
||||||
|
}
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -145,6 +145,13 @@ class AssetHandler
|
|||||||
preg_match('/^[A-Za-z0-9_]+__app\.[a-f0-9]{16}\.(js|css)$/', $filename);
|
preg_match('/^[A-Za-z0-9_]+__app\.[a-f0-9]{16}\.(js|css)$/', $filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this is a vendor CDN cache request
|
||||||
|
if (str_starts_with($path, '/_vendor/')) {
|
||||||
|
// Validate filename format: alphanumeric_hash.(js|css)
|
||||||
|
$filename = substr($path, 9); // Remove '/_vendor/'
|
||||||
|
return preg_match('/^[A-Za-z0-9_-]+_[a-f0-9]{12}\.(js|css)$/', $filename);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if path has a file extension
|
// Check if path has a file extension
|
||||||
$extension = pathinfo($path, PATHINFO_EXTENSION);
|
$extension = pathinfo($path, PATHINFO_EXTENSION);
|
||||||
|
|
||||||
@@ -171,6 +178,11 @@ class AssetHandler
|
|||||||
return static::__serve_compiled_bundle($path, $request);
|
return static::__serve_compiled_bundle($path, $request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle vendor CDN cache requests
|
||||||
|
if (str_starts_with($path, '/_vendor/')) {
|
||||||
|
return static::__serve_vendor_cdn_cache($path, $request);
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure directories are discovered
|
// Ensure directories are discovered
|
||||||
static::__ensure_directories_discovered();
|
static::__ensure_directories_discovered();
|
||||||
|
|
||||||
@@ -681,6 +693,80 @@ class AssetHandler
|
|||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serve a cached CDN vendor file
|
||||||
|
*
|
||||||
|
* Serves files from the CDN cache directory (rsx/resource/.cdn-cache/)
|
||||||
|
* with strict filename validation to prevent path traversal.
|
||||||
|
*
|
||||||
|
* @param string $path The requested path (e.g., /_vendor/lodash_abc123456789.js)
|
||||||
|
* @param Request $request
|
||||||
|
* @return Response
|
||||||
|
* @throws NotFoundHttpException
|
||||||
|
*/
|
||||||
|
protected static function __serve_vendor_cdn_cache($path, Request $request)
|
||||||
|
{
|
||||||
|
// Extract filename from path
|
||||||
|
$filename = substr($path, 9); // Remove '/_vendor/'
|
||||||
|
|
||||||
|
// Strict filename validation - only allow safe characters
|
||||||
|
// Format: name_hash.ext where name is alphanumeric/underscore/hyphen, hash is 12 hex chars
|
||||||
|
if (!preg_match('/^[A-Za-z0-9_-]+_[a-f0-9]{12}\.(js|css)$/', $filename)) {
|
||||||
|
throw new NotFoundHttpException("Invalid vendor filename: {$filename}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional safety checks - no path traversal characters
|
||||||
|
if (str_contains($filename, '..') || str_contains($filename, './') || str_contains($filename, '/')) {
|
||||||
|
Log::warning('Attempted path traversal in vendor request', [
|
||||||
|
'filename' => $filename
|
||||||
|
]);
|
||||||
|
throw new NotFoundHttpException("Invalid vendor filename: {$filename}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the CDN cache directory from the Cdn_Cache class
|
||||||
|
$cache_dir = \App\RSpade\Core\Bundle\Cdn_Cache::get_cache_directory();
|
||||||
|
|
||||||
|
// Build full path - filename is already validated as safe
|
||||||
|
$file_path = $cache_dir . '/' . $filename;
|
||||||
|
|
||||||
|
// Check if file exists
|
||||||
|
if (!file_exists($file_path)) {
|
||||||
|
throw new NotFoundHttpException("Vendor file not found: {$filename}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the file is actually in the cache directory (belt and suspenders)
|
||||||
|
// @REALPATH-EXCEPTION - Security: path traversal prevention requires symlink resolution
|
||||||
|
$real_file = realpath($file_path);
|
||||||
|
$real_cache_dir = realpath($cache_dir);
|
||||||
|
|
||||||
|
if (!$real_file || !$real_cache_dir || !str_starts_with($real_file, $real_cache_dir)) {
|
||||||
|
Log::warning('Vendor file path traversal attempt', [
|
||||||
|
'filename' => $filename,
|
||||||
|
'resolved' => $real_file
|
||||||
|
]);
|
||||||
|
throw new NotFoundHttpException("Vendor file not found: {$filename}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create binary file response
|
||||||
|
$response = new BinaryFileResponse($file_path);
|
||||||
|
|
||||||
|
// Set appropriate content type
|
||||||
|
$extension = pathinfo($filename, PATHINFO_EXTENSION);
|
||||||
|
$mime_type = $extension === 'js' ? 'application/javascript' : 'text/css';
|
||||||
|
$response->headers->set('Content-Type', $mime_type . '; charset=utf-8');
|
||||||
|
|
||||||
|
// Set cache headers - 1 month cache for vendor CDN files
|
||||||
|
// These files are versioned by their content hash in the filename
|
||||||
|
$cache_seconds = 2592000; // 30 days (1 month)
|
||||||
|
$response->headers->set('Cache-Control', 'public, max-age=' . $cache_seconds . ', immutable');
|
||||||
|
$response->headers->set('Expires', gmdate('D, d M Y H:i:s', time() + $cache_seconds) . ' GMT');
|
||||||
|
|
||||||
|
// Security headers
|
||||||
|
$response->headers->set('X-Content-Type-Options', 'nosniff');
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compile a bundle on-demand in development mode
|
* Compile a bundle on-demand in development mode
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -23,6 +23,10 @@
|
|||||||
*
|
*
|
||||||
* If conditions fail, falls back to flash alert.
|
* If conditions fail, falls back to flash alert.
|
||||||
* If layout display throws, falls back to flash alert.
|
* If layout display throws, falls back to flash alert.
|
||||||
|
*
|
||||||
|
* Error Hints:
|
||||||
|
* Common error patterns are detected and helpful hints are appended:
|
||||||
|
* - "this.$ is not a function" / "that.$ is not a function" → suggest this.$.find()
|
||||||
*/
|
*/
|
||||||
class Exception_Handler {
|
class Exception_Handler {
|
||||||
|
|
||||||
@@ -51,6 +55,39 @@ class Exception_Handler {
|
|||||||
|
|
||||||
Exception_Handler.display_unhandled_exception(exception, meta);
|
Exception_Handler.display_unhandled_exception(exception, meta);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Patch console.error to add helpful hints for common JQHTML errors
|
||||||
|
Exception_Handler._patch_console_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch console.error to detect common error patterns and add helpful hints
|
||||||
|
* This helps developers quickly understand common mistakes like:
|
||||||
|
* - Using this.$ or that.$ as a function (should use this.$.find())
|
||||||
|
*/
|
||||||
|
static _patch_console_error() {
|
||||||
|
const original_console_error = console.error.bind(console);
|
||||||
|
|
||||||
|
console.error = function(...args) {
|
||||||
|
// Call original console.error first
|
||||||
|
original_console_error(...args);
|
||||||
|
|
||||||
|
// Check if this looks like a JQHTML callback error
|
||||||
|
if (args.length >= 2 && typeof args[0] === 'string' && args[0].includes('[JQHTML]')) {
|
||||||
|
const error = args[1];
|
||||||
|
const error_message = error instanceof Error ? error.message : String(error);
|
||||||
|
|
||||||
|
// Check for common "this.$ is not a function" or "that.$ is not a function" error
|
||||||
|
if (error_message.includes('this.$ is not a function') ||
|
||||||
|
error_message.includes('that.$ is not a function')) {
|
||||||
|
original_console_error(
|
||||||
|
'%c[Hint] Did you mean this.$.find() or that.$.find()? ' +
|
||||||
|
'The component element (this.$) is a jQuery object, not a function.',
|
||||||
|
'color: #0dcaf0; font-style: italic;'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class Rsx_Behaviors {
|
|||||||
static _on_framework_core_init() {
|
static _on_framework_core_init() {
|
||||||
Rsx_Behaviors._init_ignore_invalid_anchor_links();
|
Rsx_Behaviors._init_ignore_invalid_anchor_links();
|
||||||
Rsx_Behaviors._trim_copied_text();
|
Rsx_Behaviors._trim_copied_text();
|
||||||
|
Rsx_Behaviors._init_file_drop_handler();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,4 +106,164 @@ class Rsx_Behaviors {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global file drop handler
|
||||||
|
*
|
||||||
|
* Intercepts all file drag/drop operations and routes them to components
|
||||||
|
* marked with the `rsx-droppable` class. Components receive a `file-drop`
|
||||||
|
* event with the dropped files.
|
||||||
|
*
|
||||||
|
* CSS Classes:
|
||||||
|
* - `rsx-droppable` - Marks element as a valid drop target
|
||||||
|
* - `rsx-drop-active` - Added to all droppables during file drag
|
||||||
|
* - `rsx-drop-target` - Added to the specific element that will receive drop
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - Single visible droppable: auto-becomes target
|
||||||
|
* - Multiple visible droppables: must hover over specific element
|
||||||
|
* - No-drop cursor when not over valid target
|
||||||
|
* - Widgets handle their own file type filtering
|
||||||
|
*/
|
||||||
|
static _init_file_drop_handler() {
|
||||||
|
let drag_counter = 0;
|
||||||
|
let current_target = null;
|
||||||
|
|
||||||
|
// Get all visible droppable elements
|
||||||
|
const get_visible_droppables = () => {
|
||||||
|
return $('.rsx-droppable').filter(function() {
|
||||||
|
const $el = $(this);
|
||||||
|
// Must be visible and not hidden by CSS
|
||||||
|
return $el.is(':visible') && $el.css('visibility') !== 'hidden';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if event contains files
|
||||||
|
const has_files = (e) => {
|
||||||
|
if (e.originalEvent && e.originalEvent.dataTransfer) {
|
||||||
|
const types = e.originalEvent.dataTransfer.types;
|
||||||
|
return types && (types.includes('Files') || types.indexOf('Files') >= 0);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update which element is the current target
|
||||||
|
const update_target = ($new_target) => {
|
||||||
|
if (current_target) {
|
||||||
|
$(current_target).removeClass('rsx-drop-target');
|
||||||
|
}
|
||||||
|
current_target = $new_target ? $new_target[0] : null;
|
||||||
|
if (current_target) {
|
||||||
|
$(current_target).addClass('rsx-drop-target');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clear all drag state
|
||||||
|
const clear_drag_state = () => {
|
||||||
|
drag_counter = 0;
|
||||||
|
$('.rsx-drop-active').removeClass('rsx-drop-active');
|
||||||
|
update_target(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// dragenter - file enters the window
|
||||||
|
$(document).on('dragenter', function(e) {
|
||||||
|
if (!has_files(e)) return;
|
||||||
|
|
||||||
|
drag_counter++;
|
||||||
|
|
||||||
|
if (drag_counter === 1) {
|
||||||
|
// First entry - activate all droppables
|
||||||
|
const $droppables = get_visible_droppables();
|
||||||
|
$droppables.addClass('rsx-drop-active');
|
||||||
|
|
||||||
|
// If only one droppable, auto-target it
|
||||||
|
if ($droppables.length === 1) {
|
||||||
|
update_target($droppables.first());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// dragleave - file leaves an element
|
||||||
|
$(document).on('dragleave', function(e) {
|
||||||
|
if (!has_files(e)) return;
|
||||||
|
|
||||||
|
drag_counter--;
|
||||||
|
|
||||||
|
if (drag_counter <= 0) {
|
||||||
|
clear_drag_state();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// dragover - file is over an element (fires continuously)
|
||||||
|
$(document).on('dragover', function(e) {
|
||||||
|
if (!has_files(e)) return;
|
||||||
|
|
||||||
|
e.preventDefault(); // Required to allow drop
|
||||||
|
|
||||||
|
const $droppables = get_visible_droppables();
|
||||||
|
|
||||||
|
if ($droppables.length === 0) {
|
||||||
|
// No drop targets - show no-drop cursor
|
||||||
|
e.originalEvent.dataTransfer.dropEffect = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($droppables.length === 1) {
|
||||||
|
// Single target - already set, allow copy
|
||||||
|
e.originalEvent.dataTransfer.dropEffect = 'copy';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiple targets - find if we're over one
|
||||||
|
const $hovered = $(e.target).closest('.rsx-droppable');
|
||||||
|
|
||||||
|
if ($hovered.length && $hovered.hasClass('rsx-drop-active')) {
|
||||||
|
// Over a valid droppable
|
||||||
|
update_target($hovered);
|
||||||
|
e.originalEvent.dataTransfer.dropEffect = 'copy';
|
||||||
|
} else {
|
||||||
|
// Not over any droppable - show no-drop cursor
|
||||||
|
update_target(null);
|
||||||
|
e.originalEvent.dataTransfer.dropEffect = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// drop - file is dropped
|
||||||
|
$(document).on('drop', function(e) {
|
||||||
|
if (!has_files(e)) return;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const files = e.originalEvent.dataTransfer.files;
|
||||||
|
|
||||||
|
if (current_target && files.length > 0) {
|
||||||
|
// Trigger file-drop event on the component
|
||||||
|
const $target = $(current_target);
|
||||||
|
const component = $target.component();
|
||||||
|
|
||||||
|
if (component) {
|
||||||
|
component.trigger('file-drop', {
|
||||||
|
files: files,
|
||||||
|
dataTransfer: e.originalEvent.dataTransfer,
|
||||||
|
originalEvent: e.originalEvent
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// No component - trigger jQuery event on element directly
|
||||||
|
$target.trigger('file-drop', {
|
||||||
|
files: files,
|
||||||
|
dataTransfer: e.originalEvent.dataTransfer,
|
||||||
|
originalEvent: e.originalEvent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_drag_state();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle drag end (e.g., user presses Escape)
|
||||||
|
$(document).on('dragend', function(e) {
|
||||||
|
clear_drag_state();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ This documentation is for developers working on Manifest.php itself. For usage d
|
|||||||
|
|
||||||
## CRITICAL: Testing Manifest.php Changes
|
## CRITICAL: Testing Manifest.php Changes
|
||||||
|
|
||||||
**When modifying Manifest.php, you MUST run `php artisan rsx:manifest:build --clean` to test your changes.**
|
**When modifying Manifest.php or any helper class, you MUST run `php artisan rsx:manifest:build --clean` to test your changes.**
|
||||||
|
|
||||||
The `--clean` flag performs both `rsx:clean` and a full manifest rebuild, ensuring your modifications are properly tested. Without this, the manifest may use cached or partially-built data that doesn't reflect your changes.
|
The `--clean` flag performs both `rsx:clean` and a full manifest rebuild, ensuring your modifications are properly tested. Without this, the manifest may use cached or partially-built data that doesn't reflect your changes.
|
||||||
|
|
||||||
@@ -12,6 +12,25 @@ The `--clean` flag performs both `rsx:clean` and a full manifest rebuild, ensuri
|
|||||||
|
|
||||||
The Manifest is a compiled cache of all file metadata in the RSX application, stored at `storage/rsx-build/manifest_data.php`. It replaces Laravel's scattered discovery mechanisms with a unified system that enables path-agnostic class loading.
|
The Manifest is a compiled cache of all file metadata in the RSX application, stored at `storage/rsx-build/manifest_data.php`. It replaces Laravel's scattered discovery mechanisms with a unified system that enables path-agnostic class loading.
|
||||||
|
|
||||||
|
### Helper Class Architecture
|
||||||
|
|
||||||
|
Manifest.php uses a delegator pattern with 8 helper classes for better organization:
|
||||||
|
|
||||||
|
| Helper Class | Purpose | Functions |
|
||||||
|
|--------------|---------|-----------|
|
||||||
|
| `_Manifest_PHP_Reflection_Helper` | PHP class reflection | php_find_class, php_get_extending, php_is_subclass_of, etc. |
|
||||||
|
| `_Manifest_JS_Reflection_Helper` | JS class reflection | js_find_class, js_get_extending, js_is_subclass_of, etc. |
|
||||||
|
| `_Manifest_Reflection_Helper` | Views, attributes, routes | find_view, get_with_attribute, get_routes, etc. |
|
||||||
|
| `_Manifest_Scanner_Helper` | File discovery, change detection | _get_rsx_files, _has_changed, _scan_directory_for_classes, etc. |
|
||||||
|
| `_Manifest_Builder_Helper` | Index generation | _build_autoloader_class_map, _collate_files_by_classes, etc. |
|
||||||
|
| `_Manifest_Cache_Helper` | Persistence, validation | _get_kernel, _load_cached_data, _save, _validate_cached_data, etc. |
|
||||||
|
| `_Manifest_Quality_Helper` | Code quality, IDE support | _process_code_quality_metadata, _generate_vscode_stubs, etc. |
|
||||||
|
| `_Manifest_Database_Helper` | Database schema | db_get_tables, db_get_table_columns, _verify_database_provisioned, etc. |
|
||||||
|
|
||||||
|
**Pattern**: Manifest.php contains the public API and delegator methods. Each delegator forwards to the corresponding helper class method. Internal methods use single `_` prefix (conceptually private but technically public for cross-class access).
|
||||||
|
|
||||||
|
**When modifying**: Edit the helper class containing the implementation, not Manifest.php (unless changing the public API signature).
|
||||||
|
|
||||||
### Core Data Structure
|
### Core Data Structure
|
||||||
|
|
||||||
The manifest cache contains:
|
The manifest cache contains:
|
||||||
@@ -47,26 +66,30 @@ This tool deduplicates array structures, showing unique patterns with example va
|
|||||||
|
|
||||||
## 6-Phase Build Process
|
## 6-Phase Build Process
|
||||||
|
|
||||||
The build process in Manifest.php follows these phases:
|
The build process follows these phases (implementations in helper classes):
|
||||||
|
|
||||||
### Phase 1: File Discovery (__discover_files)
|
### Phase 1: File Discovery
|
||||||
|
- Implemented in `_Manifest_Scanner_Helper::_get_rsx_files()`
|
||||||
- Scans directories from `config('rsx.manifest.scan_directories')`
|
- Scans directories from `config('rsx.manifest.scan_directories')`
|
||||||
- Default: `['rsx', 'app/RSpade/Core/Js', 'app/RSpade/Core/Manifest/Modules']`
|
- Default: `['rsx', 'app/RSpade/Core/Js', 'app/RSpade/Core/Manifest/Modules']`
|
||||||
- Excludes: vendor/, node_modules/, .git/, storage/, public/
|
- Excludes: vendor/, node_modules/, .git/, storage/, public/
|
||||||
- Returns array of file paths with basic stats (mtime, size)
|
- Returns array of file paths with basic stats (mtime, size)
|
||||||
|
|
||||||
### Phase 2: Token Parsing (__extract_basic_metadata)
|
### Phase 2: Token Parsing
|
||||||
|
- Implemented in `_Manifest_Scanner_Helper::_process_file()`
|
||||||
- Uses `token_get_all()` for fast PHP parsing without loading
|
- Uses `token_get_all()` for fast PHP parsing without loading
|
||||||
- Extracts: namespace, class name, extends, implements
|
- Extracts: namespace, class name, extends, implements
|
||||||
- **Normalizes all class references to simple names** (strips namespace qualifiers)
|
- **Normalizes all class references to simple names** (strips namespace qualifiers)
|
||||||
- Builds dependency graph for loading order
|
- Builds dependency graph for loading order
|
||||||
|
|
||||||
### Phase 3: Dependency Loading (__load_changed_php_files)
|
### Phase 3: Dependency Loading
|
||||||
|
- Implemented in `_Manifest_Scanner_Helper::_load_changed_php_files()`
|
||||||
- Loads PHP files in dependency order (parents before children)
|
- Loads PHP files in dependency order (parents before children)
|
||||||
- Uses `_load_class_hierarchy()` to ensure parent classes exist
|
- Uses `_load_class_hierarchy()` to ensure parent classes exist
|
||||||
- Critical for reflection to work properly
|
- Critical for reflection to work properly
|
||||||
|
|
||||||
### Phase 4: Reflection & Module Processing (__process_with_modules)
|
### Phase 4: Reflection & Module Processing
|
||||||
|
- Implemented in `_Manifest_Scanner_Helper::_extract_reflection_data()`
|
||||||
- Runs registered ManifestModule processors
|
- Runs registered ManifestModule processors
|
||||||
- Core processors:
|
- Core processors:
|
||||||
- `Php_ManifestModule`: Extracts attributes, methods via reflection
|
- `Php_ManifestModule`: Extracts attributes, methods via reflection
|
||||||
@@ -75,13 +98,15 @@ The build process in Manifest.php follows these phases:
|
|||||||
- `Model_ManifestSupport`: Adds database schema to models
|
- `Model_ManifestSupport`: Adds database schema to models
|
||||||
- Stores ALL attributes without validation (agnostic extraction)
|
- Stores ALL attributes without validation (agnostic extraction)
|
||||||
|
|
||||||
### Phase 5: Stub Generation (__generate_js_stubs, __generate_model_stubs)
|
### Phase 5: Stub Generation
|
||||||
|
- Implemented in Manifest.php (not delegated)
|
||||||
- Creates JavaScript stub classes for:
|
- Creates JavaScript stub classes for:
|
||||||
- Controllers with `Ajax_Endpoint` methods → `storage/rsx-build/js-stubs/`
|
- Controllers with `Ajax_Endpoint` methods → `storage/rsx-build/js-stubs/`
|
||||||
- Models with `fetch()` methods → `storage/rsx-build/js-model-stubs/`
|
- Models with `fetch()` methods → `storage/rsx-build/js-model-stubs/`
|
||||||
- Enables clean JavaScript API calls without manual Ajax wiring
|
- Enables clean JavaScript API calls without manual Ajax wiring
|
||||||
|
|
||||||
### Phase 6: Cache Writing (__write_manifest_cache)
|
### Phase 6: Cache Writing
|
||||||
|
- Implemented in `_Manifest_Cache_Helper::_save()`
|
||||||
- Serializes to PHP array format
|
- Serializes to PHP array format
|
||||||
- Writes to `storage/rsx-build/manifest_data.php`
|
- Writes to `storage/rsx-build/manifest_data.php`
|
||||||
- Uses `var_export()` for fast `include()` loading
|
- Uses `var_export()` for fast `include()` loading
|
||||||
@@ -112,22 +137,22 @@ public static function _normalize_class_name(string $class_name): string
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Applied At**:
|
**Applied At**:
|
||||||
1. **Token parsing** - `Php_Parser::__extract_class_info()` normalizes `extends` at extraction
|
1. **Token parsing** - `Php_Parser::_extract_class_info()` normalizes `extends` at extraction
|
||||||
2. **Parent lookups** - `Manifest::_load_class_hierarchy()` normalizes before comparison
|
2. **Parent lookups** - `_Manifest_PHP_Reflection_Helper::_load_class_hierarchy()` normalizes before comparison
|
||||||
3. **All class name operations** - Any code comparing class names uses normalization
|
3. **All class name operations** - Any code comparing class names uses normalization
|
||||||
|
|
||||||
**Why This Works**: RSX's unique simple class name enforcement means we only need FQCNs at actual `include_once` time. Throughout the manifest, simple names are sufficient and eliminate format inconsistencies.
|
**Why This Works**: RSX's unique simple class name enforcement means we only need FQCNs at actual `include_once` time. Throughout the manifest, simple names are sufficient and eliminate format inconsistencies.
|
||||||
|
|
||||||
**Bug This Fixes**: Previously, `extends \Rsx\Lib\DataGrid` (with leading `\` from token parser) failed to match stored FQCN `Rsx\Lib\DataGrid`, causing parent class resolution failures.
|
|
||||||
|
|
||||||
### Static State Management
|
### Static State Management
|
||||||
|
|
||||||
The Manifest uses static properties for singleton behavior:
|
The Manifest uses public static properties (accessible by helper classes):
|
||||||
|
|
||||||
```php
|
```php
|
||||||
private static $_manifest = null; // Cached manifest data
|
public static ?array $data = null; // Cached manifest data
|
||||||
private static $_is_initialized = false; // Initialization flag
|
public static bool $_has_init = false; // Initialization flag
|
||||||
private static $_manifest_loaded_at = null; // Load timestamp
|
public static bool $_needs_manifest_restart = false; // Restart signal
|
||||||
|
public static ?ManifestKernel $kernel = null; // Kernel instance
|
||||||
|
public static array $_changed_files = []; // Files changed in last scan
|
||||||
```
|
```
|
||||||
|
|
||||||
### Public API Methods
|
### Public API Methods
|
||||||
@@ -180,7 +205,7 @@ The manifest practices **agnostic attribute extraction**:
|
|||||||
3. **No Class Loading** - Attributes don't need backing classes
|
3. **No Class Loading** - Attributes don't need backing classes
|
||||||
4. **Raw Storage** - Stores exactly what reflection provides
|
4. **Raw Storage** - Stores exactly what reflection provides
|
||||||
|
|
||||||
Example from _extract_php_metadata:
|
Example from `_Manifest_Scanner_Helper::_extract_reflection_data()`:
|
||||||
```php
|
```php
|
||||||
foreach ($method->getAttributes() as $attribute) {
|
foreach ($method->getAttributes() as $attribute) {
|
||||||
$attributes[] = [
|
$attributes[] = [
|
||||||
@@ -197,13 +222,13 @@ Files are tracked by:
|
|||||||
- `size` - File size in bytes (secondary)
|
- `size` - File size in bytes (secondary)
|
||||||
- `hash` - SHA1, only computed when mtime/size change
|
- `hash` - SHA1, only computed when mtime/size change
|
||||||
|
|
||||||
The `__refresh_manifest()` method compares these to detect changes.
|
The `_Manifest_Scanner_Helper::_has_changed()` method compares these to detect changes.
|
||||||
|
|
||||||
### Error Handling
|
### Error Handling
|
||||||
|
|
||||||
All lookup methods throw `RuntimeException` when items not found:
|
All lookup methods throw `RuntimeException` when items not found:
|
||||||
```php
|
```php
|
||||||
if (!isset(self::$_manifest['data']['php_classes'][$class_name])) {
|
if (!isset(Manifest::$data['data']['php_classes'][$class_name])) {
|
||||||
throw new RuntimeException("PHP class not found in manifest: {$class_name}");
|
throw new RuntimeException("PHP class not found in manifest: {$class_name}");
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -248,7 +273,7 @@ Register in `config/rsx.php`:
|
|||||||
## Performance Considerations
|
## Performance Considerations
|
||||||
|
|
||||||
### Caching Strategy
|
### Caching Strategy
|
||||||
- **Development**: Auto-rebuilds via `__refresh_manifest()` on file changes
|
- **Development**: Auto-rebuilds on file changes
|
||||||
- **Production**: Manual rebuild required, loads from cache file
|
- **Production**: Manual rebuild required, loads from cache file
|
||||||
- **Memory**: Full manifest kept in static variable after first load
|
- **Memory**: Full manifest kept in static variable after first load
|
||||||
|
|
||||||
@@ -262,12 +287,11 @@ Register in `config/rsx.php`:
|
|||||||
|
|
||||||
### Issue: "Class not found" during reflection
|
### Issue: "Class not found" during reflection
|
||||||
**Cause**: Parent class not loaded before child
|
**Cause**: Parent class not loaded before child
|
||||||
**Solution**: Check `_load_class_hierarchy()` is working correctly
|
**Solution**: Check `_Manifest_PHP_Reflection_Helper::_load_class_hierarchy()` is working correctly
|
||||||
|
|
||||||
### Issue: Parent class not found (extends mismatch)
|
### Issue: Parent class not found (extends mismatch)
|
||||||
**Cause**: Namespace format variations in `extends` declarations (e.g., `\Rsx\Lib\DataGrid` vs `Rsx\Lib\DataGrid`)
|
**Cause**: Namespace format variations in `extends` declarations (e.g., `\Rsx\Lib\DataGrid` vs `Rsx\Lib\DataGrid`)
|
||||||
**Solution**: Use `Manifest::_normalize_class_name()` to strip namespace qualifiers before comparison
|
**Solution**: Use `Manifest::_normalize_class_name()` to strip namespace qualifiers before comparison
|
||||||
**Fixed In**: Version with class name normalization (2025-10-10)
|
|
||||||
|
|
||||||
### Issue: Attributes not appearing in manifest
|
### Issue: Attributes not appearing in manifest
|
||||||
**Cause**: Method not public static
|
**Cause**: Method not public static
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
248
app/RSpade/Core/Manifest/_Manifest_Builder_Helper.php
Executable file
248
app/RSpade/Core/Manifest/_Manifest_Builder_Helper.php
Executable file
@@ -0,0 +1,248 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\RSpade\Core\Manifest;
|
||||||
|
|
||||||
|
use App\RSpade\Core\Manifest\Manifest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _Manifest_Builder_Helper - Index generation and autoloader building
|
||||||
|
*
|
||||||
|
* This helper class contains function implementations for Manifest.
|
||||||
|
* Functions in this class are called via delegation from Manifest.php.
|
||||||
|
*
|
||||||
|
* @internal Do not use directly - use Manifest:: methods instead.
|
||||||
|
*/
|
||||||
|
class _Manifest_Builder_Helper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Build the autoloader class map for simplified class name resolution
|
||||||
|
* Maps simple class names to their fully qualified class names
|
||||||
|
* @return array Map of simple names to arrays of FQCNs
|
||||||
|
*/
|
||||||
|
public static function _build_autoloader_class_map(): array
|
||||||
|
{
|
||||||
|
$class_map = [];
|
||||||
|
|
||||||
|
// First, collect classes from the manifest files
|
||||||
|
foreach (Manifest::$data['data']['files'] as $file_path => $file_data) {
|
||||||
|
if (isset($file_data['class']) && isset($file_data['namespace'])) {
|
||||||
|
$simple_name = $file_data['class'];
|
||||||
|
$fqcn = $file_data['namespace'] . '\\' . $simple_name;
|
||||||
|
|
||||||
|
if (!isset($class_map[$simple_name])) {
|
||||||
|
$class_map[$simple_name] = [];
|
||||||
|
}
|
||||||
|
$class_map[$simple_name][] = $fqcn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second, scan app/RSpade directory for additional classes
|
||||||
|
$rspade_path = base_path('app/RSpade');
|
||||||
|
if (is_dir($rspade_path)) {
|
||||||
|
$rspade_classes = Manifest::_scan_directory_for_classes($rspade_path);
|
||||||
|
foreach ($rspade_classes as $simple_name => $fqcns) {
|
||||||
|
foreach ($fqcns as $fqcn) {
|
||||||
|
if (!isset($class_map[$simple_name])) {
|
||||||
|
$class_map[$simple_name] = [];
|
||||||
|
}
|
||||||
|
// Only add if not already present
|
||||||
|
if (!in_array($fqcn, $class_map[$simple_name])) {
|
||||||
|
$class_map[$simple_name][] = $fqcn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $class_map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collate files by class names and build inheritance indices
|
||||||
|
*
|
||||||
|
* This method creates two types of indices for both JavaScript and PHP classes:
|
||||||
|
*
|
||||||
|
* 1. Class indices (js_classes, php_classes):
|
||||||
|
* Maps class names to their file metadata for O(1) lookups by class name
|
||||||
|
*
|
||||||
|
* 2. Subclass indices (js_subclass_index, php_subclass_index):
|
||||||
|
* Maps each parent class name to an array of its direct subclasses
|
||||||
|
* Example: Rsx_Controller_Abstract => ['Demo_Index_Controller', 'Backend_Controller', ...]
|
||||||
|
*
|
||||||
|
* The subclass indices enable efficient inheritance checking without iterating
|
||||||
|
* through the entire manifest. Instead of O(n) complexity for finding subclasses,
|
||||||
|
* we get O(1) direct subclass lookups.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function _collate_files_by_classes()
|
||||||
|
{
|
||||||
|
foreach (['js', 'php'] as $ext) {
|
||||||
|
Manifest::$data['data'][$ext . '_classes'] = [];
|
||||||
|
|
||||||
|
// Step 1: Index files by class name for quick lookups
|
||||||
|
// This creates a map of className => filename (not full metadata to save space)
|
||||||
|
// NOTE: Class override detection (rsx/ vs app/RSpade/) happens earlier in _check_unique_base_class_names()
|
||||||
|
foreach (Manifest::$data['data']['files'] as $file => $filedata) {
|
||||||
|
if ($filedata['extension'] == $ext && !empty($filedata['class'])) {
|
||||||
|
$class_name = $filedata['class'];
|
||||||
|
|
||||||
|
// Duplicates should have been caught by _check_unique_base_class_names()
|
||||||
|
// but check here as a safety net
|
||||||
|
if (isset(Manifest::$data['data'][$ext . '_classes'][$class_name])) {
|
||||||
|
$existing_file = Manifest::$data['data'][$ext . '_classes'][$class_name];
|
||||||
|
throw new \RuntimeException(
|
||||||
|
"Duplicate {$ext} class detected: {$class_name}\n" .
|
||||||
|
"Found in:\n - {$existing_file}\n - {$file}\n" .
|
||||||
|
"Class names must be unique across the codebase."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Manifest::$data['data'][$ext . '_classes'][$class_name] = $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Build parent chain index for each class
|
||||||
|
// This traverses up the inheritance tree and collects all parent classes
|
||||||
|
Manifest::$data['data'][$ext . '_subclass_index'] = [];
|
||||||
|
foreach (Manifest::$data['data'][$ext . '_classes'] as $class => $file_path) {
|
||||||
|
// Get metadata from files array
|
||||||
|
$classdata = Manifest::$data['data']['files'][$file_path];
|
||||||
|
|
||||||
|
// Walk up the parent chain until we reach the root
|
||||||
|
do {
|
||||||
|
$extends = !empty($classdata['extends']) ? $classdata['extends'] : null;
|
||||||
|
if (!empty($extends)) {
|
||||||
|
// Initialize the parent chain array if needed
|
||||||
|
if (empty(Manifest::$data['data'][$ext . '_subclass_index'][$extends])) {
|
||||||
|
Manifest::$data['data'][$ext . '_subclass_index'][$extends] = [];
|
||||||
|
}
|
||||||
|
// Add this parent to the chain
|
||||||
|
Manifest::$data['data'][$ext . '_subclass_index'][$extends][] = $class;
|
||||||
|
// Move up to the parent's metadata (if it exists in manifest)
|
||||||
|
if (!empty(Manifest::$data['data'][$ext . '_classes'][$extends])) {
|
||||||
|
$parent_file = Manifest::$data['data'][$ext . '_classes'][$extends];
|
||||||
|
$classdata = Manifest::$data['data']['files'][$parent_file];
|
||||||
|
} else {
|
||||||
|
// Parent not in manifest (e.g., Laravel framework class), stop here
|
||||||
|
$classdata = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No parent, we've reached the root
|
||||||
|
$classdata = null;
|
||||||
|
}
|
||||||
|
} while (!empty($classdata));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build event handler index from OnEvent attributes
|
||||||
|
*
|
||||||
|
* Scans all PHP files for methods with #[OnEvent] attributes and builds
|
||||||
|
* an index of event_name => [handlers] for fast event dispatching.
|
||||||
|
*
|
||||||
|
* Index structure:
|
||||||
|
* ['event_handlers'] => [
|
||||||
|
* 'event.name' => [
|
||||||
|
* ['class' => 'Class_Name', 'method' => 'method_name', 'priority' => 100],
|
||||||
|
* ['class' => 'Other_Class', 'method' => 'other_method', 'priority' => 200],
|
||||||
|
* ]
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* Handlers are sorted by priority (lower numbers execute first).
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function _build_event_handler_index()
|
||||||
|
{
|
||||||
|
Manifest::$data['data']['event_handlers'] = [];
|
||||||
|
|
||||||
|
// Scan all PHP files for OnEvent attributes
|
||||||
|
foreach (Manifest::$data['data']['files'] as $file_path => $metadata) {
|
||||||
|
// Only process PHP files
|
||||||
|
if (($metadata['extension'] ?? '') !== 'php') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip files without a class
|
||||||
|
if (empty($metadata['class'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$class_name = $metadata['class'];
|
||||||
|
|
||||||
|
// Check public static methods for OnEvent attributes
|
||||||
|
if (isset($metadata['public_static_methods'])) {
|
||||||
|
foreach ($metadata['public_static_methods'] as $method_name => $method_data) {
|
||||||
|
// Check if method has OnEvent attribute
|
||||||
|
if (isset($method_data['attributes']['OnEvent'])) {
|
||||||
|
$on_event_attrs = $method_data['attributes']['OnEvent'];
|
||||||
|
|
||||||
|
// Process each OnEvent attribute instance (a method can have multiple)
|
||||||
|
foreach ($on_event_attrs as $attr_args) {
|
||||||
|
// Extract event name - could be positional or named
|
||||||
|
$event_name = $attr_args[0] ?? $attr_args['event'] ?? null;
|
||||||
|
if (!$event_name) {
|
||||||
|
continue; // Skip invalid attribute
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract priority - could be positional or named, defaults to 100
|
||||||
|
$priority = $attr_args[1] ?? $attr_args['priority'] ?? 100;
|
||||||
|
|
||||||
|
// Initialize event array if needed
|
||||||
|
if (!isset(Manifest::$data['data']['event_handlers'][$event_name])) {
|
||||||
|
Manifest::$data['data']['event_handlers'][$event_name] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add handler to event
|
||||||
|
Manifest::$data['data']['event_handlers'][$event_name][] = [
|
||||||
|
'class' => $class_name,
|
||||||
|
'method' => $method_name,
|
||||||
|
'priority' => $priority,
|
||||||
|
'file' => $file_path,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort each event's handlers by priority (lower = earlier)
|
||||||
|
foreach (Manifest::$data['data']['event_handlers'] as $event_name => &$handlers) {
|
||||||
|
usort($handlers, function ($a, $b) {
|
||||||
|
return $a['priority'] <=> $b['priority'];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build index of classless PHP files
|
||||||
|
*
|
||||||
|
* Creates a simple array of file paths for all PHP files in the manifest
|
||||||
|
* that do not contain a class. These files typically contain helper functions,
|
||||||
|
* constants, or other procedural code that needs to be loaded during post_init().
|
||||||
|
*
|
||||||
|
* Stored in manifest data at: $data['data']['classless_php_files']
|
||||||
|
*/
|
||||||
|
public static function _build_classless_php_files_index()
|
||||||
|
{
|
||||||
|
Manifest::$data['data']['classless_php_files'] = [];
|
||||||
|
|
||||||
|
// Scan all PHP files
|
||||||
|
foreach (Manifest::$data['data']['files'] as $file_path => $metadata) {
|
||||||
|
// Only process PHP files
|
||||||
|
if (($metadata['extension'] ?? '') !== 'php') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip files that have a class
|
||||||
|
if (!empty($metadata['class'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add file path to classless index
|
||||||
|
Manifest::$data['data']['classless_php_files'][] = $file_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
445
app/RSpade/Core/Manifest/_Manifest_Cache_Helper.php
Executable file
445
app/RSpade/Core/Manifest/_Manifest_Cache_Helper.php
Executable file
@@ -0,0 +1,445 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\RSpade\Core\Manifest;
|
||||||
|
|
||||||
|
use App\RSpade\Core\Kernels\ManifestKernel;
|
||||||
|
use App\RSpade\Core\Manifest\Manifest;
|
||||||
|
use App\RSpade\Core\Mode\Rsx_Mode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _Manifest_Cache_Helper - Persistence, loading, and validation
|
||||||
|
*
|
||||||
|
* This helper class contains function implementations for Manifest.
|
||||||
|
* Functions in this class are called via delegation from Manifest.php.
|
||||||
|
*
|
||||||
|
* @internal Do not use directly - use Manifest:: methods instead.
|
||||||
|
*/
|
||||||
|
class _Manifest_Cache_Helper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get or create the kernel instance
|
||||||
|
*/
|
||||||
|
public static function _get_kernel(): ManifestKernel
|
||||||
|
{
|
||||||
|
if (Manifest::$kernel === null) {
|
||||||
|
Manifest::$kernel = app(ManifestKernel::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Manifest::$kernel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the full cache file path
|
||||||
|
*/
|
||||||
|
public static function _get_cache_file_path(): string
|
||||||
|
{
|
||||||
|
return base_path() . Manifest::CACHE_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// move to lower soon
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load cached manifest data
|
||||||
|
*/
|
||||||
|
public static function _load_cached_data()
|
||||||
|
{
|
||||||
|
$cache_file = Manifest::_get_cache_file_path();
|
||||||
|
|
||||||
|
if (file_exists($cache_file)) {
|
||||||
|
Manifest::$data = include $cache_file;
|
||||||
|
// Validate structure
|
||||||
|
if (is_array(Manifest::$data) && isset(Manifest::$data['data']['files']) && count(Manifest::$data['data']['files']) > 0) {
|
||||||
|
// Check if manifest is marked as bad due to code quality violations
|
||||||
|
if (isset(Manifest::$data['data']['manifest_is_bad']) && Manifest::$data['data']['manifest_is_bad']) {
|
||||||
|
// Clear the data to force a rebuild
|
||||||
|
Manifest::$data = [
|
||||||
|
'generated' => date('Y-m-d H:i:s'),
|
||||||
|
'hash' => '',
|
||||||
|
'data' => ['files' => []],
|
||||||
|
];
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache doesn't exist or is invalid - return false without logging
|
||||||
|
// Logging happens in init() after we determine we actually need to rebuild
|
||||||
|
Manifest::$data = [
|
||||||
|
'generated' => date('Y-m-d H:i:s'),
|
||||||
|
'hash' => '',
|
||||||
|
'data' => ['files' => []],
|
||||||
|
];
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save manifest data to cache
|
||||||
|
*/
|
||||||
|
public static function _save(): void
|
||||||
|
{
|
||||||
|
// If manifest is marked as bad, handle specially
|
||||||
|
if (Manifest::$_manifest_is_bad) {
|
||||||
|
// Check if manifest has been initialized
|
||||||
|
if (!Manifest::$_has_init) {
|
||||||
|
return; // Don't save if not initialized
|
||||||
|
}
|
||||||
|
|
||||||
|
$cache_file = Manifest::_get_cache_file_path();
|
||||||
|
|
||||||
|
// Check if cache file exists
|
||||||
|
if (!file_exists($cache_file)) {
|
||||||
|
return; // Don't save if cache doesn't exist yet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we've already marked it as bad
|
||||||
|
if (isset(Manifest::$data['data']['manifest_is_bad'])) {
|
||||||
|
return; // Already marked, don't save again
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark as bad and save
|
||||||
|
Manifest::$data['data']['manifest_is_bad'] = true;
|
||||||
|
// Fall through to normal save logic below
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate manifest data before saving
|
||||||
|
Manifest::_validate_manifest_data();
|
||||||
|
|
||||||
|
$cache_file = Manifest::_get_cache_file_path();
|
||||||
|
|
||||||
|
// Ensure directory exists
|
||||||
|
$dir = dirname($cache_file);
|
||||||
|
if (!is_dir($dir)) {
|
||||||
|
mkdir($dir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort files array by key for predictable output
|
||||||
|
if (isset(Manifest::$data['data']['files'])) {
|
||||||
|
ksort(Manifest::$data['data']['files']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update metadata
|
||||||
|
Manifest::$data['generated'] = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
// In development mode, use random hash for cache-busting on every request
|
||||||
|
// if (env('APP_ENV') !== 'production') {
|
||||||
|
// Manifest::$data['hash'] = bin2hex(random_bytes(16)); // 32 character random hash
|
||||||
|
// } else {
|
||||||
|
// In production, use SHA-256 truncated to 32 chars for cryptographic security with consistent length
|
||||||
|
Manifest::$data['hash'] = substr(hash('sha256', json_encode(Manifest::$data['data'])), 0, 32);
|
||||||
|
// }
|
||||||
|
|
||||||
|
$php_content = "<?php\n\n";
|
||||||
|
$php_content .= "// Generated manifest cache - DO NOT EDIT\n";
|
||||||
|
$php_content .= '// Generated: ' . Manifest::$data['generated'] . "\n";
|
||||||
|
$php_content .= '// Files: ' . count(Manifest::$data['data']['files']) . "\n";
|
||||||
|
$php_content .= '// Hash: ' . Manifest::$data['hash'] . "\n\n";
|
||||||
|
|
||||||
|
// Use compact format in production mode, pretty format in dev/debug
|
||||||
|
if (Rsx_Mode::is_production()) {
|
||||||
|
$php_content .= 'return ' . self::_compact_var_export(Manifest::$data) . ";\n";
|
||||||
|
} else {
|
||||||
|
$php_content .= 'return ' . var_export(Manifest::$data, true) . ";\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents($cache_file, $php_content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function _validate_cached_data()
|
||||||
|
{
|
||||||
|
// If cache exists, check if anything changed
|
||||||
|
$files = Manifest::_get_rsx_files();
|
||||||
|
|
||||||
|
$any_changes = false;
|
||||||
|
|
||||||
|
// Check for changed files
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if (Manifest::_has_changed($file)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for deleted files
|
||||||
|
if (!$any_changes) {
|
||||||
|
$existing_files = array_flip($files);
|
||||||
|
|
||||||
|
foreach (array_keys(Manifest::$data['data']['files']) as $cached_file) {
|
||||||
|
// Skip storage files - they're not part of the manifest
|
||||||
|
if (str_starts_with($cached_file, 'storage/')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!isset($existing_files[$cached_file])) {
|
||||||
|
// Only show the message once per page load
|
||||||
|
if (!self::$__shown_rescan_message) {
|
||||||
|
console_debug('MANIFEST', '* Deleted file ' . $cached_file . ' is triggering manifest rescan *');
|
||||||
|
self::$__shown_rescan_message = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate manifest data for consistency
|
||||||
|
*/
|
||||||
|
public static function _validate_manifest_data(): void
|
||||||
|
{
|
||||||
|
if (!isset(Manifest::$data['data']['files'])) {
|
||||||
|
throw new \RuntimeException(
|
||||||
|
'Fatal: Manifest::validate_manifest_data() called but manifest data structure is not initialized. ' .
|
||||||
|
"This shouldn't happen - data should be populated before validation."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track unique names by file type
|
||||||
|
$php_classes = [];
|
||||||
|
$js_classes = [];
|
||||||
|
$blade_ids = [];
|
||||||
|
$jqhtml_ids = [];
|
||||||
|
|
||||||
|
foreach (Manifest::$data['data']['files'] as $file_path => $metadata) {
|
||||||
|
$extension = $metadata['extension'] ?? '';
|
||||||
|
|
||||||
|
// Check PHP class uniqueness
|
||||||
|
if ($extension === 'php' && isset($metadata['class'])) {
|
||||||
|
$class = $metadata['class'];
|
||||||
|
if (isset($php_classes[$class])) {
|
||||||
|
throw new \RuntimeException(
|
||||||
|
"Duplicate PHP class name detected: {$class}\n" .
|
||||||
|
"Found in:\n" .
|
||||||
|
" - {$php_classes[$class]}\n" .
|
||||||
|
" - {$file_path}\n\n" .
|
||||||
|
"PHP class names must be unique across all files.\n\n" .
|
||||||
|
"To resolve: Add specificity to the class names by prefixing with directory segments.\n" .
|
||||||
|
"For example, if a class named 'Index_Controller' resides in the 'demo' module directory,\n" .
|
||||||
|
"rename it to 'Demo_Index_Controller'. Similarly, classes in nested directories like\n" .
|
||||||
|
"'demo/admin/' could be named 'Demo_Admin_Index_Controller'. After renaming, refactor\n" .
|
||||||
|
'all usages of the old class name throughout the codebase to use the new, more specific name.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$php_classes[$class] = $file_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check JavaScript class uniqueness
|
||||||
|
if (in_array($extension, ['js', 'jsx', 'ts', 'tsx']) && isset($metadata['class'])) {
|
||||||
|
$class = $metadata['class'];
|
||||||
|
if (isset($js_classes[$class])) {
|
||||||
|
throw new \RuntimeException(
|
||||||
|
"Duplicate JavaScript class name detected: {$class}\n" .
|
||||||
|
"Found in:\n" .
|
||||||
|
" - {$js_classes[$class]}\n" .
|
||||||
|
" - {$file_path}\n\n" .
|
||||||
|
"JavaScript class names must be unique across all files.\n\n" .
|
||||||
|
"To resolve: Add specificity to the class names by prefixing with directory segments.\n" .
|
||||||
|
"For example, if a class named 'Base' resides in the 'demo' module directory,\n" .
|
||||||
|
"rename it to 'Demo_Base'. Similarly, classes in nested directories like\n" .
|
||||||
|
"'demo/admin/' could be named 'Demo_Admin_Base'. After renaming, refactor\n" .
|
||||||
|
'all usages of the old class name throughout the codebase to use the new, more specific name.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$js_classes[$class] = $file_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Blade ID uniqueness
|
||||||
|
if ($extension === 'blade.php' && isset($metadata['id'])) {
|
||||||
|
$id = $metadata['id'];
|
||||||
|
if (isset($blade_ids[$id])) {
|
||||||
|
throw new \RuntimeException(
|
||||||
|
"Duplicate Blade @rsx_id detected: {$id}\n" .
|
||||||
|
"Found in:\n" .
|
||||||
|
" - {$blade_ids[$id]}\n" .
|
||||||
|
" - {$file_path}\n\n" .
|
||||||
|
"Blade @rsx_id values must be unique across all files.\n\n" .
|
||||||
|
"To resolve: Add specificity to the @rsx_id by prefixing with directory segments.\n" .
|
||||||
|
"For example, if a view with @rsx_id('Layout') resides in the 'demo' module directory,\n" .
|
||||||
|
"change it to @rsx_id('Demo_Layout'). Similarly, views in nested directories like\n" .
|
||||||
|
"'demo/sections/' could use @rsx_id('Demo_Sections_Layout'). After renaming, refactor\n" .
|
||||||
|
'all references to the old @rsx_id (in @rsx_extends, @rsx_include, etc.) to use the new name.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$blade_ids[$id] = $file_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Jqhtml component ID uniqueness
|
||||||
|
if ($extension === 'jqhtml' && isset($metadata['id'])) {
|
||||||
|
$id = $metadata['id'];
|
||||||
|
if (isset($jqhtml_ids[$id])) {
|
||||||
|
throw new \RuntimeException(
|
||||||
|
"Duplicate jqhtml component name detected: {$id}\n" .
|
||||||
|
"Found in:\n" .
|
||||||
|
" - {$jqhtml_ids[$id]}\n" .
|
||||||
|
" - {$file_path}\n\n" .
|
||||||
|
"Jqhtml component names (<Define:ComponentName>) must be unique across all files.\n\n" .
|
||||||
|
"To resolve: Add specificity to the component name by prefixing with directory segments.\n" .
|
||||||
|
"For example, if a component named 'Card' resides in the 'demo' module directory,\n" .
|
||||||
|
"rename it to 'Demo_Card' in the <Define:> tag. Similarly, components in nested directories\n" .
|
||||||
|
"like 'demo/widgets/' could be named 'Demo_Widgets_Card'. After renaming, refactor all\n" .
|
||||||
|
'usages of the component (in Blade templates and JavaScript) to use the new, more specific name.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$jqhtml_ids[$id] = $file_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that controller actions don't have both Route and Ajax_Endpoint
|
||||||
|
if ($extension === 'php' && isset($metadata['extends'])) {
|
||||||
|
// Check if this is a controller (extends Rsx_Controller_Abstract)
|
||||||
|
$is_controller = Manifest::_is_controller_class($metadata);
|
||||||
|
|
||||||
|
if ($is_controller && isset($metadata['public_static_methods'])) {
|
||||||
|
foreach ($metadata['public_static_methods'] as $method_name => $method_info) {
|
||||||
|
if (!isset($method_info['attributes'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$has_route = false;
|
||||||
|
$has_ajax_endpoint = false;
|
||||||
|
$has_task = false;
|
||||||
|
|
||||||
|
foreach ($method_info['attributes'] as $attr_name => $attr_instances) {
|
||||||
|
if ($attr_name === 'Route' || str_ends_with($attr_name, '\\Route')) {
|
||||||
|
$has_route = true;
|
||||||
|
}
|
||||||
|
if ($attr_name === 'Ajax_Endpoint' || str_ends_with($attr_name, '\\Ajax_Endpoint')) {
|
||||||
|
$has_ajax_endpoint = true;
|
||||||
|
}
|
||||||
|
if ($attr_name === 'Task' || str_ends_with($attr_name, '\\Task')) {
|
||||||
|
$has_task = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for conflicting attributes
|
||||||
|
$conflicts = [];
|
||||||
|
if ($has_route) {
|
||||||
|
$conflicts[] = 'Route';
|
||||||
|
}
|
||||||
|
if ($has_ajax_endpoint) {
|
||||||
|
$conflicts[] = 'Ajax_Endpoint';
|
||||||
|
}
|
||||||
|
if ($has_task) {
|
||||||
|
$conflicts[] = 'Task';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($conflicts) > 1) {
|
||||||
|
$class_name = $metadata['class'] ?? 'Unknown';
|
||||||
|
|
||||||
|
throw new \RuntimeException(
|
||||||
|
'Method cannot have multiple execution type attributes: ' . implode(', ', $conflicts) . "\n" .
|
||||||
|
"Class: {$class_name}\n" .
|
||||||
|
"Method: {$method_name}\n" .
|
||||||
|
"File: {$file_path}\n" .
|
||||||
|
'A method must be either a Route, Ajax_Endpoint, OR Task, not multiple types.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Ajax_Endpoint methods don't have return types
|
||||||
|
if ($has_ajax_endpoint && isset($method_info['return_type'])) {
|
||||||
|
$class_name = $metadata['class'] ?? 'Unknown';
|
||||||
|
$return_type_info = $method_info['return_type'];
|
||||||
|
|
||||||
|
// Format return type for error message
|
||||||
|
if (isset($return_type_info['type']) && $return_type_info['type'] === 'union') {
|
||||||
|
$type_display = implode('|', $return_type_info['types']);
|
||||||
|
} else {
|
||||||
|
$type_display = $return_type_info['type'] ?? 'unknown';
|
||||||
|
if (!empty($return_type_info['nullable'])) {
|
||||||
|
$type_display = '?' . $type_display;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \RuntimeException(
|
||||||
|
"Ajax endpoint has forbidden return type declaration: {$type_display}\n" .
|
||||||
|
"Class: {$class_name}\n" .
|
||||||
|
"Method: {$method_name}\n" .
|
||||||
|
"File: {$file_path}\n\n" .
|
||||||
|
"Ajax endpoints must NOT declare return types because they need flexibility to return:\n" .
|
||||||
|
"- Array data (success case)\n" .
|
||||||
|
"- Form_Error_Response (validation errors)\n" .
|
||||||
|
"- Redirect_Response (redirects)\n" .
|
||||||
|
"- Other response types as needed\n\n" .
|
||||||
|
"Solution: Remove the return type declaration from this method.\n" .
|
||||||
|
"Change: public static function {$method_name}(...): {$type_display}\n" .
|
||||||
|
"To: public static function {$method_name}(...)\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlink the manifest cache file only (fast rebuild trigger)
|
||||||
|
*
|
||||||
|
* This removes only the manifest cache file, preserving all parsed AST data
|
||||||
|
* and incremental caches. On next load, the manifest will do a full scan and
|
||||||
|
* reindex but will reuse existing parsed metadata where files haven't changed.
|
||||||
|
*
|
||||||
|
* This is much faster than rsx:clean which wipes all caches including parsed
|
||||||
|
* AST data, forcing expensive re-parsing of all PHP/JS files.
|
||||||
|
*
|
||||||
|
* Use this after database migrations or schema changes that affect model
|
||||||
|
* metadata without changing the actual source code.
|
||||||
|
*/
|
||||||
|
public static function _unlink_cache(): void
|
||||||
|
{
|
||||||
|
$cache_file = Manifest::_get_cache_file_path();
|
||||||
|
if (file_exists($cache_file)) {
|
||||||
|
@unlink($cache_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate compact PHP array export (no whitespace formatting)
|
||||||
|
*
|
||||||
|
* Produces valid PHP array syntax like var_export() but without
|
||||||
|
* newlines and indentation for smaller file size in production.
|
||||||
|
*/
|
||||||
|
public static function _compact_var_export(mixed $value): string
|
||||||
|
{
|
||||||
|
if (is_null($value)) {
|
||||||
|
return 'NULL';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_bool($value)) {
|
||||||
|
return $value ? 'true' : 'false';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_int($value) || is_float($value)) {
|
||||||
|
return (string) $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($value)) {
|
||||||
|
return var_export($value, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($value)) {
|
||||||
|
if (empty($value)) {
|
||||||
|
return 'array()';
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = [];
|
||||||
|
$is_sequential = array_keys($value) === range(0, count($value) - 1);
|
||||||
|
|
||||||
|
foreach ($value as $k => $v) {
|
||||||
|
if ($is_sequential) {
|
||||||
|
$parts[] = self::_compact_var_export($v);
|
||||||
|
} else {
|
||||||
|
$parts[] = var_export($k, true) . '=>' . self::_compact_var_export($v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'array(' . implode(',', $parts) . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback for objects/other types
|
||||||
|
return var_export($value, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
176
app/RSpade/Core/Manifest/_Manifest_Database_Helper.php
Executable file
176
app/RSpade/Core/Manifest/_Manifest_Database_Helper.php
Executable file
@@ -0,0 +1,176 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\RSpade\Core\Manifest;
|
||||||
|
|
||||||
|
use App\RSpade\Core\Manifest\Manifest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _Manifest_Database_Helper - Database schema introspection
|
||||||
|
*
|
||||||
|
* This helper class contains function implementations for Manifest.
|
||||||
|
* Functions in this class are called via delegation from Manifest.php.
|
||||||
|
*
|
||||||
|
* @internal Do not use directly - use Manifest:: methods instead.
|
||||||
|
*/
|
||||||
|
class _Manifest_Database_Helper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get list of all database tables from manifest model metadata
|
||||||
|
*
|
||||||
|
* Returns only tables that have been indexed via Model_ManifestSupport
|
||||||
|
* (i.e., tables with corresponding model classes)
|
||||||
|
*
|
||||||
|
* @return array Array of table names
|
||||||
|
*/
|
||||||
|
public static function db_get_tables(): array
|
||||||
|
{
|
||||||
|
Manifest::init();
|
||||||
|
|
||||||
|
if (!isset(Manifest::$data['data']['models'])) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$tables = [];
|
||||||
|
foreach (Manifest::$data['data']['models'] as $model_data) {
|
||||||
|
if (isset($model_data['table'])) {
|
||||||
|
$tables[] = $model_data['table'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_unique($tables));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get columns for a specific table with their types from manifest
|
||||||
|
*
|
||||||
|
* Returns simplified column information extracted during manifest build.
|
||||||
|
* Types are from Model_ManifestSupport::__parse_column_type() which simplifies
|
||||||
|
* MySQL types (e.g., 'bigint', 'varchar(255)' -> 'integer', 'string')
|
||||||
|
*
|
||||||
|
* @param string $table Table name
|
||||||
|
* @return array Associative array of column_name => type, or empty if table not found
|
||||||
|
*/
|
||||||
|
public static function db_get_table_columns(string $table): array
|
||||||
|
{
|
||||||
|
Manifest::init();
|
||||||
|
|
||||||
|
if (!isset(Manifest::$data['data']['models'])) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the model for this table
|
||||||
|
foreach (Manifest::$data['data']['models'] as $model_data) {
|
||||||
|
if (isset($model_data['table']) && $model_data['table'] === $table) {
|
||||||
|
if (!isset($model_data['columns'])) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($model_data['columns'] as $column_name => $column_data) {
|
||||||
|
$result[$column_name] = $column_data['type'] ?? 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify database has been provisioned before running code quality checks
|
||||||
|
*
|
||||||
|
* Checks that:
|
||||||
|
* 1. The _migrations table exists (created by Laravel migrate)
|
||||||
|
* 2. At least one migration has been applied
|
||||||
|
*
|
||||||
|
* This prevents confusing code quality errors when the real issue is
|
||||||
|
* that migrations haven't been run yet.
|
||||||
|
*/
|
||||||
|
public static function _verify_database_provisioned(): void
|
||||||
|
{
|
||||||
|
$migrations_table = config('database.migrations', 'migrations');
|
||||||
|
|
||||||
|
// Check if migrations table exists
|
||||||
|
if (!\Illuminate\Support\Facades\Schema::hasTable($migrations_table)) {
|
||||||
|
throw new \RuntimeException(
|
||||||
|
"Database not provisioned - migrations table '{$migrations_table}' does not exist.\n\n" .
|
||||||
|
"Run migrations before continuing:\n" .
|
||||||
|
" php artisan migrate\n\n" .
|
||||||
|
"If this is a fresh installation, you may also need to:\n" .
|
||||||
|
" 1. Create the database\n" .
|
||||||
|
" 2. Configure .env with correct DB_* settings\n" .
|
||||||
|
" 3. Run: php artisan migrate"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if at least one migration has been applied
|
||||||
|
$result = \Illuminate\Support\Facades\DB::select("SELECT COUNT(*) as cnt FROM `{$migrations_table}`");
|
||||||
|
if ($result[0]->cnt === 0) {
|
||||||
|
throw new \RuntimeException(
|
||||||
|
"Database not provisioned - no migrations have been applied.\n\n" .
|
||||||
|
"Run migrations before continuing:\n" .
|
||||||
|
" php artisan migrate"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if we're running in a migration context
|
||||||
|
*
|
||||||
|
* Returns true if running migrate, make:migration, or other DB setup commands.
|
||||||
|
* Used to skip code quality checks that depend on database state.
|
||||||
|
*/
|
||||||
|
public static function _is_migration_context(): bool
|
||||||
|
{
|
||||||
|
// Check if running from CLI
|
||||||
|
if (php_sapi_name() !== 'cli') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the artisan command being run
|
||||||
|
$argv = $_SERVER['argv'] ?? [];
|
||||||
|
if (count($argv) < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commands that should skip code quality checks
|
||||||
|
$migration_commands = [
|
||||||
|
'migrate',
|
||||||
|
'migrate:begin',
|
||||||
|
'migrate:commit',
|
||||||
|
'migrate:fresh',
|
||||||
|
'migrate:install',
|
||||||
|
'migrate:refresh',
|
||||||
|
'migrate:reset',
|
||||||
|
'migrate:rollback',
|
||||||
|
'migrate:status',
|
||||||
|
'make:migration',
|
||||||
|
'make:migration:safe',
|
||||||
|
'db:seed',
|
||||||
|
'db:wipe',
|
||||||
|
];
|
||||||
|
|
||||||
|
$command = $argv[1] ?? '';
|
||||||
|
return in_array($command, $migration_commands, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get columns of a specific type for a table from manifest
|
||||||
|
*
|
||||||
|
* Useful for finding all boolean columns (tinyint), integer columns, etc.
|
||||||
|
*
|
||||||
|
* @param string $table Table name
|
||||||
|
* @param string $type Type to filter by (from Model_ManifestSupport simplified types)
|
||||||
|
* @return array Array of column names matching the type
|
||||||
|
*/
|
||||||
|
public static function db_get_columns_by_type(string $table, string $type): array
|
||||||
|
{
|
||||||
|
$columns = Manifest::db_get_table_columns($table);
|
||||||
|
|
||||||
|
return array_keys(array_filter($columns, function ($col_type) use ($type) {
|
||||||
|
return $col_type === $type;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
185
app/RSpade/Core/Manifest/_Manifest_JS_Reflection_Helper.php
Executable file
185
app/RSpade/Core/Manifest/_Manifest_JS_Reflection_Helper.php
Executable file
@@ -0,0 +1,185 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\RSpade\Core\Manifest;
|
||||||
|
|
||||||
|
use App\RSpade\Core\Manifest\Manifest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _Manifest_JS_Reflection_Helper - JavaScript class reflection operations
|
||||||
|
*
|
||||||
|
* This helper class contains function implementations for Manifest.
|
||||||
|
* Functions in this class are called via delegation from Manifest.php.
|
||||||
|
*
|
||||||
|
* @internal Do not use directly - use Manifest:: methods instead.
|
||||||
|
*/
|
||||||
|
class _Manifest_JS_Reflection_Helper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Find a JavaScript class
|
||||||
|
*/
|
||||||
|
public static function js_find_class(string $class_name): string
|
||||||
|
{
|
||||||
|
Manifest::init();
|
||||||
|
|
||||||
|
if (!isset(Manifest::$data['data']['js_classes'][$class_name])) {
|
||||||
|
throw new \RuntimeException("JavaScript class not found in manifest: {$class_name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Manifest::$data['data']['js_classes'][$class_name];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all JavaScript classes extending a parent
|
||||||
|
* Returns array of class metadata indexed by class name
|
||||||
|
*/
|
||||||
|
public static function js_get_extending(string $parentclass): array
|
||||||
|
{
|
||||||
|
// Get all subclasses (JavaScript has no abstract concept)
|
||||||
|
$subclasses = self::js_get_subclasses_of($parentclass);
|
||||||
|
|
||||||
|
$classpile = [];
|
||||||
|
foreach ($subclasses as $classname) {
|
||||||
|
// Get the file path from js_classes index, then get metadata from files
|
||||||
|
if (isset(Manifest::$data['data']['js_classes'][$classname])) {
|
||||||
|
$file_path = Manifest::$data['data']['js_classes'][$classname];
|
||||||
|
$classpile[$classname] = Manifest::$data['data']['files'][$file_path];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $classpile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a class is a subclass of another by traversing the inheritance chain
|
||||||
|
*
|
||||||
|
* @param string $subclass The child class name (simple name, not FQCN)
|
||||||
|
* @param string $superclass The parent class name to check for (simple name, not FQCN)
|
||||||
|
* @return bool True if subclass extends superclass (directly or indirectly), false otherwise
|
||||||
|
*/
|
||||||
|
public static function js_is_subclass_of(string $subclass, string $superclass): bool
|
||||||
|
{
|
||||||
|
// Strip namespace if FQCN was passed (contains backslash)
|
||||||
|
if (strpos($subclass, '\\') !== false) {
|
||||||
|
// Get the class name after the last backslash
|
||||||
|
$parts = explode('\\', $subclass);
|
||||||
|
$subclass = end($parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($superclass, '\\') !== false) {
|
||||||
|
// Get the class name after the last backslash
|
||||||
|
$parts = explode('\\', $superclass);
|
||||||
|
$superclass = end($parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = Manifest::get_all();
|
||||||
|
$current_class = $subclass;
|
||||||
|
$visited = []; // Prevent infinite loops in case of circular inheritance
|
||||||
|
|
||||||
|
while ($current_class) {
|
||||||
|
// Prevent infinite loops
|
||||||
|
if (in_array($current_class, $visited)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$visited[] = $current_class;
|
||||||
|
|
||||||
|
// HACK #1 - JS Model shortcut: When checking against Rsx_Js_Model, if we encounter
|
||||||
|
// a PHP model class name (like "Project_Model"), we know it's a model that will have
|
||||||
|
// a generated Base_ stub extending Rsx_Js_Model. Return true immediately.
|
||||||
|
if ($superclass === 'Rsx_Js_Model' && Manifest::is_php_model_class($current_class)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the current class in the manifest
|
||||||
|
if (!isset(Manifest::$data['data']['js_classes'][$current_class])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get file metadata
|
||||||
|
$file_path = Manifest::$data['data']['js_classes'][$current_class];
|
||||||
|
$metadata = Manifest::$data['data']['files'][$file_path];
|
||||||
|
|
||||||
|
if (empty($metadata['extends'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($metadata['extends'] == $superclass) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move up the chain to the parent class
|
||||||
|
$current_class = $metadata['extends'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the complete inheritance chain for a JavaScript class
|
||||||
|
* Returns array of parent class names in order from immediate parent to root
|
||||||
|
*
|
||||||
|
* @param string $class_name The class name to get lineage for
|
||||||
|
* @return array Array of parent class names (empty if class not found or has no parents)
|
||||||
|
* Example: If A extends B and B extends C, js_get_lineage('A') returns ['B', 'C']
|
||||||
|
*/
|
||||||
|
public static function js_get_lineage(string $class_name): array
|
||||||
|
{
|
||||||
|
// Strip namespace if FQCN was passed
|
||||||
|
if (strpos($class_name, '\\') !== false) {
|
||||||
|
$parts = explode('\\', $class_name);
|
||||||
|
$class_name = end($parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
$lineage = [];
|
||||||
|
$current_class = $class_name;
|
||||||
|
$visited = []; // Prevent infinite loops
|
||||||
|
|
||||||
|
while ($current_class) {
|
||||||
|
// Prevent infinite loops in circular inheritance
|
||||||
|
if (in_array($current_class, $visited)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$visited[] = $current_class;
|
||||||
|
|
||||||
|
// Find the current class in manifest
|
||||||
|
if (!isset(Manifest::$data['data']['js_classes'][$current_class])) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the file path from js_classes index, then get metadata from files
|
||||||
|
$file_path = Manifest::$data['data']['js_classes'][$current_class];
|
||||||
|
$metadata = Manifest::$data['data']['files'][$file_path];
|
||||||
|
$extends = $metadata['extends'] ?? null;
|
||||||
|
|
||||||
|
if (!$extends) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add parent to lineage
|
||||||
|
$lineage[] = $extends;
|
||||||
|
|
||||||
|
// Move up the chain
|
||||||
|
$current_class = $extends;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $lineage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all direct subclasses of a given JavaScript class using the pre-built index
|
||||||
|
*
|
||||||
|
* @param string $class_name The parent class name
|
||||||
|
* @return array Array of subclass names, or empty array if class not found or has no children
|
||||||
|
*/
|
||||||
|
public static function js_get_subclasses_of(string $class_name): array
|
||||||
|
{
|
||||||
|
// Return empty array if class not in subclass_index
|
||||||
|
if (!isset(Manifest::$data['data']['js_subclass_index'][$class_name])) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Manifest::$data['data']['js_subclass_index'][$class_name];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
439
app/RSpade/Core/Manifest/_Manifest_PHP_Reflection_Helper.php
Executable file
439
app/RSpade/Core/Manifest/_Manifest_PHP_Reflection_Helper.php
Executable file
@@ -0,0 +1,439 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\RSpade\Core\Manifest;
|
||||||
|
|
||||||
|
use App\RSpade\Core\Manifest\Manifest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _Manifest_PHP_Reflection_Helper - PHP class reflection operations
|
||||||
|
*
|
||||||
|
* This helper class contains function implementations for Manifest.
|
||||||
|
* Functions in this class are called via delegation from Manifest.php.
|
||||||
|
*
|
||||||
|
* @internal Do not use directly - use Manifest:: methods instead.
|
||||||
|
*/
|
||||||
|
class _Manifest_PHP_Reflection_Helper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Find a PHP class by name
|
||||||
|
*/
|
||||||
|
public static function php_find_class(string $class_name): string
|
||||||
|
{
|
||||||
|
Manifest::init();
|
||||||
|
|
||||||
|
if (!isset(Manifest::$data['data']['php_classes'][$class_name])) {
|
||||||
|
throw new \RuntimeException("PHP class not found in manifest: {$class_name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Manifest::$data['data']['php_classes'][$class_name];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a PHP class by fully qualified name
|
||||||
|
*/
|
||||||
|
public static function find_php_fqcn(string $fqcn): string
|
||||||
|
{
|
||||||
|
$files = Manifest::get_all();
|
||||||
|
|
||||||
|
foreach ($files as $file => $metadata) {
|
||||||
|
if (isset($metadata['fqcn']) && $metadata['fqcn'] === $fqcn) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \RuntimeException("PHP class with FQCN not found in manifest: {$fqcn}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get manifest metadata by PHP class name
|
||||||
|
* This is a convenience method that finds the class and returns its metadata
|
||||||
|
*/
|
||||||
|
public static function php_get_metadata_by_class(string $class_name): array
|
||||||
|
{
|
||||||
|
$file = Manifest::php_find_class($class_name);
|
||||||
|
|
||||||
|
return Manifest::get_file($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get manifest metadata by PHP fully qualified class name
|
||||||
|
* This is a convenience method that finds the class and returns its metadata
|
||||||
|
*/
|
||||||
|
public static function php_get_metadata_by_fqcn(string $fqcn): array
|
||||||
|
{
|
||||||
|
$file = Manifest::find_php_fqcn($fqcn);
|
||||||
|
|
||||||
|
return Manifest::get_file($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all classes extending a parent (filters out abstract classes by default)
|
||||||
|
* Returns array of class metadata indexed by class name
|
||||||
|
*/
|
||||||
|
public static function php_get_extending(string $parentclass): array
|
||||||
|
{
|
||||||
|
// Get concrete subclasses only (abstract filtered out by default)
|
||||||
|
$subclasses = self::php_get_subclasses_of($parentclass, true);
|
||||||
|
|
||||||
|
$classpile = [];
|
||||||
|
foreach ($subclasses as $classname) {
|
||||||
|
// Get the file path from php_classes index, then get metadata from files
|
||||||
|
if (isset(Manifest::$data['data']['php_classes'][$classname])) {
|
||||||
|
$file_path = Manifest::$data['data']['php_classes'][$classname];
|
||||||
|
$classpile[$classname] = Manifest::$data['data']['files'][$file_path];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $classpile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a class is a subclass of another by traversing the inheritance chain
|
||||||
|
*
|
||||||
|
* @param string $subclass The child class name (simple name, not FQCN)
|
||||||
|
* @param string $superclass The parent class name to check for (simple name, not FQCN)
|
||||||
|
* @return bool True if subclass extends superclass (directly or indirectly), false otherwise
|
||||||
|
*/
|
||||||
|
public static function php_is_subclass_of(string $subclass, string $superclass): bool
|
||||||
|
{
|
||||||
|
// Strip namespace if FQCN was passed (contains backslash)
|
||||||
|
if (strpos($subclass, '\\') !== false) {
|
||||||
|
// Get the class name after the last backslash
|
||||||
|
$parts = explode('\\', $subclass);
|
||||||
|
$subclass = end($parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($superclass, '\\') !== false) {
|
||||||
|
// Get the class name after the last backslash
|
||||||
|
$parts = explode('\\', $superclass);
|
||||||
|
$superclass = end($parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = Manifest::get_all();
|
||||||
|
$current_class = $subclass;
|
||||||
|
$visited = []; // Prevent infinite loops in case of circular inheritance
|
||||||
|
|
||||||
|
while ($current_class) {
|
||||||
|
// Prevent infinite loops
|
||||||
|
if (in_array($current_class, $visited)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$visited[] = $current_class;
|
||||||
|
|
||||||
|
// Find the current class in the manifest
|
||||||
|
if (!isset(Manifest::$data['data']['php_classes'][$current_class])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get file metadata
|
||||||
|
$file_path = Manifest::$data['data']['php_classes'][$current_class];
|
||||||
|
$metadata = Manifest::$data['data']['files'][$file_path];
|
||||||
|
|
||||||
|
if (empty($metadata['extends'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($metadata['extends'] == $superclass) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Maybe use native reflection if base class does not exist in the manifest past this point>
|
||||||
|
|
||||||
|
// Move up the chain to the parent class
|
||||||
|
$current_class = $metadata['extends'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a PHP class is abstract
|
||||||
|
*
|
||||||
|
* @param string $class_name The class name to check (simple name, not FQCN)
|
||||||
|
* @return bool True if the class is abstract, false if concrete or not found
|
||||||
|
*/
|
||||||
|
public static function php_is_abstract(string $class_name): bool
|
||||||
|
{
|
||||||
|
// Ensure manifest is loaded
|
||||||
|
Manifest::init();
|
||||||
|
|
||||||
|
// Strip namespace if FQCN was passed
|
||||||
|
if (strpos($class_name, '\\') !== false) {
|
||||||
|
$parts = explode('\\', $class_name);
|
||||||
|
$class_name = end($parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return false if class not in manifest
|
||||||
|
if (!isset(Manifest::$data['data']['php_classes'][$class_name])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get file metadata and check the abstract property
|
||||||
|
$file_path = Manifest::$data['data']['php_classes'][$class_name];
|
||||||
|
$metadata = Manifest::$data['data']['files'][$file_path];
|
||||||
|
|
||||||
|
return $metadata['abstract'] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the full inheritance lineage (ancestry) of a PHP class
|
||||||
|
*
|
||||||
|
* Returns an array of parent class names from immediate parent to top-level ancestor.
|
||||||
|
* Example: For class C extends B extends A, returns ['B', 'A']
|
||||||
|
*
|
||||||
|
* @param string $class_name The class name (FQCN or simple name)
|
||||||
|
* @return array Array of parent class simple names in order from immediate parent to root
|
||||||
|
*/
|
||||||
|
public static function php_get_lineage(string $class_name): array
|
||||||
|
{
|
||||||
|
// Ensure manifest is loaded
|
||||||
|
Manifest::init();
|
||||||
|
|
||||||
|
// Strip namespace if FQCN was passed
|
||||||
|
if (strpos($class_name, '\\') !== false) {
|
||||||
|
$parts = explode('\\', $class_name);
|
||||||
|
$class_name = end($parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
$lineage = [];
|
||||||
|
$current_class = $class_name;
|
||||||
|
$visited = []; // Prevent infinite loops
|
||||||
|
|
||||||
|
while ($current_class) {
|
||||||
|
// Prevent infinite loops in circular inheritance
|
||||||
|
if (in_array($current_class, $visited)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$visited[] = $current_class;
|
||||||
|
|
||||||
|
// Find current class in manifest
|
||||||
|
if (!isset(Manifest::$data['data']['php_classes'][$current_class])) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$file_path = Manifest::$data['data']['php_classes'][$current_class];
|
||||||
|
$metadata = Manifest::$data['data']['files'][$file_path];
|
||||||
|
|
||||||
|
if (!$metadata) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$extends = $metadata['extends'] ?? null;
|
||||||
|
|
||||||
|
if (!$extends) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add parent to lineage (simple name)
|
||||||
|
$lineage[] = $extends;
|
||||||
|
|
||||||
|
// Move up the chain
|
||||||
|
$current_class = $extends;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $lineage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all direct subclasses of a given PHP class using the pre-built index
|
||||||
|
*
|
||||||
|
* @param string $class_name The parent class name (simple name, not FQCN)
|
||||||
|
* @param bool $concrete_only Whether to filter out abstract classes (default: true)
|
||||||
|
* @return array Array of subclass names, or empty array if class not found or has no children
|
||||||
|
*/
|
||||||
|
public static function php_get_subclasses_of(string $class_name, bool $concrete_only = true): array
|
||||||
|
{
|
||||||
|
// Strip namespace if FQCN was passed
|
||||||
|
if (strpos($class_name, '\\') !== false) {
|
||||||
|
$parts = explode('\\', $class_name);
|
||||||
|
$class_name = end($parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return empty array if class not in subclass_index
|
||||||
|
if (!isset(Manifest::$data['data']['php_subclass_index'][$class_name])) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$subclasses = Manifest::$data['data']['php_subclass_index'][$class_name];
|
||||||
|
|
||||||
|
// If not filtering for concrete classes, return all subclasses
|
||||||
|
if (!$concrete_only) {
|
||||||
|
return $subclasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out abstract classes
|
||||||
|
$concrete_subclasses = [];
|
||||||
|
foreach ($subclasses as $subclass) {
|
||||||
|
// Get file path and metadata
|
||||||
|
if (!isset(Manifest::$data['data']['php_classes'][$subclass])) {
|
||||||
|
shouldnt_happen(
|
||||||
|
"Fatal: PHP class '{$subclass}' found in subclass index but not in php_classes.\n" .
|
||||||
|
"This indicates a major data integrity issue with the manifest.\n" .
|
||||||
|
'Try running: php artisan rsx:manifest:build --clean'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$file_path = Manifest::$data['data']['php_classes'][$subclass];
|
||||||
|
$metadata = Manifest::$data['data']['files'][$file_path];
|
||||||
|
|
||||||
|
// Check if abstract property exists in manifest data
|
||||||
|
if (!isset($metadata['abstract'])) {
|
||||||
|
shouldnt_happen(
|
||||||
|
"Fatal: Abstract property missing for PHP class '{$subclass}' in manifest data.\n" .
|
||||||
|
"This indicates a major data integrity issue with the manifest.\n" .
|
||||||
|
'Try running: php artisan rsx:manifest:build --clean'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include only non-abstract classes
|
||||||
|
if (!$metadata['abstract']) {
|
||||||
|
$concrete_subclasses[] = $subclass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $concrete_subclasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a class name corresponds to a PHP model class (exists in models index)
|
||||||
|
*
|
||||||
|
* This is used by the JS model system to recognize PHP model class names that may
|
||||||
|
* appear in JS inheritance chains but don't exist as JS classes in the manifest.
|
||||||
|
* PHP models like "Project_Model" generate JS stubs during bundle compilation.
|
||||||
|
*
|
||||||
|
* @param string $class_name The class name to check
|
||||||
|
* @return bool True if this is a PHP model class name
|
||||||
|
*/
|
||||||
|
public static function is_php_model_class(string $class_name): bool
|
||||||
|
{
|
||||||
|
return isset(Manifest::$data['data']['models'][$class_name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize class name to simple name (strip namespace qualifiers)
|
||||||
|
*
|
||||||
|
* Since RSX enforces unique simple class names across the codebase,
|
||||||
|
* we normalize all class references to simple names for consistent
|
||||||
|
* comparison and storage. FQCNs are only needed at actual class loading time.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* \Rsx\Lib\DataGrid → DataGrid
|
||||||
|
* Rsx\Lib\DataGrid → DataGrid
|
||||||
|
* DataGrid → DataGrid
|
||||||
|
*
|
||||||
|
* @param string $class_name Class name in any format (with or without namespace)
|
||||||
|
* @return string Simple class name without namespace
|
||||||
|
*/
|
||||||
|
public static function _normalize_class_name(string $class_name): string
|
||||||
|
{
|
||||||
|
// Strip leading backslash
|
||||||
|
$class_name = ltrim($class_name, '\\');
|
||||||
|
|
||||||
|
// Extract just the class name (last part after final backslash)
|
||||||
|
$parts = explode('\\', $class_name);
|
||||||
|
|
||||||
|
return end($parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a class and all its parent classes from manifest data
|
||||||
|
*
|
||||||
|
* This utility method ensures a class and its entire parent hierarchy
|
||||||
|
* are loaded before doing reflection or other operations.
|
||||||
|
* Classes are loaded in dependency order (parents first).
|
||||||
|
* Used by stub generators and reflection extraction.
|
||||||
|
*
|
||||||
|
* @param string $fqcn Fully qualified class name to load
|
||||||
|
* @param array $manifest_data The manifest data array
|
||||||
|
* @return void
|
||||||
|
* @throws \RuntimeException if class or parent cannot be loaded
|
||||||
|
*/
|
||||||
|
public static function _load_class_hierarchy(string $fqcn, array $manifest_data): void
|
||||||
|
{
|
||||||
|
// Already loaded? Nothing to do
|
||||||
|
if (class_exists($fqcn, false) || interface_exists($fqcn, false) || trait_exists($fqcn, false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build list of classes to load in hierarchy order
|
||||||
|
$hierarchy = [];
|
||||||
|
$current_fqcn = $fqcn;
|
||||||
|
|
||||||
|
while ($current_fqcn) {
|
||||||
|
// Find this class in the manifest
|
||||||
|
$found = false;
|
||||||
|
foreach ($manifest_data['data']['files'] as $file_path => $metadata) {
|
||||||
|
if (isset($metadata['fqcn']) && $metadata['fqcn'] === $current_fqcn) {
|
||||||
|
// Add to front of hierarchy (parents first)
|
||||||
|
array_unshift($hierarchy, [
|
||||||
|
'fqcn' => $current_fqcn,
|
||||||
|
'file' => $file_path,
|
||||||
|
'extends' => $metadata['extends'] ?? null,
|
||||||
|
]);
|
||||||
|
$found = true;
|
||||||
|
|
||||||
|
// Move to parent class
|
||||||
|
// extends is always stored as simple class name (normalized by parser)
|
||||||
|
if (isset($metadata['extends'])) {
|
||||||
|
$parent_simple_name = Manifest::_normalize_class_name($metadata['extends']);
|
||||||
|
|
||||||
|
// Look for this class by simple name in manifest
|
||||||
|
// Only check PHP files (those with fqcn key)
|
||||||
|
$parent_fqcn = null;
|
||||||
|
foreach ($manifest_data['data']['files'] as $parent_file => $parent_meta) {
|
||||||
|
if (isset($parent_meta['class']) && $parent_meta['class'] === $parent_simple_name && isset($parent_meta['fqcn'])) {
|
||||||
|
$parent_fqcn = $parent_meta['fqcn'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$current_fqcn = $parent_fqcn;
|
||||||
|
} else {
|
||||||
|
$current_fqcn = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$found && $current_fqcn) {
|
||||||
|
// Check if it's a built-in or framework class that can be autoloaded
|
||||||
|
// Try to autoload it
|
||||||
|
if (class_exists($current_fqcn, true) ||
|
||||||
|
interface_exists($current_fqcn, true) ||
|
||||||
|
trait_exists($current_fqcn, true)) {
|
||||||
|
// Framework or built-in class, stop here
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If still not found, it's a fatal error
|
||||||
|
shouldnt_happen("Parent class {$current_fqcn} not found in manifest or autoloader for {$fqcn}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load classes in order (parents first)
|
||||||
|
foreach ($hierarchy as $class_info) {
|
||||||
|
if (!class_exists($class_info['fqcn'], false) &&
|
||||||
|
!interface_exists($class_info['fqcn'], false) &&
|
||||||
|
!trait_exists($class_info['fqcn'], false)) {
|
||||||
|
$full_path = base_path($class_info['file']);
|
||||||
|
if (!file_exists($full_path)) {
|
||||||
|
shouldnt_happen("Class file not found: {$full_path} for {$class_info['fqcn']}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// This includes the file.
|
||||||
|
// A side effect of this include is this line also lints the file. Past this point, we can assume all php
|
||||||
|
// files (well, class files) have valid syntax.
|
||||||
|
include_once $full_path;
|
||||||
|
|
||||||
|
// Verify the class loaded successfully
|
||||||
|
if (!class_exists($class_info['fqcn'], false) &&
|
||||||
|
!interface_exists($class_info['fqcn'], false) &&
|
||||||
|
!trait_exists($class_info['fqcn'], false)) {
|
||||||
|
shouldnt_happen("Failed to load class {$class_info['fqcn']} from {$full_path}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
423
app/RSpade/Core/Manifest/_Manifest_Quality_Helper.php
Executable file
423
app/RSpade/Core/Manifest/_Manifest_Quality_Helper.php
Executable file
@@ -0,0 +1,423 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\RSpade\Core\Manifest;
|
||||||
|
|
||||||
|
use App\RSpade\Core\Manifest\Manifest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _Manifest_Quality_Helper - Code quality and IDE support
|
||||||
|
*
|
||||||
|
* This helper class contains function implementations for Manifest.
|
||||||
|
* Functions in this class are called via delegation from Manifest.php.
|
||||||
|
*
|
||||||
|
* @internal Do not use directly - use Manifest:: methods instead.
|
||||||
|
*/
|
||||||
|
class _Manifest_Quality_Helper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Process code quality metadata extraction for changed files
|
||||||
|
* This runs during Phase 3.5, after PHP classes are loaded but before reflection
|
||||||
|
*/
|
||||||
|
public static function _process_code_quality_metadata(array $changed_files): void
|
||||||
|
{
|
||||||
|
// Only run in development mode
|
||||||
|
if (env('APP_ENV') === 'production') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover rules with on_manifest_file_update method
|
||||||
|
$rules_with_update = [];
|
||||||
|
$rule_files = glob(base_path('app/RSpade/CodeQuality/Rules/**/*Rule.php'));
|
||||||
|
|
||||||
|
foreach ($rule_files as $rule_file) {
|
||||||
|
$rule_class = null;
|
||||||
|
$content = file_get_contents($rule_file);
|
||||||
|
|
||||||
|
// Extract class name from file
|
||||||
|
if (preg_match('/class\s+(\w+)\s+extends/', $content, $matches)) {
|
||||||
|
$class_name = $matches[1];
|
||||||
|
|
||||||
|
// Extract namespace
|
||||||
|
if (preg_match('/namespace\s+([^;]+);/', $content, $ns_matches)) {
|
||||||
|
$namespace = $ns_matches[1];
|
||||||
|
$rule_class = $namespace . '\\' . $class_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($rule_class && class_exists($rule_class)) {
|
||||||
|
// Check if it has the on_manifest_file_update method
|
||||||
|
if (method_exists($rule_class, 'on_manifest_file_update')) {
|
||||||
|
$rule_instance = new $rule_class(new \App\RSpade\CodeQuality\Support\ViolationCollector());
|
||||||
|
|
||||||
|
// Check if it's a manifest-time rule
|
||||||
|
if ($rule_instance->is_called_during_manifest_scan()) {
|
||||||
|
$rules_with_update[] = $rule_instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each changed file with each rule
|
||||||
|
foreach ($changed_files as $file) {
|
||||||
|
$absolute_path = base_path($file);
|
||||||
|
|
||||||
|
// Skip if file doesn't exist
|
||||||
|
if (!file_exists($absolute_path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize code_quality_metadata if not present
|
||||||
|
if (!isset(Manifest::$data['data']['files'][$file]['code_quality_metadata'])) {
|
||||||
|
Manifest::$data['data']['files'][$file]['code_quality_metadata'] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process with each rule that has on_manifest_file_update
|
||||||
|
foreach ($rules_with_update as $rule) {
|
||||||
|
try {
|
||||||
|
$metadata = $rule->on_manifest_file_update(
|
||||||
|
$absolute_path,
|
||||||
|
file_get_contents($absolute_path),
|
||||||
|
Manifest::$data['data']['files'][$file] ?? []
|
||||||
|
);
|
||||||
|
|
||||||
|
// Store metadata if returned
|
||||||
|
if ($metadata !== null) {
|
||||||
|
$rule_id = $rule->get_id();
|
||||||
|
Manifest::$data['data']['files'][$file]['code_quality_metadata'][$rule_id] = $metadata;
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
// If the rule throws during metadata extraction, it's a violation
|
||||||
|
// Mark manifest as bad and throw the error
|
||||||
|
Manifest::_set_manifest_is_bad();
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run code quality checks during manifest scan
|
||||||
|
* Only runs in development mode after manifest changes
|
||||||
|
* Throws fatal exception on first violation found
|
||||||
|
*
|
||||||
|
* Supports two types of rules:
|
||||||
|
* - Incremental rules (is_incremental() = true): Only check changed files
|
||||||
|
* - Cross-file rules (is_incremental() = false): Run once with full manifest context
|
||||||
|
*
|
||||||
|
* @param array $changed_files Files that changed in this manifest scan
|
||||||
|
*/
|
||||||
|
public static function _run_manifest_time_code_quality_checks(array $changed_files = []): void
|
||||||
|
{
|
||||||
|
// Create a minimal violation collector
|
||||||
|
$collector = new \App\RSpade\CodeQuality\Support\ViolationCollector();
|
||||||
|
|
||||||
|
// Discover rules that should run during manifest scan
|
||||||
|
// This uses direct file scanning, not the manifest, to avoid pollution
|
||||||
|
$rules = \App\RSpade\CodeQuality\Support\RuleDiscovery::discover_rules(
|
||||||
|
$collector,
|
||||||
|
[],
|
||||||
|
true // Only get rules with is_called_during_manifest_scan() = true
|
||||||
|
);
|
||||||
|
|
||||||
|
// Separate rules by type
|
||||||
|
$incremental_rules = [];
|
||||||
|
$cross_file_rules = [];
|
||||||
|
|
||||||
|
foreach ($rules as $rule) {
|
||||||
|
if ($rule->is_incremental()) {
|
||||||
|
$incremental_rules[] = $rule;
|
||||||
|
} else {
|
||||||
|
$cross_file_rules[] = $rule;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to throw violation exception
|
||||||
|
$throw_violation = function ($collector) {
|
||||||
|
$violations = $collector->get_all();
|
||||||
|
if (!empty($violations)) {
|
||||||
|
$first_violation = $violations[0];
|
||||||
|
$data = $first_violation->to_array();
|
||||||
|
|
||||||
|
Manifest::_set_manifest_is_bad();
|
||||||
|
|
||||||
|
$relative_file = str_replace(base_path() . '/', '', $data['file']);
|
||||||
|
|
||||||
|
$error_message = "Code Quality Violation ({$data['type']}) - {$data['message']}\n\n";
|
||||||
|
$error_message .= "File: {$relative_file}:{$data['line']}\n\n";
|
||||||
|
|
||||||
|
if (!empty($data['code'])) {
|
||||||
|
$error_message .= "Code:\n " . $data['code'] . "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($data['resolution'])) {
|
||||||
|
$error_message .= "Resolution:\n" . $data['resolution'];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \App\RSpade\CodeQuality\RuntimeChecks\YoureDoingItWrongException(
|
||||||
|
$error_message,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
$data['file'],
|
||||||
|
$data['line']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper to check if file matches rule patterns
|
||||||
|
$file_matches_rule = function ($file_path, $rule) {
|
||||||
|
$patterns = $rule->get_file_patterns();
|
||||||
|
foreach ($patterns as $pattern) {
|
||||||
|
$pattern = str_replace('*', '.*', $pattern);
|
||||||
|
if (preg_match('/^' . $pattern . '$/i', basename($file_path))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// INCREMENTAL RULES: Only check changed files
|
||||||
|
// =========================================================
|
||||||
|
// Determine which files to check - changed files only for incremental rebuild,
|
||||||
|
// or all files if this is a full rebuild (empty changed_files means check all)
|
||||||
|
$files_to_check = !empty($changed_files)
|
||||||
|
? array_flip($changed_files) // Use as lookup for O(1) checks
|
||||||
|
: null; // null = check all files
|
||||||
|
|
||||||
|
foreach ($incremental_rules as $rule) {
|
||||||
|
foreach (Manifest::$data['data']['files'] as $file_path => $metadata) {
|
||||||
|
// Skip files that haven't changed (for incremental rebuilds)
|
||||||
|
if ($files_to_check !== null && !isset($files_to_check[$file_path])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$file_matches_rule($file_path, $rule)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$absolute_path = base_path($file_path);
|
||||||
|
if (!file_exists($absolute_path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contents = file_get_contents($absolute_path);
|
||||||
|
|
||||||
|
$enhanced_metadata = $metadata;
|
||||||
|
if (isset(Manifest::$data['data']['files'][$file_path]['code_quality_metadata'][$rule->get_id()])) {
|
||||||
|
$enhanced_metadata['rule_metadata'] = Manifest::$data['data']['files'][$file_path]['code_quality_metadata'][$rule->get_id()];
|
||||||
|
}
|
||||||
|
|
||||||
|
$rule->check($absolute_path, $contents, $enhanced_metadata);
|
||||||
|
$throw_violation($collector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// CROSS-FILE RULES: Run once with full manifest context
|
||||||
|
// =========================================================
|
||||||
|
// These rules internally access Manifest::get_all() to check all files
|
||||||
|
// We just need to trigger them once with any matching file
|
||||||
|
foreach ($cross_file_rules as $rule) {
|
||||||
|
// Find first file matching the rule's patterns to trigger the check
|
||||||
|
foreach (Manifest::$data['data']['files'] as $file_path => $metadata) {
|
||||||
|
if (!$file_matches_rule($file_path, $rule)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$absolute_path = base_path($file_path);
|
||||||
|
if (!file_exists($absolute_path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contents = file_get_contents($absolute_path);
|
||||||
|
|
||||||
|
$enhanced_metadata = $metadata;
|
||||||
|
if (isset(Manifest::$data['data']['files'][$file_path]['code_quality_metadata'][$rule->get_id()])) {
|
||||||
|
$enhanced_metadata['rule_metadata'] = Manifest::$data['data']['files'][$file_path]['code_quality_metadata'][$rule->get_id()];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cross-file rules run once, they handle iteration internally
|
||||||
|
$rule->check($absolute_path, $contents, $enhanced_metadata);
|
||||||
|
$throw_violation($collector);
|
||||||
|
|
||||||
|
// Only trigger once per cross-file rule
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for duplicate base class names within the same file type
|
||||||
|
*
|
||||||
|
* This method handles two scenarios:
|
||||||
|
* 1. RESTORE: If a .upstream file exists but no active override exists, restore it
|
||||||
|
* 2. OVERRIDE: When a class exists in both rsx/ and app/RSpade/, rename framework to .upstream
|
||||||
|
*
|
||||||
|
* Throws a fatal error if duplicates exist within the same area (both rsx/ or both app/RSpade/)
|
||||||
|
*/
|
||||||
|
public static function _check_unique_base_class_names(): void
|
||||||
|
{
|
||||||
|
// ==================================================================================
|
||||||
|
// STEP 1: Restore orphaned .upstream files
|
||||||
|
// ==================================================================================
|
||||||
|
// Check all php.upstream files - if their class no longer has an override in rsx/,
|
||||||
|
// restore the framework file by renaming .upstream back to .php
|
||||||
|
// ==================================================================================
|
||||||
|
Manifest::_restore_orphaned_upstream_files();
|
||||||
|
|
||||||
|
// ==================================================================================
|
||||||
|
// STEP 2: Group classes by extension, then by class name
|
||||||
|
// ==================================================================================
|
||||||
|
$classes_by_extension = [];
|
||||||
|
|
||||||
|
foreach (Manifest::$data['data']['files'] as $file => $metadata) {
|
||||||
|
if (isset($metadata['class']) && !empty($metadata['class'])) {
|
||||||
|
$base_class_name = $metadata['class'];
|
||||||
|
$extension = $metadata['extension'] ?? '';
|
||||||
|
|
||||||
|
// Skip php.upstream files - they're tracked separately for restoration
|
||||||
|
if ($extension === 'php.upstream') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group JavaScript-like files together
|
||||||
|
if (in_array($extension, ['js', 'jsx', 'ts', 'tsx'])) {
|
||||||
|
$extension = 'js';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($classes_by_extension[$extension])) {
|
||||||
|
$classes_by_extension[$extension] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($classes_by_extension[$extension][$base_class_name])) {
|
||||||
|
$classes_by_extension[$extension][$base_class_name] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$classes_by_extension[$extension][$base_class_name][] = $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================================================================================
|
||||||
|
// STEP 3: Check for duplicates and create overrides
|
||||||
|
// ==================================================================================
|
||||||
|
foreach ($classes_by_extension as $extension => $base_class_files) {
|
||||||
|
foreach ($base_class_files as $class_name => $files) {
|
||||||
|
if (count($files) > 1) {
|
||||||
|
// Check if this is a valid override (rsx/ vs app/RSpade/)
|
||||||
|
$rsx_files = array_filter($files, fn($f) => str_starts_with($f, 'rsx/'));
|
||||||
|
$framework_files = array_filter($files, fn($f) => str_starts_with($f, 'app/RSpade/'));
|
||||||
|
|
||||||
|
// Valid override: exactly one file in rsx/, rest in app/RSpade/
|
||||||
|
if (count($rsx_files) === 1 && count($framework_files) >= 1) {
|
||||||
|
// Rename framework files to .upstream and remove from manifest
|
||||||
|
foreach ($framework_files as $framework_file) {
|
||||||
|
$full_framework_path = base_path($framework_file);
|
||||||
|
$upstream_path = $full_framework_path . '.upstream';
|
||||||
|
|
||||||
|
if (file_exists($full_framework_path) && !file_exists($upstream_path)) {
|
||||||
|
rename($full_framework_path, $upstream_path);
|
||||||
|
console_debug('MANIFEST', "Class override: {$class_name} - moved {$framework_file} to .upstream");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from manifest data so it won't be indexed
|
||||||
|
unset(Manifest::$data['data']['files'][$framework_file]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Manifest::$_needs_manifest_restart = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a valid override - throw error
|
||||||
|
$file_type = $extension === 'php' ? 'PHP' : ($extension === 'js' ? 'JavaScript' : $extension);
|
||||||
|
|
||||||
|
throw new \RuntimeException(
|
||||||
|
"Fatal: Duplicate {$file_type} class name '{$class_name}' found in multiple files:\n" .
|
||||||
|
' - ' . implode("\n - ", $files) . "\n" .
|
||||||
|
'Each class name must be unique within the same file type.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate VS Code IDE helper stubs for attributes and class aliases
|
||||||
|
*/
|
||||||
|
public static function _generate_vscode_stubs(): void
|
||||||
|
{
|
||||||
|
// Generate to project root (parent of system/)
|
||||||
|
$project_root = dirname(base_path());
|
||||||
|
$stub_file = $project_root . '/._rsx_helper.php';
|
||||||
|
|
||||||
|
$output = "<?php\n";
|
||||||
|
$output .= "/* @noinspection ALL */\n";
|
||||||
|
$output .= "// @formatter:off\n";
|
||||||
|
$output .= "// phpcs:ignoreFile\n\n";
|
||||||
|
$output .= "/**\n";
|
||||||
|
$output .= " * RSX Framework Attribute Stubs for IDE Support\n";
|
||||||
|
$output .= " *\n";
|
||||||
|
$output .= " * AUTO-GENERATED FILE - DO NOT EDIT MANUALLY\n";
|
||||||
|
$output .= " *\n";
|
||||||
|
$output .= " * This file is automatically regenerated during manifest builds when:\n";
|
||||||
|
$output .= " * - Running `php artisan rsx:manifest:build`\n";
|
||||||
|
$output .= " * - File changes detected in development mode\n";
|
||||||
|
$output .= " * - Any PHP file with attributes is modified\n";
|
||||||
|
$output .= " *\n";
|
||||||
|
$output .= " * These are stub definitions to provide IDE autocomplete and eliminate warnings\n";
|
||||||
|
$output .= " * for RSX framework attributes. These classes are never actually loaded or used\n";
|
||||||
|
$output .= " * at runtime - they exist purely for IDE IntelliSense.\n";
|
||||||
|
$output .= " *\n";
|
||||||
|
$output .= " * This file should not be included in your code, only analyzed by your IDE!\n";
|
||||||
|
$output .= " */\n\n";
|
||||||
|
|
||||||
|
// Generate attribute stubs
|
||||||
|
$attributes = [];
|
||||||
|
|
||||||
|
foreach (Manifest::$data['data']['files'] as $file_path => $metadata) {
|
||||||
|
if (!isset($metadata['extension']) || $metadata['extension'] !== 'php') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($metadata['class'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect attributes from the class itself
|
||||||
|
if (isset($metadata['attributes'])) {
|
||||||
|
foreach ($metadata['attributes'] as $attr_name => $attr_data) {
|
||||||
|
// Extract simple attribute name (last part after backslash)
|
||||||
|
$simple_attr_name = basename(str_replace('\\', '/', $attr_name));
|
||||||
|
$attributes[$simple_attr_name] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect attributes from public static methods
|
||||||
|
if (isset($metadata['public_static_methods'])) {
|
||||||
|
foreach ($metadata['public_static_methods'] as $method_name => $method_info) {
|
||||||
|
if (isset($method_info['attributes'])) {
|
||||||
|
foreach ($method_info['attributes'] as $attr_name => $attr_data) {
|
||||||
|
// Extract simple attribute name (last part after backslash)
|
||||||
|
$simple_attr_name = basename(str_replace('\\', '/', $attr_name));
|
||||||
|
$attributes[$simple_attr_name] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($attributes)) {
|
||||||
|
$output .= "namespace {\n";
|
||||||
|
foreach (array_keys($attributes) as $attr_name) {
|
||||||
|
$output .= " #[\\Attribute(\\Attribute::TARGET_ALL | \\Attribute::IS_REPEATABLE)]\n";
|
||||||
|
$output .= " class {$attr_name} {\n";
|
||||||
|
$output .= " public function __construct(...\$args) {}\n";
|
||||||
|
$output .= " }\n\n";
|
||||||
|
}
|
||||||
|
$output .= "}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents($stub_file, $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
257
app/RSpade/Core/Manifest/_Manifest_Reflection_Helper.php
Executable file
257
app/RSpade/Core/Manifest/_Manifest_Reflection_Helper.php
Executable file
@@ -0,0 +1,257 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\RSpade\Core\Manifest;
|
||||||
|
|
||||||
|
use App\RSpade\Core\Manifest\Manifest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _Manifest_Reflection_Helper - View, attribute, and route resolution
|
||||||
|
*
|
||||||
|
* This helper class contains function implementations for Manifest.
|
||||||
|
* Functions in this class are called via delegation from Manifest.php.
|
||||||
|
*
|
||||||
|
* @internal Do not use directly - use Manifest:: methods instead.
|
||||||
|
*/
|
||||||
|
class _Manifest_Reflection_Helper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Find a view by ID
|
||||||
|
*/
|
||||||
|
public static function find_view(string $id): string
|
||||||
|
{
|
||||||
|
$files = Manifest::get_all();
|
||||||
|
$matches = [];
|
||||||
|
|
||||||
|
// Find all files with matching ID (only check Blade views)
|
||||||
|
foreach ($files as $file => $metadata) {
|
||||||
|
if (isset($metadata['id']) && $metadata['id'] === $id && str_ends_with($file, '.blade.php')) {
|
||||||
|
$matches[] = $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check results
|
||||||
|
if (count($matches) === 0) {
|
||||||
|
throw new \RuntimeException("View not found in manifest: {$id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($matches) > 1) {
|
||||||
|
$file_list = implode("\n - ", $matches);
|
||||||
|
|
||||||
|
throw new \RuntimeException(
|
||||||
|
"Duplicate view ID detected: {$id}\n" .
|
||||||
|
"Found in multiple files:\n - {$file_list}\n" .
|
||||||
|
'View IDs must be unique across all Blade files.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $matches[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a view by RSX ID (path-agnostic identifier)
|
||||||
|
*/
|
||||||
|
public static function find_view_by_rsx_id(string $id): string
|
||||||
|
{
|
||||||
|
// This method now properly checks for duplicates
|
||||||
|
return Manifest::find_view($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get path for a file by its filename only (quick and dirty lookup)
|
||||||
|
*
|
||||||
|
* This is a convenience method for finding files when you know the filename is unique.
|
||||||
|
* Only works for files in the /rsx directory. Fatal errors if:
|
||||||
|
* - File not found in manifest
|
||||||
|
* - Multiple files with the same name exist
|
||||||
|
* - File is outside /rsx directory
|
||||||
|
*
|
||||||
|
* @param string $filename Just the filename with extension (e.g., "Counter_Widget.jqhtml")
|
||||||
|
* @return string The relative path to the file (e.g., "rsx/app/demo/components/Counter_Widget.jqhtml")
|
||||||
|
* @throws RuntimeException If file not found, multiple matches, or outside /rsx
|
||||||
|
*/
|
||||||
|
public static function get_path_by_filename(string $filename): string
|
||||||
|
{
|
||||||
|
$files = Manifest::get_all();
|
||||||
|
$matches = [];
|
||||||
|
|
||||||
|
foreach ($files as $path => $metadata) {
|
||||||
|
// Only consider files in /rsx directory
|
||||||
|
if (!str_starts_with($path, 'rsx/')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract just the filename from the path
|
||||||
|
$file_basename = basename($path);
|
||||||
|
|
||||||
|
if ($file_basename === $filename) {
|
||||||
|
$matches[] = $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($matches)) {
|
||||||
|
throw new \RuntimeException(
|
||||||
|
"Fatal: File not found in manifest: {$filename}\n" .
|
||||||
|
'This method only searches files in the /rsx directory.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($matches) > 1) {
|
||||||
|
throw new \RuntimeException(
|
||||||
|
"Fatal: Multiple files with name '{$filename}' found in manifest:\n" .
|
||||||
|
' - ' . implode("\n - ", $matches) . "\n" .
|
||||||
|
'This method requires unique filenames.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $matches[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all classes with a specific attribute
|
||||||
|
*/
|
||||||
|
public static function get_with_attribute(string $attribute_class): array
|
||||||
|
{
|
||||||
|
$files = Manifest::get_all();
|
||||||
|
$results = [];
|
||||||
|
|
||||||
|
foreach ($files as $file => $metadata) {
|
||||||
|
// Check class attributes
|
||||||
|
if (isset($metadata['attributes'][$attribute_class])) {
|
||||||
|
$results[] = [
|
||||||
|
'file' => $file,
|
||||||
|
'class' => $metadata['class'] ?? null,
|
||||||
|
'fqcn' => $metadata['fqcn'] ?? null,
|
||||||
|
'type' => 'class',
|
||||||
|
'instances' => $metadata['attributes'][$attribute_class],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check public static method attributes (PHP files)
|
||||||
|
if (isset($metadata['public_static_methods'])) {
|
||||||
|
foreach ($metadata['public_static_methods'] as $method_name => $method_data) {
|
||||||
|
if (isset($method_data['attributes'][$attribute_class])) {
|
||||||
|
$results[] = [
|
||||||
|
'file' => $file,
|
||||||
|
'class' => $metadata['class'] ?? null,
|
||||||
|
'fqcn' => $metadata['fqcn'] ?? null,
|
||||||
|
'method' => $method_name,
|
||||||
|
'type' => 'method',
|
||||||
|
'instances' => $method_data['attributes'][$attribute_class],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check regular method attributes (JS files may have these)
|
||||||
|
if (isset($metadata['methods'])) {
|
||||||
|
foreach ($metadata['methods'] as $method_name => $method_data) {
|
||||||
|
if (isset($method_data['attributes'][$attribute_class])) {
|
||||||
|
$results[] = [
|
||||||
|
'file' => $file,
|
||||||
|
'class' => $metadata['class'] ?? null,
|
||||||
|
'fqcn' => $metadata['fqcn'] ?? null,
|
||||||
|
'method' => $method_name,
|
||||||
|
'type' => 'method',
|
||||||
|
'instances' => $method_data['attributes'][$attribute_class],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort alphabetically by class name to ensure deterministic behavior and prevent race condition bugs
|
||||||
|
usort($results, function ($a, $b) {
|
||||||
|
return strcmp($a['class'] ?? '', $b['class'] ?? '');
|
||||||
|
});
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all routes from the manifest
|
||||||
|
*
|
||||||
|
* Returns unified route structure: $routes[$pattern] => route_data
|
||||||
|
* where route_data contains:
|
||||||
|
* - methods: ['GET', 'POST']
|
||||||
|
* - type: 'spa' | 'standard'
|
||||||
|
* - class: Full class name
|
||||||
|
* - method: Method name
|
||||||
|
* - file: File path
|
||||||
|
* - require: Auth requirements
|
||||||
|
* - js_action_class: (SPA routes only) JavaScript action class
|
||||||
|
*/
|
||||||
|
public static function get_routes(): array
|
||||||
|
{
|
||||||
|
Manifest::init();
|
||||||
|
|
||||||
|
return Manifest::$data['data']['routes'] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Auth attributes from a controller's pre_dispatch method
|
||||||
|
*
|
||||||
|
* @param string $class_name Simple class name or FQCN
|
||||||
|
* @return array Array of Auth attribute instances
|
||||||
|
*/
|
||||||
|
public static function get_pre_dispatch_requires(string $class_name): array
|
||||||
|
{
|
||||||
|
Manifest::init();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try to get metadata by class name or FQCN
|
||||||
|
if (strpos($class_name, '\\') !== false) {
|
||||||
|
$metadata = Manifest::php_get_metadata_by_fqcn($class_name);
|
||||||
|
} else {
|
||||||
|
$metadata = Manifest::php_get_metadata_by_class($class_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if pre_dispatch method exists and has Auth attributes
|
||||||
|
if (isset($metadata['public_static_methods']['pre_dispatch']['attributes']['Auth'])) {
|
||||||
|
return $metadata['public_static_methods']['pre_dispatch']['attributes']['Auth'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
} catch (\RuntimeException $e) {
|
||||||
|
// Class not found in manifest
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if metadata represents a controller class
|
||||||
|
* @param array $metadata File metadata
|
||||||
|
* @return bool True if class extends Rsx_Controller_Abstract
|
||||||
|
*/
|
||||||
|
public static function _is_controller_class(array $metadata): bool
|
||||||
|
{
|
||||||
|
$extends = $metadata['extends'] ?? '';
|
||||||
|
|
||||||
|
if ($extends === 'Rsx_Controller_Abstract') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check parent hierarchy
|
||||||
|
$current_class = $extends;
|
||||||
|
$max_depth = 10;
|
||||||
|
|
||||||
|
while ($current_class && $max_depth-- > 0) {
|
||||||
|
try {
|
||||||
|
$parent_metadata = Manifest::php_get_metadata_by_class($current_class);
|
||||||
|
if (($parent_metadata['extends'] ?? '') === 'Rsx_Controller_Abstract') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$current_class = $parent_metadata['extends'] ?? '';
|
||||||
|
} catch (\RuntimeException $e) {
|
||||||
|
// Check FQCN match
|
||||||
|
if ($current_class === 'Rsx_Controller_Abstract' ||
|
||||||
|
$current_class === 'App\\RSpade\\Core\\Controller\\Rsx_Controller_Abstract') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
1158
app/RSpade/Core/Manifest/_Manifest_Scanner_Helper.php
Executable file
1158
app/RSpade/Core/Manifest/_Manifest_Scanner_Helper.php
Executable file
File diff suppressed because it is too large
Load Diff
182
app/RSpade/Core/Mode/Rsx_Mode.php
Executable file
182
app/RSpade/Core/Mode/Rsx_Mode.php
Executable file
@@ -0,0 +1,182 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\RSpade\Core\Mode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rsx_Mode - Centralized application mode detection
|
||||||
|
*
|
||||||
|
* RSX_MODE is the authoritative source of truth for application mode,
|
||||||
|
* taking precedence over Laravel's APP_ENV setting.
|
||||||
|
*
|
||||||
|
* Three modes:
|
||||||
|
* - development: Auto-rebuild, full debugging, sourcemaps
|
||||||
|
* - debug: Production optimizations with sourcemaps for debugging
|
||||||
|
* - production: Full optimization, minification, merging, CDN bundling
|
||||||
|
*
|
||||||
|
* @see php artisan rsx:man app_mode
|
||||||
|
*/
|
||||||
|
class Rsx_Mode
|
||||||
|
{
|
||||||
|
public const DEVELOPMENT = 'development';
|
||||||
|
public const DEBUG = 'debug';
|
||||||
|
public const PRODUCTION = 'production';
|
||||||
|
|
||||||
|
private static ?string $_cached_mode = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current application mode
|
||||||
|
*
|
||||||
|
* @return string One of: development, debug, production
|
||||||
|
*/
|
||||||
|
public static function get(): string
|
||||||
|
{
|
||||||
|
if (self::$_cached_mode !== null) {
|
||||||
|
return self::$_cached_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mode = env('RSX_MODE', self::DEVELOPMENT);
|
||||||
|
|
||||||
|
// Normalize aliases
|
||||||
|
if ($mode === 'dev') {
|
||||||
|
$mode = self::DEVELOPMENT;
|
||||||
|
} elseif ($mode === 'prod') {
|
||||||
|
$mode = self::PRODUCTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
if (!in_array($mode, [self::DEVELOPMENT, self::DEBUG, self::PRODUCTION], true)) {
|
||||||
|
throw new \RuntimeException(
|
||||||
|
"Invalid RSX_MODE '{$mode}'. Must be: development, debug, or production"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$_cached_mode = $mode;
|
||||||
|
|
||||||
|
return $mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if running in development mode
|
||||||
|
*/
|
||||||
|
public static function is_development(): bool
|
||||||
|
{
|
||||||
|
return self::get() === self::DEVELOPMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if running in debug mode
|
||||||
|
*/
|
||||||
|
public static function is_debug(): bool
|
||||||
|
{
|
||||||
|
return self::get() === self::DEBUG;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if running in production mode
|
||||||
|
*/
|
||||||
|
public static function is_production(): bool
|
||||||
|
{
|
||||||
|
return self::get() === self::PRODUCTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if running in a production-like mode (debug or production)
|
||||||
|
*
|
||||||
|
* Use this for checks that apply to both debug and production,
|
||||||
|
* such as requiring pre-built assets.
|
||||||
|
*/
|
||||||
|
public static function is_production_like(): bool
|
||||||
|
{
|
||||||
|
return self::get() !== self::DEVELOPMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if manifest/bundles should auto-rebuild on file changes
|
||||||
|
*
|
||||||
|
* Only in development mode.
|
||||||
|
*/
|
||||||
|
public static function should_auto_rebuild(): bool
|
||||||
|
{
|
||||||
|
return self::is_development();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if JS/CSS should be minified
|
||||||
|
*
|
||||||
|
* In debug and production modes.
|
||||||
|
*/
|
||||||
|
public static function should_minify(): bool
|
||||||
|
{
|
||||||
|
return self::is_production_like();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if JS/CSS should be merged into single files
|
||||||
|
*
|
||||||
|
* Only in production mode (not debug - keeps files separate for debugging).
|
||||||
|
*/
|
||||||
|
public static function should_merge_bundles(): bool
|
||||||
|
{
|
||||||
|
return self::is_production();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if inline sourcemaps should be included
|
||||||
|
*
|
||||||
|
* In development and debug modes.
|
||||||
|
*/
|
||||||
|
public static function should_inline_sourcemaps(): bool
|
||||||
|
{
|
||||||
|
return !self::is_production();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if CDN assets should be cached locally and bundled
|
||||||
|
*
|
||||||
|
* Only in production mode.
|
||||||
|
*/
|
||||||
|
public static function should_cache_cdn(): bool
|
||||||
|
{
|
||||||
|
return self::is_production();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if console_debug() calls should be stripped from source
|
||||||
|
*
|
||||||
|
* Only in production mode.
|
||||||
|
*/
|
||||||
|
public static function should_strip_console_debug(): bool
|
||||||
|
{
|
||||||
|
return self::is_production();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if debug info should be included in window.rsxapp
|
||||||
|
*
|
||||||
|
* Only in development mode.
|
||||||
|
*/
|
||||||
|
public static function should_include_debug_info(): bool
|
||||||
|
{
|
||||||
|
return self::is_development();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the cached mode (for use after .env changes)
|
||||||
|
*/
|
||||||
|
public static function clear_cache(): void
|
||||||
|
{
|
||||||
|
self::$_cached_mode = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get human-readable mode label
|
||||||
|
*/
|
||||||
|
public static function get_label(): string
|
||||||
|
{
|
||||||
|
return match (self::get()) {
|
||||||
|
self::DEVELOPMENT => 'Development',
|
||||||
|
self::DEBUG => 'Debug',
|
||||||
|
self::PRODUCTION => 'Production',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -364,7 +364,7 @@ class Spa {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { path, search, query_params };
|
return { path, search, query_params, hash: parsed_url.hash };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
257
app/RSpade/man/app_mode.txt
Executable file
257
app/RSpade/man/app_mode.txt
Executable file
@@ -0,0 +1,257 @@
|
|||||||
|
APP_MODE(3) RSX Framework Manual APP_MODE(3)
|
||||||
|
|
||||||
|
NAME
|
||||||
|
app_mode - Application execution modes and production build system
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
Environment:
|
||||||
|
RSX_MODE=development|debug|production
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
php artisan rsx:mode:set <mode>
|
||||||
|
php artisan rsx:prod:build
|
||||||
|
php artisan rsx:prod:export [--path=./rsx-export]
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
RSpade supports three execution modes that control build behavior,
|
||||||
|
optimization level, and debugging capabilities. RSX_MODE is the
|
||||||
|
authoritative source of truth for application mode, taking precedence
|
||||||
|
over Laravel's APP_ENV setting.
|
||||||
|
|
||||||
|
The mode system enables a single codebase to run efficiently in
|
||||||
|
development (with automatic rebuilds and debugging tools) and
|
||||||
|
production (with pre-built optimized assets and caching).
|
||||||
|
|
||||||
|
MODES
|
||||||
|
development
|
||||||
|
Default mode for local development.
|
||||||
|
|
||||||
|
- Manifest rebuilds automatically on file changes
|
||||||
|
- Bundles compile JIT on web request
|
||||||
|
- Source maps included inline
|
||||||
|
- CDN assets loaded directly from remote URLs
|
||||||
|
- console_debug() output appears in browser console
|
||||||
|
- Debug info included in window.rsxapp
|
||||||
|
- Laravel caches disabled (config, routes, views)
|
||||||
|
- File change detection active
|
||||||
|
|
||||||
|
debug
|
||||||
|
Production-like mode with debugging aids for troubleshooting
|
||||||
|
production-specific issues.
|
||||||
|
|
||||||
|
- All production optimizations applied
|
||||||
|
- Source maps included inline (for debugging minified code)
|
||||||
|
- CDN assets loaded directly from remote URLs
|
||||||
|
- console_debug() calls remain in source (not stripped)
|
||||||
|
- Debug info excluded from window.rsxapp
|
||||||
|
- Requires pre-built assets (no automatic rebuilds)
|
||||||
|
- Errors if build artifacts missing
|
||||||
|
|
||||||
|
production
|
||||||
|
Full production mode for deployed applications.
|
||||||
|
|
||||||
|
- All production optimizations applied
|
||||||
|
- No source maps
|
||||||
|
- CDN assets cached locally and compiled into bundles
|
||||||
|
- console_debug() calls stripped from source via Babel
|
||||||
|
- Debug info excluded from window.rsxapp
|
||||||
|
- JS/CSS minified
|
||||||
|
- All JS merged into single file per bundle
|
||||||
|
- All CSS merged into single file per bundle
|
||||||
|
- Laravel caches enabled (config, routes, views)
|
||||||
|
- Requires pre-built assets (no automatic rebuilds)
|
||||||
|
- Errors if build artifacts missing
|
||||||
|
|
||||||
|
MODE COMPARISON
|
||||||
|
Feature development debug production
|
||||||
|
------- ----------- ----- ----------
|
||||||
|
Auto manifest rebuild Yes No No
|
||||||
|
Auto bundle compile Yes No No
|
||||||
|
Inline source maps Yes Yes No
|
||||||
|
Minified output No Yes Yes
|
||||||
|
Merged JS/CSS files No No Yes
|
||||||
|
CDN assets bundled No No Yes
|
||||||
|
console_debug() in source Yes Yes No (stripped)
|
||||||
|
Debug in window.rsxapp Yes No No
|
||||||
|
Laravel caches No Yes Yes
|
||||||
|
Error if artifacts missing No Yes Yes
|
||||||
|
|
||||||
|
COMMANDS
|
||||||
|
rsx:mode:set <mode>
|
||||||
|
Change the application mode. Accepts: dev, development, debug,
|
||||||
|
prod, production.
|
||||||
|
|
||||||
|
This command:
|
||||||
|
1. Updates RSX_MODE in .env file
|
||||||
|
2. Clears all build artifacts (rsx:clean)
|
||||||
|
3. For development: Runs rsx:bundle:compile --all
|
||||||
|
4. For debug/production: Runs rsx:prod:build
|
||||||
|
|
||||||
|
Example:
|
||||||
|
php artisan rsx:mode:set production
|
||||||
|
php artisan rsx:mode:set dev
|
||||||
|
|
||||||
|
rsx:prod:build
|
||||||
|
Build all production assets. Required before running in debug
|
||||||
|
or production mode.
|
||||||
|
|
||||||
|
This command:
|
||||||
|
1. Rebuilds manifest with production settings
|
||||||
|
2. Compiles all bundles with minification
|
||||||
|
3. For production mode only:
|
||||||
|
- Fetches and caches all CDN assets
|
||||||
|
- Merges JS files into single bundle file
|
||||||
|
- Merges CSS files into single bundle file
|
||||||
|
- Strips console_debug() calls via Babel
|
||||||
|
4. Runs Laravel cache commands:
|
||||||
|
- php artisan config:cache
|
||||||
|
- php artisan route:cache
|
||||||
|
- php artisan view:cache
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--force Force rebuild even if artifacts exist
|
||||||
|
|
||||||
|
rsx:prod:export [--path=PATH]
|
||||||
|
Export the application for deployment. Runs rsx:prod:build
|
||||||
|
first, then copies all necessary files to the export directory.
|
||||||
|
|
||||||
|
Default path: ./rsx-export
|
||||||
|
|
||||||
|
Included in export:
|
||||||
|
/system/ Framework code
|
||||||
|
/rsx/ Application code
|
||||||
|
/rsx-build/ Compiled assets
|
||||||
|
/node_modules/ Node dependencies
|
||||||
|
/vendor/ Composer dependencies
|
||||||
|
/*.json Root JSON files (package.json, etc.)
|
||||||
|
|
||||||
|
Excluded from export:
|
||||||
|
.env Security - must be configured per environment
|
||||||
|
/storage/ User uploads and application storage
|
||||||
|
/rsx-tmp/ Temporary build caches
|
||||||
|
/rsx-export/ Previous exports
|
||||||
|
|
||||||
|
After export, the destination can be deployed via CI/CD to
|
||||||
|
production servers. The destination server must have its own
|
||||||
|
.env file configured.
|
||||||
|
|
||||||
|
CDN ASSET HANDLING
|
||||||
|
In development and debug modes, CDN assets (Bootstrap, jQuery, etc.)
|
||||||
|
are loaded directly from their remote URLs as defined in bundles.
|
||||||
|
|
||||||
|
In production mode, CDN assets are:
|
||||||
|
1. Fetched via curl during rsx:prod:build
|
||||||
|
2. Cached locally in /rsx-tmp/cdn_cache/ using URL-derived filenames
|
||||||
|
3. Compiled directly into the merged bundle output
|
||||||
|
|
||||||
|
Cache filenames are generated by hashing the full CDN URL, ensuring
|
||||||
|
that URL changes (e.g., version upgrades) automatically trigger
|
||||||
|
re-fetching without manual cache invalidation.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
URL: https://cdn.example.com/lib@1.0.0/lib.min.js
|
||||||
|
Cache: /rsx-tmp/cdn_cache/a3f8b2c1d4e5.js (hash of URL)
|
||||||
|
|
||||||
|
When the bundle definition changes to @1.1.0, a new cache file is
|
||||||
|
created and the old version is no longer referenced.
|
||||||
|
|
||||||
|
BABEL TRANSFORMATIONS
|
||||||
|
Production builds use the rspade_prod Babel plugin to perform
|
||||||
|
code transformations:
|
||||||
|
|
||||||
|
console_debug() removal
|
||||||
|
All console_debug() function calls are stripped from the
|
||||||
|
source code in production mode. This includes:
|
||||||
|
- console_debug('message')
|
||||||
|
- console_debug('tag', data)
|
||||||
|
- console_debug() with any arguments
|
||||||
|
|
||||||
|
The calls are completely removed, not replaced with no-ops,
|
||||||
|
resulting in smaller bundle sizes.
|
||||||
|
|
||||||
|
Future transformations may be added to this plugin as needed.
|
||||||
|
|
||||||
|
WINDOW.RSXAPP IN PRODUCTION
|
||||||
|
In debug and production modes, the following are excluded from
|
||||||
|
window.rsxapp output:
|
||||||
|
|
||||||
|
- console_debug configuration
|
||||||
|
- ajax_disable_batching flag
|
||||||
|
- Other debug-only properties
|
||||||
|
|
||||||
|
Core functionality (user, site, csrf, params, etc.) remains
|
||||||
|
available for application code.
|
||||||
|
|
||||||
|
DEPLOYMENT WORKFLOW
|
||||||
|
Typical deployment process:
|
||||||
|
|
||||||
|
1. Development
|
||||||
|
RSX_MODE=development (default)
|
||||||
|
- Code changes reflect immediately
|
||||||
|
- Full debugging capabilities
|
||||||
|
|
||||||
|
2. Pre-deployment testing
|
||||||
|
php artisan rsx:mode:set debug
|
||||||
|
- Test with production optimizations
|
||||||
|
- Source maps available for debugging
|
||||||
|
|
||||||
|
3. Build for production
|
||||||
|
php artisan rsx:mode:set production
|
||||||
|
- Or: php artisan rsx:prod:build
|
||||||
|
|
||||||
|
4. Export for deployment
|
||||||
|
php artisan rsx:prod:export
|
||||||
|
- Creates ./rsx-export/ with all necessary files
|
||||||
|
|
||||||
|
5. Deploy
|
||||||
|
- Copy rsx-export/ to production server
|
||||||
|
- Configure .env on production server
|
||||||
|
- Point web server to deployment
|
||||||
|
|
||||||
|
6. Return to development
|
||||||
|
php artisan rsx:mode:set development
|
||||||
|
- Clears production artifacts
|
||||||
|
- Re-enables automatic rebuilding
|
||||||
|
|
||||||
|
CONFIGURATION
|
||||||
|
.env file:
|
||||||
|
RSX_MODE=development # development|debug|production
|
||||||
|
|
||||||
|
The RSX_MODE value is read by the framework at boot time and
|
||||||
|
determines all build and runtime behavior. Laravel's APP_ENV
|
||||||
|
and APP_DEBUG settings are respected for their own purposes
|
||||||
|
but RSX_MODE is authoritative for RSpade-specific behavior.
|
||||||
|
|
||||||
|
ERROR HANDLING
|
||||||
|
In debug and production modes, if required build artifacts are
|
||||||
|
missing, the framework throws an error rather than attempting
|
||||||
|
to build them automatically.
|
||||||
|
|
||||||
|
Common errors:
|
||||||
|
|
||||||
|
"Manifest not built for production mode"
|
||||||
|
Run: php artisan rsx:prod:build
|
||||||
|
|
||||||
|
"Bundle 'X' not compiled for production mode"
|
||||||
|
Run: php artisan rsx:prod:build
|
||||||
|
|
||||||
|
"CDN asset not cached: [url]"
|
||||||
|
Run: php artisan rsx:prod:build --force
|
||||||
|
|
||||||
|
These errors prevent accidental performance degradation from
|
||||||
|
JIT compilation in production environments.
|
||||||
|
|
||||||
|
GITIGNORE
|
||||||
|
Add to .gitignore:
|
||||||
|
/rsx-export/
|
||||||
|
|
||||||
|
The export directory should not be committed to version control.
|
||||||
|
Each deployment should generate a fresh export.
|
||||||
|
|
||||||
|
SEE ALSO
|
||||||
|
bundle_api(3), manifest(3)
|
||||||
|
|
||||||
|
AUTHOR
|
||||||
|
RSpade Framework
|
||||||
|
|
||||||
|
RSpade January 2026 APP_MODE(3)
|
||||||
318
app/RSpade/man/file_drop.txt
Executable file
318
app/RSpade/man/file_drop.txt
Executable file
@@ -0,0 +1,318 @@
|
|||||||
|
NAME
|
||||||
|
File Drop Handler - Global drag-and-drop file interception system
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
Add class="rsx-droppable" to any element or component to receive
|
||||||
|
dropped files via the file-drop event.
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
The RSpade framework provides a global file drop handler that intercepts
|
||||||
|
all file drag-and-drop operations at the document level. This system
|
||||||
|
routes dropped files to designated drop targets using CSS class-based
|
||||||
|
registration.
|
||||||
|
|
||||||
|
Unlike traditional HTML5 drag-and-drop which requires explicit event
|
||||||
|
handlers on each element, this system provides:
|
||||||
|
- Automatic visual feedback during file drags
|
||||||
|
- Smart target selection (single vs multiple targets)
|
||||||
|
- Cursor feedback indicating valid/invalid drop zones
|
||||||
|
- Component event integration
|
||||||
|
|
||||||
|
The framework initializes this handler automatically during bootstrap.
|
||||||
|
No manual initialization is required.
|
||||||
|
|
||||||
|
CSS CLASSES
|
||||||
|
|
||||||
|
rsx-droppable
|
||||||
|
Marks an element as a valid file drop target. Add this class to
|
||||||
|
any element or component that should receive dropped files.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
<div class="rsx-droppable">
|
||||||
|
Drop files here
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<My_Upload_Widget class="rsx-droppable" />
|
||||||
|
|
||||||
|
rsx-drop-active
|
||||||
|
Automatically added to ALL visible rsx-droppable elements when
|
||||||
|
files are being dragged anywhere on the page. Use this for visual
|
||||||
|
feedback like highlighting or borders.
|
||||||
|
|
||||||
|
Example CSS:
|
||||||
|
.My_Upload_Widget.rsx-drop-active {
|
||||||
|
border: 2px dashed #007bff;
|
||||||
|
background: rgba(0, 123, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
rsx-drop-target
|
||||||
|
Automatically added to the specific element that will receive
|
||||||
|
the drop. This is the "hot" target that files will go to if
|
||||||
|
the user releases.
|
||||||
|
|
||||||
|
Example CSS:
|
||||||
|
.My_Upload_Widget.rsx-drop-target {
|
||||||
|
border: 2px solid #007bff;
|
||||||
|
background: rgba(0, 123, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TARGET SELECTION BEHAVIOR
|
||||||
|
|
||||||
|
Single Target:
|
||||||
|
When only ONE visible rsx-droppable element exists on the page,
|
||||||
|
it automatically becomes the drop target as soon as files enter
|
||||||
|
the window. No hover required.
|
||||||
|
|
||||||
|
Multiple Targets:
|
||||||
|
When MULTIPLE visible rsx-droppable elements exist, the user must
|
||||||
|
hover over a specific element to select it as the target. The
|
||||||
|
cursor shows "no-drop" when not over a valid target.
|
||||||
|
|
||||||
|
Visibility:
|
||||||
|
Only visible elements participate in drop handling. Elements that
|
||||||
|
are display:none, visibility:hidden, or outside the viewport are
|
||||||
|
ignored. This allows inactive widgets to exist in the DOM without
|
||||||
|
interfering.
|
||||||
|
|
||||||
|
FILE-DROP EVENT
|
||||||
|
|
||||||
|
When files are dropped on a valid target, a file-drop event is triggered
|
||||||
|
on the component (if the element is a component) or the element itself.
|
||||||
|
|
||||||
|
Component Event Handler:
|
||||||
|
class My_Upload_Widget extends Jqhtml_Component {
|
||||||
|
on_render() {
|
||||||
|
this.on('file-drop', (component, data) => {
|
||||||
|
this._handle_files(data.files);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_handle_files(files) {
|
||||||
|
for (let file of files) {
|
||||||
|
console.log('Received:', file.name, file.type, file.size);
|
||||||
|
// Upload file, validate type, etc.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jQuery Event Handler (non-component elements):
|
||||||
|
$('.my-drop-zone').on('file-drop', function(e, data) {
|
||||||
|
for (let file of data.files) {
|
||||||
|
console.log('Received:', file.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Event Data:
|
||||||
|
{
|
||||||
|
files: FileList, // The dropped files
|
||||||
|
dataTransfer: DataTransfer, // Full dataTransfer object
|
||||||
|
originalEvent: DragEvent // Original browser event
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE VALIDATION
|
||||||
|
|
||||||
|
Each widget is responsible for validating dropped files. The framework
|
||||||
|
provides files without filtering. Common validation patterns:
|
||||||
|
|
||||||
|
By MIME Type:
|
||||||
|
_handle_files(files) {
|
||||||
|
for (let file of files) {
|
||||||
|
if (!file.type.startsWith('image/')) {
|
||||||
|
Flash.error(`${file.name} is not an image`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this._upload(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
By Extension:
|
||||||
|
_handle_files(files) {
|
||||||
|
const allowed = ['.pdf', '.doc', '.docx'];
|
||||||
|
for (let file of files) {
|
||||||
|
const ext = '.' + file.name.split('.').pop().toLowerCase();
|
||||||
|
if (!allowed.includes(ext)) {
|
||||||
|
Flash.error(`${file.name}: only PDF and Word documents allowed`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this._upload(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
By Size:
|
||||||
|
_handle_files(files) {
|
||||||
|
const max_size = 10 * 1024 * 1024; // 10 MB
|
||||||
|
for (let file of files) {
|
||||||
|
if (file.size > max_size) {
|
||||||
|
Flash.error(`${file.name} exceeds 10 MB limit`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this._upload(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Single File Only:
|
||||||
|
_handle_files(files) {
|
||||||
|
if (files.length > 1) {
|
||||||
|
Flash.error('Please drop only one file');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._upload(files[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
CURSOR FEEDBACK
|
||||||
|
|
||||||
|
The framework automatically sets dropEffect to control cursor appearance:
|
||||||
|
|
||||||
|
- "copy" cursor: Shown when hovering over a valid drop target
|
||||||
|
- "none" cursor: Shown when no valid target exists or when hovering
|
||||||
|
outside all targets (in multi-target mode)
|
||||||
|
|
||||||
|
This provides immediate visual feedback about whether a drop will succeed.
|
||||||
|
|
||||||
|
IMPLEMENTATION NOTES
|
||||||
|
|
||||||
|
Drag Counter:
|
||||||
|
The framework uses a drag counter to track when files enter and
|
||||||
|
leave the window. This handles the common issue where dragenter
|
||||||
|
and dragleave fire for child elements.
|
||||||
|
|
||||||
|
Event Prevention:
|
||||||
|
The handler prevents default browser behavior for all file drags,
|
||||||
|
ensuring files are never accidentally downloaded or opened.
|
||||||
|
|
||||||
|
Cleanup:
|
||||||
|
Drag state is automatically cleared when:
|
||||||
|
- Files are dropped (successfully or not)
|
||||||
|
- Drag operation is cancelled (e.g., Escape key)
|
||||||
|
- Files leave the window entirely
|
||||||
|
|
||||||
|
EXAMPLES
|
||||||
|
|
||||||
|
Basic Image Uploader:
|
||||||
|
<Define:Image_Uploader tag="div" class="rsx-droppable">
|
||||||
|
<div class="drop-hint">Drop image here</div>
|
||||||
|
<img $sid="preview" style="display: none" />
|
||||||
|
</Define:Image_Uploader>
|
||||||
|
|
||||||
|
class Image_Uploader extends Jqhtml_Component {
|
||||||
|
on_render() {
|
||||||
|
this.on('file-drop', (_, data) => {
|
||||||
|
const file = data.files[0];
|
||||||
|
if (!file || !file.type.startsWith('image/')) {
|
||||||
|
Flash.error('Please drop an image file');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show preview
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
this.$sid('preview').attr('src', e.target.result).show();
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
|
||||||
|
// Upload
|
||||||
|
this._upload(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Multi-File Document Upload:
|
||||||
|
<Define:Document_Dropzone tag="div" class="rsx-droppable Document_Dropzone">
|
||||||
|
<div class="drop-area">
|
||||||
|
<span class="icon">Drop documents here</span>
|
||||||
|
<ul $sid="file_list"></ul>
|
||||||
|
</div>
|
||||||
|
</Define:Document_Dropzone>
|
||||||
|
|
||||||
|
class Document_Dropzone extends Jqhtml_Component {
|
||||||
|
on_create() {
|
||||||
|
this.state.files = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
on_render() {
|
||||||
|
this.on('file-drop', (_, data) => {
|
||||||
|
for (let file of data.files) {
|
||||||
|
if (!this._validate(file)) continue;
|
||||||
|
this.state.files.push(file);
|
||||||
|
this._add_to_list(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_validate(file) {
|
||||||
|
const allowed = ['application/pdf', 'application/msword'];
|
||||||
|
if (!allowed.includes(file.type)) {
|
||||||
|
Flash.error(`${file.name}: only PDF and Word files allowed`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (file.size > 25 * 1024 * 1024) {
|
||||||
|
Flash.error(`${file.name}: maximum 25 MB`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
STYLING RECOMMENDATIONS
|
||||||
|
|
||||||
|
Provide clear visual states for drag operations:
|
||||||
|
|
||||||
|
.My_Dropzone {
|
||||||
|
border: 2px dashed #ccc;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Files are being dragged - highlight potential targets */
|
||||||
|
.My_Dropzone.rsx-drop-active {
|
||||||
|
border-color: #007bff;
|
||||||
|
background: rgba(0, 123, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This specific element will receive the drop */
|
||||||
|
.My_Dropzone.rsx-drop-target {
|
||||||
|
border-style: solid;
|
||||||
|
background: rgba(0, 123, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
RSX VS HTML5 DRAG-DROP
|
||||||
|
|
||||||
|
Standard HTML5:
|
||||||
|
- Must add dragenter, dragover, drop handlers to each element
|
||||||
|
- Must manually prevent default behavior
|
||||||
|
- Must track drag state per element
|
||||||
|
- No automatic multi-target coordination
|
||||||
|
|
||||||
|
RSX:
|
||||||
|
- Add rsx-droppable class, handle file-drop event
|
||||||
|
- Framework handles all drag events
|
||||||
|
- Automatic state management and cleanup
|
||||||
|
- Smart single vs multi-target behavior
|
||||||
|
|
||||||
|
TROUBLESHOOTING
|
||||||
|
|
||||||
|
Files not being received:
|
||||||
|
- Verify element has rsx-droppable class
|
||||||
|
- Check element is visible (not display:none)
|
||||||
|
- Ensure event handler is registered before drop
|
||||||
|
|
||||||
|
Visual feedback not appearing:
|
||||||
|
- Define CSS for .rsx-drop-active and .rsx-drop-target
|
||||||
|
- Check CSS specificity isn't being overridden
|
||||||
|
|
||||||
|
Wrong target receiving files (multiple targets):
|
||||||
|
- Both targets are visible; ensure unused widget is hidden
|
||||||
|
- Check z-index if elements overlap
|
||||||
|
|
||||||
|
Cursor shows "no-drop" unexpectedly:
|
||||||
|
- No visible rsx-droppable elements on page
|
||||||
|
- Not hovering over any target in multi-target mode
|
||||||
|
|
||||||
|
SEE ALSO
|
||||||
|
file_upload.txt - Server-side file upload system
|
||||||
|
jqhtml.txt - JQHTML component system
|
||||||
|
|
||||||
|
VERSION
|
||||||
|
RSpade Framework 1.0
|
||||||
|
Last Updated: 2025-01-14
|
||||||
@@ -199,6 +199,37 @@ normalize_schema ensures these columns exist on ALL tables (except excluded tabl
|
|||||||
• site_id BIGINT (for Siteable trait)
|
• site_id BIGINT (for Siteable trait)
|
||||||
• version BIGINT (for Versionable/Ajaxable traits)
|
• version BIGINT (for Versionable/Ajaxable traits)
|
||||||
|
|
||||||
|
### Order Column (Manual Sorting)
|
||||||
|
Tables with an `order` column are automatically normalized:
|
||||||
|
• Type: BIGINT DEFAULT NULL
|
||||||
|
• Index: order_idx covering (order)
|
||||||
|
• Triggers: Auto-increment on INSERT/UPDATE when order is NULL
|
||||||
|
|
||||||
|
The triggers ensure that when a record is inserted or updated with `order = NULL`,
|
||||||
|
it automatically gets assigned `MAX(order) + 1` from that table. This provides
|
||||||
|
automatic "add to end" behavior while still allowing explicit ordering.
|
||||||
|
|
||||||
|
Usage pattern:
|
||||||
|
```php
|
||||||
|
// Insert at end (auto-assigns order)
|
||||||
|
$item = new Menu_Item();
|
||||||
|
$item->name = 'New Item';
|
||||||
|
$item->order = null; // Will be MAX(order) + 1
|
||||||
|
$item->save();
|
||||||
|
|
||||||
|
// Insert at specific position
|
||||||
|
$item->order = 5; // Explicit position
|
||||||
|
$item->save();
|
||||||
|
|
||||||
|
// Reorder (swap with another item)
|
||||||
|
$item1->order = $item2->order;
|
||||||
|
$item2->order = $item1_old_order;
|
||||||
|
$item1->save();
|
||||||
|
$item2->save();
|
||||||
|
```
|
||||||
|
|
||||||
|
Trigger naming convention: {table_name}_order_insert, {table_name}_order_update
|
||||||
|
|
||||||
Excluded Tables: migrations, api_clients, sessions
|
Excluded Tables: migrations, api_clients, sessions
|
||||||
|
|
||||||
AUTOMATIC INDEX MANAGEMENT
|
AUTOMATIC INDEX MANAGEMENT
|
||||||
|
|||||||
66
app/RSpade/upstream_changes/app_mode_01_14.txt
Executable file
66
app/RSpade/upstream_changes/app_mode_01_14.txt
Executable file
@@ -0,0 +1,66 @@
|
|||||||
|
APPLICATION MODE SYSTEM - MIGRATION GUIDE
|
||||||
|
Date: 2026-01-14
|
||||||
|
|
||||||
|
SUMMARY
|
||||||
|
The framework now supports three execution modes: development, debug,
|
||||||
|
and production. A new RSX_MODE environment variable controls mode
|
||||||
|
behavior. Production mode requires pre-built assets and includes
|
||||||
|
optimizations like minification, JS/CSS merging, and CDN bundling.
|
||||||
|
|
||||||
|
AFFECTED FILES
|
||||||
|
- .env (add RSX_MODE)
|
||||||
|
- .gitignore (add /rsx-export/)
|
||||||
|
|
||||||
|
CHANGES REQUIRED
|
||||||
|
|
||||||
|
1. Add RSX_MODE to .env
|
||||||
|
|
||||||
|
Add to your .env file:
|
||||||
|
|
||||||
|
RSX_MODE=development
|
||||||
|
|
||||||
|
Valid values: development, debug, production
|
||||||
|
|
||||||
|
2. Add Export Directory to .gitignore
|
||||||
|
|
||||||
|
Add to your .gitignore:
|
||||||
|
|
||||||
|
/rsx-export/
|
||||||
|
|
||||||
|
This directory is created by rsx:prod:export and should not be
|
||||||
|
committed to version control.
|
||||||
|
|
||||||
|
3. Update Deployment Process
|
||||||
|
|
||||||
|
If you deploy manually, update your process to use:
|
||||||
|
|
||||||
|
php artisan rsx:prod:export
|
||||||
|
|
||||||
|
This creates a deployment-ready copy in ./rsx-export/ with all
|
||||||
|
production optimizations applied.
|
||||||
|
|
||||||
|
NEW COMMANDS
|
||||||
|
|
||||||
|
rsx:mode:set <mode>
|
||||||
|
Change application mode. Updates .env, clears artifacts, and
|
||||||
|
rebuilds as appropriate for the new mode.
|
||||||
|
|
||||||
|
php artisan rsx:mode:set production
|
||||||
|
php artisan rsx:mode:set development
|
||||||
|
|
||||||
|
rsx:prod:build
|
||||||
|
Build all production assets (minified, merged, CDN cached).
|
||||||
|
|
||||||
|
rsx:prod:export
|
||||||
|
Export application for deployment to ./rsx-export/
|
||||||
|
|
||||||
|
VERIFICATION
|
||||||
|
1. Add RSX_MODE=development to .env
|
||||||
|
2. Run: php artisan rsx:mode:set production
|
||||||
|
3. Verify ./rsx-export/ is NOT created (that requires rsx:prod:export)
|
||||||
|
4. Verify site loads with production optimizations
|
||||||
|
5. Run: php artisan rsx:mode:set development
|
||||||
|
6. Verify automatic rebuilding resumes
|
||||||
|
|
||||||
|
REFERENCE
|
||||||
|
php artisan rsx:man app_mode
|
||||||
@@ -89,6 +89,24 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application Mode
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| RSX_MODE is the authoritative source of truth for application mode.
|
||||||
|
| Valid values: development, debug, production
|
||||||
|
|
|
||||||
|
| - development: Auto-rebuild, full debugging, sourcemaps
|
||||||
|
| - debug: Production optimizations with sourcemaps for debugging
|
||||||
|
| - production: Full optimization, minification, merging, CDN bundling
|
||||||
|
|
|
||||||
|
| See: php artisan rsx:man app_mode
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'mode' => env('RSX_MODE', 'development'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Manifest Modules
|
| Manifest Modules
|
||||||
@@ -299,6 +317,18 @@ return [
|
|||||||
'.gitattributes', // Git attributes config
|
'.gitattributes', // Git attributes config
|
||||||
'._rsx_helper.php', // IDE helper stubs (auto-generated)
|
'._rsx_helper.php', // IDE helper stubs (auto-generated)
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// Directories to exclude from code quality checks (relative path segments)
|
||||||
|
// These are matched via str_contains, so 'Core/Manifest' matches 'app/RSpade/Core/Manifest/'
|
||||||
|
'excluded_dirs' => [
|
||||||
|
'vendor',
|
||||||
|
'node_modules',
|
||||||
|
'storage',
|
||||||
|
'.git',
|
||||||
|
'public',
|
||||||
|
'resource',
|
||||||
|
'Core/Manifest', // Manifest builder uses reflection - can't use Manifest API
|
||||||
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -216,6 +216,36 @@ Merged via `array_merge_deep()`. Common overrides: `development.auto_rename_file
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## APPLICATION MODES
|
||||||
|
|
||||||
|
Three modes control build behavior: `development`, `debug`, `production`.
|
||||||
|
|
||||||
|
**Switch modes**: `php artisan rsx:mode:set dev|debug|prod`
|
||||||
|
|
||||||
|
| Behavior | Development | Debug | Production |
|
||||||
|
|----------|-------------|-------|------------|
|
||||||
|
| Auto-rebuild on file change | Yes | No | No |
|
||||||
|
| Minification (JS/CSS) | No | Yes | Yes |
|
||||||
|
| Inline sourcemaps | Yes | Yes | No |
|
||||||
|
| console_debug() works | Yes | Yes | Stripped |
|
||||||
|
| Bundle merging | No | No | Yes (single app.js/css) |
|
||||||
|
| CDN assets | External URLs | External URLs | Cached & bundled |
|
||||||
|
|
||||||
|
**Development**: Active coding - files auto-compile, full debugging.
|
||||||
|
|
||||||
|
**Debug**: Test production-like build locally - minified but with sourcemaps and working console_debug.
|
||||||
|
|
||||||
|
**Production**: Optimized deployment - everything merged, minified, debug code stripped.
|
||||||
|
|
||||||
|
**Build commands**:
|
||||||
|
- `rsx:mode:set` - Switch mode and rebuild
|
||||||
|
- `rsx:prod:build` - Rebuild for debug/production
|
||||||
|
- `rsx:prod:export` - Export deployable package
|
||||||
|
|
||||||
|
**Environment**: `RSX_MODE` in `.env` (default: `development`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## ROUTING & CONTROLLERS
|
## ROUTING & CONTROLLERS
|
||||||
|
|
||||||
```php
|
```php
|
||||||
|
|||||||
1
node_modules/.bin/.svgo-AV73XapU
generated
vendored
Symbolic link
1
node_modules/.bin/.svgo-AV73XapU
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../svgo/bin/svgo
|
||||||
2
node_modules/.bin/svgo
generated
vendored
2
node_modules/.bin/svgo
generated
vendored
@@ -1 +1 @@
|
|||||||
../svgo/bin/svgo
|
../svgo/bin/svgo.js
|
||||||
1407
node_modules/.package-lock.json
generated
vendored
1407
node_modules/.package-lock.json
generated
vendored
File diff suppressed because it is too large
Load Diff
21
node_modules/@parcel/watcher-linux-x64-musl/LICENSE
generated
vendored
Executable file
21
node_modules/@parcel/watcher-linux-x64-musl/LICENSE
generated
vendored
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017-present Devon Govett
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
1
node_modules/@parcel/watcher-linux-x64-musl/README.md
generated
vendored
Executable file
1
node_modules/@parcel/watcher-linux-x64-musl/README.md
generated
vendored
Executable file
@@ -0,0 +1 @@
|
|||||||
|
This is the linux-x64-musl build of @parcel/watcher. See https://github.com/parcel-bundler/watcher for details.
|
||||||
33
node_modules/@parcel/watcher-linux-x64-musl/package.json
generated
vendored
Executable file
33
node_modules/@parcel/watcher-linux-x64-musl/package.json
generated
vendored
Executable file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "@parcel/watcher-linux-x64-musl",
|
||||||
|
"version": "2.5.1",
|
||||||
|
"main": "watcher.node",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/parcel-bundler/watcher.git"
|
||||||
|
},
|
||||||
|
"description": "A native C++ Node module for querying and subscribing to filesystem events. Used by Parcel 2.",
|
||||||
|
"license": "MIT",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"watcher.node"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
node_modules/@parcel/watcher-linux-x64-musl/watcher.node
generated
vendored
Executable file
BIN
node_modules/@parcel/watcher-linux-x64-musl/watcher.node
generated
vendored
Executable file
Binary file not shown.
3
node_modules/@rollup/rollup-linux-x64-musl/README.md
generated
vendored
Executable file
3
node_modules/@rollup/rollup-linux-x64-musl/README.md
generated
vendored
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
# `@rollup/rollup-linux-x64-musl`
|
||||||
|
|
||||||
|
This is the **x86_64-unknown-linux-musl** binary for `rollup`
|
||||||
25
node_modules/@rollup/rollup-linux-x64-musl/package.json
generated
vendored
Executable file
25
node_modules/@rollup/rollup-linux-x64-musl/package.json
generated
vendored
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "@rollup/rollup-linux-x64-musl",
|
||||||
|
"version": "4.54.0",
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"rollup.linux-x64-musl.node"
|
||||||
|
],
|
||||||
|
"description": "Native bindings for Rollup",
|
||||||
|
"author": "Lukas Taegert-Atkinson",
|
||||||
|
"homepage": "https://rollupjs.org/",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/rollup/rollup.git"
|
||||||
|
},
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"main": "./rollup.linux-x64-musl.node"
|
||||||
|
}
|
||||||
BIN
node_modules/@rollup/rollup-linux-x64-musl/rollup.linux-x64-musl.node
generated
vendored
Executable file
BIN
node_modules/@rollup/rollup-linux-x64-musl/rollup.linux-x64-musl.node
generated
vendored
Executable file
Binary file not shown.
3145
node_modules/css-declaration-sorter/dist/main.cjs
generated
vendored
3145
node_modules/css-declaration-sorter/dist/main.cjs
generated
vendored
File diff suppressed because it is too large
Load Diff
43
node_modules/css-declaration-sorter/package.json
generated
vendored
43
node_modules/css-declaration-sorter/package.json
generated
vendored
@@ -1,45 +1,48 @@
|
|||||||
{
|
{
|
||||||
"name": "css-declaration-sorter",
|
"name": "css-declaration-sorter",
|
||||||
"version": "6.4.1",
|
"version": "7.3.1",
|
||||||
"description": "Sorts CSS declarations fast and automatically in a certain order.",
|
"description": "Sorts CSS declarations fast and automatically in a certain order.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/main.cjs",
|
|
||||||
"exports": {
|
"exports": {
|
||||||
"import": "./src/main.mjs",
|
"import": {
|
||||||
"require": "./dist/main.cjs"
|
"types": "./src/core/main.d.mts",
|
||||||
|
"default": "./src/core/main.mjs"
|
||||||
},
|
},
|
||||||
"types": "./src/main.d.ts",
|
"require": {
|
||||||
|
"types": "./src/core/main.d.cts",
|
||||||
|
"default": "./dist/main.cjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"types": "./src/core/main.d.cts",
|
||||||
|
"main": "./dist/main.cjs",
|
||||||
"files": [
|
"files": [
|
||||||
"src/main.mjs",
|
"src/core/",
|
||||||
"src/main.d.ts",
|
"src/orders/",
|
||||||
"src/shorthand-data.mjs",
|
"dist/"
|
||||||
"src/bubble-sort.mjs",
|
|
||||||
"orders",
|
|
||||||
"dist"
|
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rollup -c",
|
"build": "rollup -c",
|
||||||
"preversion": "npm test",
|
"preversion": "npm test",
|
||||||
"test": "uvu src .+\\.test\\.mjs",
|
"test": "uvu src .+\\.test\\.mjs",
|
||||||
"test:ci": "npm test && npm run lint -- --max-warnings 0",
|
"test:ci": "npm test && npm run lint -- --max-warnings 0",
|
||||||
"lint": "eslint src/*.mjs",
|
"lint": "eslint src/core/*.mjs",
|
||||||
"scrape": "node --experimental-import-meta-resolve src/property-scraper.mjs",
|
"scrape": "node src/property-scraper.mjs",
|
||||||
"prepack": "npm run build"
|
"prepack": "npm run build"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@mdn/browser-compat-data": "^5.2.23",
|
"@eslint/js": "^9.35.0",
|
||||||
"@rollup/plugin-dynamic-import-vars": "^2.0.2",
|
"@mdn/browser-compat-data": "^7.1.7",
|
||||||
"@rollup/plugin-replace": "^5.0.2",
|
"@rollup/plugin-dynamic-import-vars": "^2.1.5",
|
||||||
"eslint": "^8.35.0",
|
"eslint": "^9.35.0",
|
||||||
"postcss": "^8.4.18",
|
"postcss": "^8.5.6",
|
||||||
"rollup": "^3.15.0",
|
"rollup": "^4.52.4",
|
||||||
"uvu": "^0.5.6"
|
"uvu": "^0.5.6"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"postcss": "^8.0.9"
|
"postcss": "^8.0.9"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^10 || ^12 || >=14"
|
"node": "^14 || ^16 || >=18"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
12
node_modules/css-declaration-sorter/readme.md
generated
vendored
12
node_modules/css-declaration-sorter/readme.md
generated
vendored
@@ -58,6 +58,18 @@ body {
|
|||||||
4. Dimensions
|
4. Dimensions
|
||||||
5. Text
|
5. Text
|
||||||
|
|
||||||
|
- [Frakto](https://github.com/fraktodev/frakto-css-order)
|
||||||
|
`frakto`
|
||||||
|
*Order from the outermost layout and positioning rules, moving inward through structure, style, and interaction — inspired by the browser’s render tree.*
|
||||||
|
1. Positioning
|
||||||
|
2. Box Model
|
||||||
|
3. Layout
|
||||||
|
4. Typography
|
||||||
|
5. Visual
|
||||||
|
6. Transform
|
||||||
|
7. Interaction
|
||||||
|
8. Miscellaneous
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
Following the PostCSS plugin guidelines, this package depends on PostCSS as a peer dependency:
|
Following the PostCSS plugin guidelines, this package depends on PostCSS as a peer dependency:
|
||||||
`npm install postcss css-declaration-sorter --save-dev`
|
`npm install postcss css-declaration-sorter --save-dev`
|
||||||
|
|||||||
0
node_modules/css-declaration-sorter/src/bubble-sort.mjs → node_modules/css-declaration-sorter/src/core/bubble-sort.mjs
generated
vendored
Executable file → Normal file
0
node_modules/css-declaration-sorter/src/bubble-sort.mjs → node_modules/css-declaration-sorter/src/core/bubble-sort.mjs
generated
vendored
Executable file → Normal file
27
node_modules/css-declaration-sorter/src/core/main.d.cts
generated
vendored
Normal file
27
node_modules/css-declaration-sorter/src/core/main.d.cts
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import type { PluginCreator } from 'postcss';
|
||||||
|
|
||||||
|
declare const cssDeclarationSorter: PluginCreator<{
|
||||||
|
/**
|
||||||
|
Provide the name of one of the built-in sort orders or a comparison function that is passed to `Array.sort`.
|
||||||
|
|
||||||
|
@default 'alphabetical'
|
||||||
|
*/
|
||||||
|
order?: SortOrder | SortFunction | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
To prevent breaking legacy CSS where shorthand declarations override longhand declarations. For example `animation-name: some; animation: greeting;` will be kept in this order.
|
||||||
|
|
||||||
|
@default false
|
||||||
|
*/
|
||||||
|
keepOverrides?: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export = cssDeclarationSorter;
|
||||||
|
|
||||||
|
type SortOrder = 'alphabetical' | 'concentric-css' | 'smacss' | 'frakto';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function receives two declaration property names and is expected
|
||||||
|
* to return -1, 0 or 1 depending on the wanted order.
|
||||||
|
*/
|
||||||
|
type SortFunction = (propertyNameA: string, propertyNameB: string) => -1 | 0 | 1;
|
||||||
27
node_modules/css-declaration-sorter/src/core/main.d.mts
generated
vendored
Normal file
27
node_modules/css-declaration-sorter/src/core/main.d.mts
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import type { PluginCreator } from 'postcss';
|
||||||
|
|
||||||
|
export const cssDeclarationSorter: PluginCreator<{
|
||||||
|
/**
|
||||||
|
Provide the name of one of the built-in sort orders or a comparison function that is passed to `Array.sort`.
|
||||||
|
|
||||||
|
@default 'alphabetical'
|
||||||
|
*/
|
||||||
|
order?: SortOrder | SortFunction | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
To prevent breaking legacy CSS where shorthand declarations override longhand declarations. For example `animation-name: some; animation: greeting;` will be kept in this order.
|
||||||
|
|
||||||
|
@default false
|
||||||
|
*/
|
||||||
|
keepOverrides?: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default cssDeclarationSorter;
|
||||||
|
|
||||||
|
type SortOrder = 'alphabetical' | 'concentric-css' | 'smacss' | 'frakto';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function receives two declaration property names and is expected
|
||||||
|
* to return -1, 0 or 1 depending on the wanted order.
|
||||||
|
*/
|
||||||
|
type SortFunction = (propertyNameA: string, propertyNameB: string) => -1 | 0 | 1;
|
||||||
153
node_modules/css-declaration-sorter/src/core/main.mjs
generated
vendored
Normal file
153
node_modules/css-declaration-sorter/src/core/main.mjs
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import { shorthandData } from './shorthand-data.mjs';
|
||||||
|
import { bubbleSort } from './bubble-sort.mjs';
|
||||||
|
|
||||||
|
const builtInOrders = [
|
||||||
|
'alphabetical',
|
||||||
|
'concentric-css',
|
||||||
|
'smacss',
|
||||||
|
'frakto'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const cssDeclarationSorter = ({ order = 'alphabetical', keepOverrides = false } = {}) => ({
|
||||||
|
postcssPlugin: 'css-declaration-sorter',
|
||||||
|
OnceExit (css) {
|
||||||
|
let withKeepOverrides = comparator => comparator;
|
||||||
|
if (keepOverrides) {
|
||||||
|
withKeepOverrides = withOverridesComparator(shorthandData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof order === 'function') {
|
||||||
|
return processCss({ css, comparator: withKeepOverrides(order) });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!builtInOrders.includes(order))
|
||||||
|
return Promise.reject(
|
||||||
|
Error([
|
||||||
|
`Invalid built-in order '${order}' provided.`,
|
||||||
|
`Available built-in orders are: ${builtInOrders}`,
|
||||||
|
].join('\n'))
|
||||||
|
);
|
||||||
|
|
||||||
|
return import(`../orders/${order}.mjs`)
|
||||||
|
.then(({ properties }) => processCss({
|
||||||
|
css,
|
||||||
|
comparator: withKeepOverrides(orderComparator(properties)),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
cssDeclarationSorter.postcss = true;
|
||||||
|
|
||||||
|
// Kept for backward compatibility
|
||||||
|
export default cssDeclarationSorter;
|
||||||
|
|
||||||
|
function processCss ({ css, comparator }) {
|
||||||
|
const comments = [];
|
||||||
|
const rulesCache = [];
|
||||||
|
|
||||||
|
css.walk(node => {
|
||||||
|
const nodes = node.nodes;
|
||||||
|
const type = node.type;
|
||||||
|
|
||||||
|
if (type === 'comment') {
|
||||||
|
// Don't do anything to root comments or the last newline comment
|
||||||
|
const isNewlineNode = node.raws.before && node.raws.before.includes('\n');
|
||||||
|
const lastNewlineNode = isNewlineNode && !node.next();
|
||||||
|
const onlyNode = !node.prev() && !node.next() || !node.parent;
|
||||||
|
|
||||||
|
if (lastNewlineNode || onlyNode || node.parent.type === 'root') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNewlineNode) {
|
||||||
|
const pairedNode = node.next() || node.prev();
|
||||||
|
if (pairedNode) {
|
||||||
|
comments.unshift({
|
||||||
|
'comment': node,
|
||||||
|
'pairedNode': pairedNode,
|
||||||
|
'insertPosition': node.next() ? 'Before' : 'After',
|
||||||
|
});
|
||||||
|
node.remove();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const pairedNode = node.prev() || node.next();
|
||||||
|
if (pairedNode) {
|
||||||
|
comments.push({
|
||||||
|
'comment': node,
|
||||||
|
'pairedNode': pairedNode,
|
||||||
|
'insertPosition': 'After',
|
||||||
|
});
|
||||||
|
node.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add rule-like nodes to a cache so that we can remove all
|
||||||
|
// comment nodes before we start sorting.
|
||||||
|
const isRule = type === 'rule' || type === 'atrule';
|
||||||
|
if (isRule && nodes && nodes.length > 1) {
|
||||||
|
rulesCache.push(nodes);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Perform a sort once all comment nodes are removed
|
||||||
|
rulesCache.forEach(nodes => {
|
||||||
|
sortCssDeclarations({ nodes, comparator });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add comments back to the nodes they are paired with
|
||||||
|
comments.forEach(node => {
|
||||||
|
const pairedNode = node.pairedNode;
|
||||||
|
node.comment.remove();
|
||||||
|
pairedNode.parent && pairedNode.parent['insert' + node.insertPosition](pairedNode, node.comment);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortCssDeclarations ({ nodes, comparator }) {
|
||||||
|
bubbleSort(nodes, (a, b) => {
|
||||||
|
if (a.type === 'decl' && b.type === 'decl') {
|
||||||
|
return comparator(a.prop, b.prop);
|
||||||
|
} else {
|
||||||
|
return compareDifferentType(a, b);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function withOverridesComparator (shorthandData) {
|
||||||
|
return function (comparator) {
|
||||||
|
return function (a, b) {
|
||||||
|
a = removeVendorPrefix(a);
|
||||||
|
b = removeVendorPrefix(b);
|
||||||
|
|
||||||
|
if (shorthandData[a] && shorthandData[a].includes(b)) return 0;
|
||||||
|
if (shorthandData[b] && shorthandData[b].includes(a)) return 0;
|
||||||
|
|
||||||
|
return comparator(a, b);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function orderComparator (order) {
|
||||||
|
return function (a, b) {
|
||||||
|
const bIndex = order.indexOf(b);
|
||||||
|
|
||||||
|
if (bIndex === -1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return order.indexOf(a) - bIndex;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareDifferentType (a, b) {
|
||||||
|
if (b.type === 'atrule' || a.type === 'atrule') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.type === 'decl' ? -1 : b.type === 'decl' ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeVendorPrefix (property) {
|
||||||
|
return property.replace(/^-\w+-/, '');
|
||||||
|
}
|
||||||
261
node_modules/css-declaration-sorter/src/core/main.test.mjs
generated
vendored
Normal file
261
node_modules/css-declaration-sorter/src/core/main.test.mjs
generated
vendored
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
import { test } from 'uvu';
|
||||||
|
import * as assert from 'uvu/assert';
|
||||||
|
import postcss from 'postcss';
|
||||||
|
import { cssDeclarationSorter as plugin } from './main.mjs';
|
||||||
|
|
||||||
|
const testCssFixtures = (testMessage, tests) => {
|
||||||
|
test(testMessage, () => (
|
||||||
|
Promise.all(tests.map(({ message, fixture, expected, options }) => (
|
||||||
|
postcss(plugin(options))
|
||||||
|
.process(fixture, { from: undefined })
|
||||||
|
.then((result) => {
|
||||||
|
assert.is(result.css, expected, message);
|
||||||
|
assert.is(result.warnings().length, 0);
|
||||||
|
})
|
||||||
|
)))
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortOrderTests = [
|
||||||
|
{
|
||||||
|
message: 'Keep same order for identical properties.',
|
||||||
|
fixture: 'a{flex: 0;flex: 2;}',
|
||||||
|
expected: 'a{flex: 0;flex: 2;}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Sort alphabetically with no order defined.',
|
||||||
|
fixture: 'a{flex: 0;border: 0;}',
|
||||||
|
expected: 'a{border: 0;flex: 0;}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Sort alphabetically with a defined order.',
|
||||||
|
fixture: 'a{flex: 0;border: 0;}',
|
||||||
|
expected: 'a{border: 0;flex: 0;}',
|
||||||
|
options: { order: 'alphabetical' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Sort according to custom order, changed.',
|
||||||
|
fixture: 'a{border: 0;z-index: 0;}',
|
||||||
|
expected: 'a{z-index: 0;border: 0;}',
|
||||||
|
options: { order: () => 1 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Sort according to custom order, retained.',
|
||||||
|
fixture: 'a{border: 0;z-index: 0;}',
|
||||||
|
expected: 'a{border: 0;z-index: 0;}',
|
||||||
|
options: { order: () => -1 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Sort according to SMACSS.',
|
||||||
|
fixture: 'a{border: 0;flex: 0;}',
|
||||||
|
expected: 'a{flex: 0;border: 0;}',
|
||||||
|
options: { order: 'smacss' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Sort according to Concentric CSS.',
|
||||||
|
fixture: 'a{border: 0;flex: 0;}',
|
||||||
|
expected: 'a{flex: 0;border: 0;}',
|
||||||
|
options: { order: 'concentric-css' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Sort according to Frakto.',
|
||||||
|
fixture: 'a{border: 0;flex: 0;}',
|
||||||
|
expected: 'a{flex: 0;border: 0;}',
|
||||||
|
options: { order: 'frakto' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Keep at-rule at the same position.',
|
||||||
|
fixture: 'a{border: 0;@import sii;flex:0;}',
|
||||||
|
expected: 'a{border: 0;@import sii;flex:0;}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Retain unknown properties, left to right.',
|
||||||
|
fixture: 'a{unknown-a: 0;unknown-b: 0;}',
|
||||||
|
expected: 'a{unknown-a: 0;unknown-b: 0;}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Retain unknown properties, right to left.',
|
||||||
|
fixture: 'a{unknown-b: 0;unknown-a: 0;}',
|
||||||
|
expected: 'a{unknown-b: 0;unknown-a: 0;}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Retain unknown next to known properties, left to right.',
|
||||||
|
fixture: 'a{animation: 0;unknown-a: none;}',
|
||||||
|
expected: 'a{animation: 0;unknown-a: none;}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Retain unknown next to known properties, right to left.',
|
||||||
|
fixture: 'a{unknown-a: none;animation: 0;}',
|
||||||
|
expected: 'a{unknown-a: none;animation: 0;}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Sort shorthand, resulting in impactful ordering.',
|
||||||
|
fixture: 'a{border-width: 0;border-radius: 0;border-bottom: 1px;}',
|
||||||
|
expected: 'a{border-bottom: 1px;border-radius: 0;border-width: 0;}',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const commentOrderTests = [
|
||||||
|
{
|
||||||
|
message: 'Keep comment intact.',
|
||||||
|
fixture: 'a{flex: 0;/*flex*/}',
|
||||||
|
expected: 'a{flex: 0;/*flex*/}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Keep root comments intact.',
|
||||||
|
fixture: '/*a*/\na{}\n/*b*/\nb{}',
|
||||||
|
expected: '/*a*/\na{}\n/*b*/\nb{}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Handle declaration with one comment.',
|
||||||
|
fixture: 'a{/*comment*/}',
|
||||||
|
expected: 'a{/*comment*/}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Keep dangling comment intact.',
|
||||||
|
fixture: 'a{flex: 0;\n/*end*/}',
|
||||||
|
expected: 'a{flex: 0;\n/*end*/}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Keep multiple comments intact.',
|
||||||
|
fixture: 'a{flex: 0;\n/*flex*/\n/*flex 2*/}',
|
||||||
|
expected: 'a{flex: 0;\n/*flex*/\n/*flex 2*/}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Keep newline comment above declaration.',
|
||||||
|
fixture: 'a{flex: 0;\n/*border*/\nborder: 0;}',
|
||||||
|
expected: 'a{\n/*border*/\nborder: 0;flex: 0;}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Handle multiple newline comments.',
|
||||||
|
fixture: 'a{flex: 0;\n/*border a*/\n/*border b*/\nborder: 0;}',
|
||||||
|
expected: 'a{\n/*border a*/\n/*border b*/\nborder: 0;flex: 0;}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Keep inline comment beside declaration.',
|
||||||
|
fixture: 'a{flex: 0;\nborder: 0; /*border*/}',
|
||||||
|
expected: 'a{\nborder: 0; /*border*/flex: 0;}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Do not lose reference to paired comment node on one line.',
|
||||||
|
fixture: 'body{/*a*/border:0;/*b*/}',
|
||||||
|
expected: 'body{border:0;/*b*//*a*/}',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const nestedDeclarationTests = [
|
||||||
|
{
|
||||||
|
message: 'Sort nested declarations.',
|
||||||
|
fixture: 'a{a{flex: 0;border: 0;}}',
|
||||||
|
expected: 'a{a{border: 0;flex: 0;}}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Sort nested at-rule declarations.',
|
||||||
|
fixture: 'a{@media(){flex: 0;border: 0;}}',
|
||||||
|
expected: 'a{@media(){border: 0;flex: 0;}}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Keep nested newline comment above declaration.',
|
||||||
|
fixture: 'a{&:hover{flex: 0;\n/*border*/\nborder: 0;}}',
|
||||||
|
expected: 'a{&:hover{\n/*border*/\nborder: 0;flex: 0;}}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Keep nested inline comment beside declaration.',
|
||||||
|
fixture: 'a{&:hover{flex: 0;\nborder: 0; /*border*/}}',
|
||||||
|
expected: 'a{&:hover{\nborder: 0; /*border*/flex: 0;}}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Put declarations before nested selector.',
|
||||||
|
fixture: 'a{margin: 0;&:hover{color: red;}padding: 0;}',
|
||||||
|
expected: 'a{margin: 0;padding: 0;&:hover{color: red;}}',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const keepOverridesTests = [
|
||||||
|
{
|
||||||
|
message: 'Keep shorthand overrides in place.',
|
||||||
|
fixture: 'a{animation-name: hi;animation: hey 1s ease;}',
|
||||||
|
expected: 'a{animation-name: hi;animation: hey 1s ease;}',
|
||||||
|
options: { keepOverrides: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Keep longhand overrides in place.',
|
||||||
|
fixture: 'a{flex: 1;flex-grow: -1;}',
|
||||||
|
expected: 'a{flex: 1;flex-grow: -1;}',
|
||||||
|
options: { keepOverrides: true, order: () => -1 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Sort overrides with other declarations.',
|
||||||
|
fixture: 'a{z-index: 1;animation: hey 1s ease;}',
|
||||||
|
expected: 'a{animation: hey 1s ease;z-index: 1;}',
|
||||||
|
options: { keepOverrides: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Keep overrides in place mixed with declaration.',
|
||||||
|
fixture: 'a{z-index: 1;animation: hey 1s ease;animation-name: hi;}',
|
||||||
|
expected: 'a{animation: hey 1s ease;animation-name: hi;z-index: 1;}',
|
||||||
|
options: { keepOverrides: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Keep vendor prefixed declarations in place.',
|
||||||
|
fixture: 'a{animation: a;-moz-animation:b;}',
|
||||||
|
expected: 'a{animation: a;-moz-animation:b;}',
|
||||||
|
options: { keepOverrides: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Keep border declarations in place.',
|
||||||
|
fixture: 'a{border-top: 1px solid;border-color: purple;}',
|
||||||
|
expected:'a{border-top: 1px solid;border-color: purple;}',
|
||||||
|
options: { keepOverrides: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Keep padding declarations in place.',
|
||||||
|
fixture: 'a{padding-left: unset;padding-inline-start: 0;}',
|
||||||
|
expected:'a{padding-left: unset;padding-inline-start: 0;}',
|
||||||
|
options: { keepOverrides: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Keep border block declarations in place.',
|
||||||
|
fixture: 'a{border-block-end: 1px solid purple;border-block: none;}',
|
||||||
|
expected: 'a{border-block-end: 1px solid purple;border-block: none;}',
|
||||||
|
options: { keepOverrides: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Keep border block style declarations in place.',
|
||||||
|
fixture: 'a{border-style: none;border-block-end: 1px solid purple;}',
|
||||||
|
expected: 'a{border-style: none;border-block-end: 1px solid purple;}',
|
||||||
|
options: { keepOverrides: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Keep border width logical property declarations in place.',
|
||||||
|
fixture: 'a{background: grey;border-width: 0;border-top-width: 1px;border-inline-start-width: 1px;}',
|
||||||
|
expected: 'a{background: grey;border-width: 0;border-inline-start-width: 1px;border-top-width: 1px;}',
|
||||||
|
options: { keepOverrides: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Keep longhand border style declaration in place.',
|
||||||
|
fixture: 'a{border-width: 0;border-radius: 0;border-bottom: 1px;}',
|
||||||
|
expected: 'a{border-radius: 0;border-width: 0;border-bottom: 1px;}',
|
||||||
|
options: { keepOverrides: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: 'Keep longhand border logical declaration in place.',
|
||||||
|
fixture: 'a{border-radius: 5px;border-end-start-radius: 0;border-end-end-radius: 0;}',
|
||||||
|
expected: 'a{border-radius: 5px;border-end-end-radius: 0;border-end-start-radius: 0;}',
|
||||||
|
options: { keepOverrides: true },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
testCssFixtures('Should order declarations.', sortOrderTests);
|
||||||
|
|
||||||
|
testCssFixtures('Should retain comments.', commentOrderTests);
|
||||||
|
|
||||||
|
testCssFixtures('Should order nested declarations.', nestedDeclarationTests);
|
||||||
|
|
||||||
|
testCssFixtures('Should keep shorthand override order.', keepOverridesTests);
|
||||||
|
|
||||||
|
test('Should use the PostCSS plugin API.', () => {
|
||||||
|
assert.is(plugin().postcssPlugin, 'css-declaration-sorter', 'Able to access name.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test.run();
|
||||||
493
node_modules/css-declaration-sorter/src/core/shorthand-data.mjs
generated
vendored
Normal file
493
node_modules/css-declaration-sorter/src/core/shorthand-data.mjs
generated
vendored
Normal file
@@ -0,0 +1,493 @@
|
|||||||
|
export const shorthandData = {
|
||||||
|
'animation': [
|
||||||
|
'animation-duration',
|
||||||
|
'animation-timing-function',
|
||||||
|
'animation-delay',
|
||||||
|
'animation-iteration-count',
|
||||||
|
'animation-direction',
|
||||||
|
'animation-fill-mode',
|
||||||
|
'animation-play-state',
|
||||||
|
'animation-name',
|
||||||
|
'animation-timeline',
|
||||||
|
],
|
||||||
|
'animation-range': [
|
||||||
|
'animation-range-start',
|
||||||
|
'animation-range-end',
|
||||||
|
],
|
||||||
|
'background': [
|
||||||
|
'background-image',
|
||||||
|
'background-position',
|
||||||
|
'background-size',
|
||||||
|
'background-repeat',
|
||||||
|
'background-attachment',
|
||||||
|
'background-origin',
|
||||||
|
'background-clip',
|
||||||
|
'background-color',
|
||||||
|
],
|
||||||
|
'columns': [
|
||||||
|
'column-width',
|
||||||
|
'column-count',
|
||||||
|
],
|
||||||
|
'column-rule': [
|
||||||
|
'column-rule-width',
|
||||||
|
'column-rule-style',
|
||||||
|
'column-rule-color',
|
||||||
|
],
|
||||||
|
'contain-intrinsic-size': [
|
||||||
|
'contain-intrinsic-width',
|
||||||
|
'contain-intrinsic-height',
|
||||||
|
],
|
||||||
|
'flex': [
|
||||||
|
'flex-grow',
|
||||||
|
'flex-shrink',
|
||||||
|
'flex-basis',
|
||||||
|
],
|
||||||
|
'flex-flow': [
|
||||||
|
'flex-direction',
|
||||||
|
'flex-wrap',
|
||||||
|
],
|
||||||
|
'font': [
|
||||||
|
'font-style',
|
||||||
|
'font-variant',
|
||||||
|
'font-weight',
|
||||||
|
'font-stretch',
|
||||||
|
'font-size',
|
||||||
|
'font-family',
|
||||||
|
'line-height',
|
||||||
|
],
|
||||||
|
'gap': [
|
||||||
|
'column-gap',
|
||||||
|
'row-gap',
|
||||||
|
],
|
||||||
|
'grid': [
|
||||||
|
'grid-template-rows',
|
||||||
|
'grid-template-columns',
|
||||||
|
'grid-template-areas',
|
||||||
|
'grid-auto-rows',
|
||||||
|
'grid-auto-columns',
|
||||||
|
'grid-auto-flow',
|
||||||
|
'column-gap',
|
||||||
|
'row-gap',
|
||||||
|
],
|
||||||
|
'grid-area': [
|
||||||
|
'grid-row-start',
|
||||||
|
'grid-column-start',
|
||||||
|
'grid-row-end',
|
||||||
|
'grid-column-end',
|
||||||
|
],
|
||||||
|
'grid-column': [
|
||||||
|
'grid-column-start',
|
||||||
|
'grid-column-end',
|
||||||
|
],
|
||||||
|
'grid-row': [
|
||||||
|
'grid-row-start',
|
||||||
|
'grid-row-end',
|
||||||
|
],
|
||||||
|
'grid-template': [
|
||||||
|
'grid-template-columns',
|
||||||
|
'grid-template-rows',
|
||||||
|
'grid-template-areas',
|
||||||
|
],
|
||||||
|
'list-style': [
|
||||||
|
'list-style-type',
|
||||||
|
'list-style-position',
|
||||||
|
'list-style-image',
|
||||||
|
],
|
||||||
|
'offset': [
|
||||||
|
'offset-anchor',
|
||||||
|
'offset-distance',
|
||||||
|
'offset-path',
|
||||||
|
'offset-position',
|
||||||
|
'offset-rotate',
|
||||||
|
],
|
||||||
|
'padding': [
|
||||||
|
'padding-block',
|
||||||
|
'padding-block-start',
|
||||||
|
'padding-block-end',
|
||||||
|
'padding-inline',
|
||||||
|
'padding-inline-start',
|
||||||
|
'padding-inline-end',
|
||||||
|
'padding-top',
|
||||||
|
'padding-right',
|
||||||
|
'padding-bottom',
|
||||||
|
'padding-left',
|
||||||
|
],
|
||||||
|
'padding-block': [
|
||||||
|
'padding-block-start',
|
||||||
|
'padding-block-end',
|
||||||
|
'padding-top',
|
||||||
|
'padding-right',
|
||||||
|
'padding-bottom',
|
||||||
|
'padding-left',
|
||||||
|
],
|
||||||
|
'padding-block-start': [
|
||||||
|
'padding-top',
|
||||||
|
'padding-right',
|
||||||
|
'padding-left',
|
||||||
|
],
|
||||||
|
'padding-block-end': [
|
||||||
|
'padding-right',
|
||||||
|
'padding-bottom',
|
||||||
|
'padding-left',
|
||||||
|
],
|
||||||
|
'padding-inline': [
|
||||||
|
'padding-inline-start',
|
||||||
|
'padding-inline-end',
|
||||||
|
'padding-top',
|
||||||
|
'padding-right',
|
||||||
|
'padding-bottom',
|
||||||
|
'padding-left',
|
||||||
|
],
|
||||||
|
'padding-inline-start': [
|
||||||
|
'padding-top',
|
||||||
|
'padding-right',
|
||||||
|
'padding-left',
|
||||||
|
],
|
||||||
|
'padding-inline-end': [
|
||||||
|
'padding-right',
|
||||||
|
'padding-bottom',
|
||||||
|
'padding-left',
|
||||||
|
],
|
||||||
|
'position-try': [
|
||||||
|
'position-try-order',
|
||||||
|
'position-try-fallbacks',
|
||||||
|
],
|
||||||
|
'scroll-timeline': [
|
||||||
|
'scroll-timeline-name',
|
||||||
|
'scroll-timeline-axis',
|
||||||
|
],
|
||||||
|
'margin': [
|
||||||
|
'margin-block',
|
||||||
|
'margin-block-start',
|
||||||
|
'margin-block-end',
|
||||||
|
'margin-inline',
|
||||||
|
'margin-inline-start',
|
||||||
|
'margin-inline-end',
|
||||||
|
'margin-top',
|
||||||
|
'margin-right',
|
||||||
|
'margin-bottom',
|
||||||
|
'margin-left',
|
||||||
|
],
|
||||||
|
'margin-block': [
|
||||||
|
'margin-block-start',
|
||||||
|
'margin-block-end',
|
||||||
|
'margin-top',
|
||||||
|
'margin-right',
|
||||||
|
'margin-bottom',
|
||||||
|
'margin-left',
|
||||||
|
],
|
||||||
|
'margin-inline': [
|
||||||
|
'margin-inline-start',
|
||||||
|
'margin-inline-end',
|
||||||
|
'margin-top',
|
||||||
|
'margin-right',
|
||||||
|
'margin-bottom',
|
||||||
|
'margin-left',
|
||||||
|
],
|
||||||
|
'margin-inline-start': [
|
||||||
|
'margin-top',
|
||||||
|
'margin-right',
|
||||||
|
'margin-bottom',
|
||||||
|
'margin-left',
|
||||||
|
],
|
||||||
|
'margin-inline-end': [
|
||||||
|
'margin-top',
|
||||||
|
'margin-right',
|
||||||
|
'margin-bottom',
|
||||||
|
'margin-left',
|
||||||
|
],
|
||||||
|
'marker': [
|
||||||
|
'marker-start',
|
||||||
|
'marker-mid',
|
||||||
|
'marker-end',
|
||||||
|
],
|
||||||
|
'view-timeline': [
|
||||||
|
'view-timeline-name',
|
||||||
|
'view-timeline-axis',
|
||||||
|
'view-timeline-inset',
|
||||||
|
],
|
||||||
|
'border': [
|
||||||
|
'border-top',
|
||||||
|
'border-right',
|
||||||
|
'border-bottom',
|
||||||
|
'border-left',
|
||||||
|
'border-width',
|
||||||
|
'border-style',
|
||||||
|
'border-color',
|
||||||
|
'border-top-width',
|
||||||
|
'border-right-width',
|
||||||
|
'border-bottom-width',
|
||||||
|
'border-left-width',
|
||||||
|
'border-inline-start-width',
|
||||||
|
'border-inline-end-width',
|
||||||
|
'border-block-start-width',
|
||||||
|
'border-block-end-width',
|
||||||
|
'border-top-style',
|
||||||
|
'border-right-style',
|
||||||
|
'border-bottom-style',
|
||||||
|
'border-left-style',
|
||||||
|
'border-inline-start-style',
|
||||||
|
'border-inline-end-style',
|
||||||
|
'border-block-start-style',
|
||||||
|
'border-block-end-style',
|
||||||
|
'border-top-color',
|
||||||
|
'border-right-color',
|
||||||
|
'border-bottom-color',
|
||||||
|
'border-left-color',
|
||||||
|
'border-inline-start-color',
|
||||||
|
'border-inline-end-color',
|
||||||
|
'border-block-start-color',
|
||||||
|
'border-block-end-color',
|
||||||
|
'border-block',
|
||||||
|
'border-block-start',
|
||||||
|
'border-block-end',
|
||||||
|
'border-block-width',
|
||||||
|
'border-block-style',
|
||||||
|
'border-block-color',
|
||||||
|
'border-inline',
|
||||||
|
'border-inline-start',
|
||||||
|
'border-inline-end',
|
||||||
|
'border-inline-width',
|
||||||
|
'border-inline-style',
|
||||||
|
'border-inline-color',
|
||||||
|
],
|
||||||
|
'border-top': [
|
||||||
|
'border-width',
|
||||||
|
'border-style',
|
||||||
|
'border-color',
|
||||||
|
'border-top-width',
|
||||||
|
'border-top-style',
|
||||||
|
'border-top-color',
|
||||||
|
],
|
||||||
|
'border-right': [
|
||||||
|
'border-width',
|
||||||
|
'border-style',
|
||||||
|
'border-color',
|
||||||
|
'border-right-width',
|
||||||
|
'border-right-style',
|
||||||
|
'border-right-color',
|
||||||
|
],
|
||||||
|
'border-bottom': [
|
||||||
|
'border-width',
|
||||||
|
'border-style',
|
||||||
|
'border-color',
|
||||||
|
'border-bottom-width',
|
||||||
|
'border-bottom-style',
|
||||||
|
'border-bottom-color',
|
||||||
|
],
|
||||||
|
'border-left': [
|
||||||
|
'border-width',
|
||||||
|
'border-style',
|
||||||
|
'border-color',
|
||||||
|
'border-left-width',
|
||||||
|
'border-left-style',
|
||||||
|
'border-left-color',
|
||||||
|
],
|
||||||
|
'border-color': [
|
||||||
|
'border-top-color',
|
||||||
|
'border-bottom-color',
|
||||||
|
'border-left-color',
|
||||||
|
'border-right-color',
|
||||||
|
'border-inline-start-color',
|
||||||
|
'border-inline-end-color',
|
||||||
|
'border-block-start-color',
|
||||||
|
'border-block-end-color',
|
||||||
|
],
|
||||||
|
'border-width': [
|
||||||
|
'border-top-width',
|
||||||
|
'border-bottom-width',
|
||||||
|
'border-left-width',
|
||||||
|
'border-right-width',
|
||||||
|
'border-inline-start-width',
|
||||||
|
'border-inline-end-width',
|
||||||
|
'border-block-start-width',
|
||||||
|
'border-block-end-width',
|
||||||
|
],
|
||||||
|
'border-style': [
|
||||||
|
'border-top-style',
|
||||||
|
'border-bottom-style',
|
||||||
|
'border-left-style',
|
||||||
|
'border-right-style',
|
||||||
|
'border-inline-start-style',
|
||||||
|
'border-inline-end-style',
|
||||||
|
'border-block-start-style',
|
||||||
|
'border-block-end-style',
|
||||||
|
],
|
||||||
|
'border-radius': [
|
||||||
|
'border-top-right-radius',
|
||||||
|
'border-top-left-radius',
|
||||||
|
'border-bottom-right-radius',
|
||||||
|
'border-bottom-left-radius',
|
||||||
|
'border-end-end-radius',
|
||||||
|
'border-end-start-radius',
|
||||||
|
'border-start-end-radius',
|
||||||
|
'border-start-start-radius',
|
||||||
|
],
|
||||||
|
'border-block': [
|
||||||
|
'border-block-start',
|
||||||
|
'border-block-end',
|
||||||
|
'border-block-width',
|
||||||
|
'border-width',
|
||||||
|
'border-block-style',
|
||||||
|
'border-style',
|
||||||
|
'border-block-color',
|
||||||
|
'border-color',
|
||||||
|
],
|
||||||
|
'border-block-start': [
|
||||||
|
'border-block-start-width',
|
||||||
|
'border-width',
|
||||||
|
'border-block-start-style',
|
||||||
|
'border-style',
|
||||||
|
'border-block-start-color',
|
||||||
|
'border-color',
|
||||||
|
],
|
||||||
|
'border-block-end': [
|
||||||
|
'border-block-end-width',
|
||||||
|
'border-width',
|
||||||
|
'border-block-end-style',
|
||||||
|
'border-style',
|
||||||
|
'border-block-end-color',
|
||||||
|
'border-color',
|
||||||
|
],
|
||||||
|
'border-inline': [
|
||||||
|
'border-inline-start',
|
||||||
|
'border-inline-end',
|
||||||
|
'border-inline-width',
|
||||||
|
'border-width',
|
||||||
|
'border-inline-style',
|
||||||
|
'border-style',
|
||||||
|
'border-inline-color',
|
||||||
|
'border-color',
|
||||||
|
],
|
||||||
|
'border-inline-start': [
|
||||||
|
'border-inline-start-width',
|
||||||
|
'border-width',
|
||||||
|
'border-inline-start-style',
|
||||||
|
'border-style',
|
||||||
|
'border-inline-start-color',
|
||||||
|
'border-color',
|
||||||
|
],
|
||||||
|
'border-inline-end': [
|
||||||
|
'border-inline-end-width',
|
||||||
|
'border-width',
|
||||||
|
'border-inline-end-style',
|
||||||
|
'border-style',
|
||||||
|
'border-inline-end-color',
|
||||||
|
'border-color',
|
||||||
|
],
|
||||||
|
'border-image': [
|
||||||
|
'border-image-source',
|
||||||
|
'border-image-slice',
|
||||||
|
'border-image-width',
|
||||||
|
'border-image-outset',
|
||||||
|
'border-image-repeat',
|
||||||
|
],
|
||||||
|
'mask': [
|
||||||
|
'mask-image',
|
||||||
|
'mask-mode',
|
||||||
|
'mask-position',
|
||||||
|
'mask-size',
|
||||||
|
'mask-repeat',
|
||||||
|
'mask-origin',
|
||||||
|
'mask-clip',
|
||||||
|
'mask-composite',
|
||||||
|
],
|
||||||
|
'inline-size': [
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
],
|
||||||
|
'block-size': [
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
],
|
||||||
|
'max-inline-size': [
|
||||||
|
'max-width',
|
||||||
|
'max-height',
|
||||||
|
],
|
||||||
|
'max-block-size': [
|
||||||
|
'max-width',
|
||||||
|
'max-height',
|
||||||
|
],
|
||||||
|
'inset': [
|
||||||
|
'inset-block',
|
||||||
|
'inset-block-start',
|
||||||
|
'inset-block-end',
|
||||||
|
'inset-inline',
|
||||||
|
'inset-inline-start',
|
||||||
|
'inset-inline-end',
|
||||||
|
'top',
|
||||||
|
'right',
|
||||||
|
'bottom',
|
||||||
|
'left',
|
||||||
|
],
|
||||||
|
'inset-block': [
|
||||||
|
'inset-block-start',
|
||||||
|
'inset-block-end',
|
||||||
|
'top',
|
||||||
|
'right',
|
||||||
|
'bottom',
|
||||||
|
'left',
|
||||||
|
],
|
||||||
|
'inset-inline': [
|
||||||
|
'inset-inline-start',
|
||||||
|
'inset-inline-end',
|
||||||
|
'top',
|
||||||
|
'right',
|
||||||
|
'bottom',
|
||||||
|
'left',
|
||||||
|
],
|
||||||
|
'outline': [
|
||||||
|
'outline-color',
|
||||||
|
'outline-style',
|
||||||
|
'outline-width',
|
||||||
|
],
|
||||||
|
'overflow': [
|
||||||
|
'overflow-x',
|
||||||
|
'overflow-y',
|
||||||
|
],
|
||||||
|
'place-content': [
|
||||||
|
'align-content',
|
||||||
|
'justify-content',
|
||||||
|
],
|
||||||
|
'place-items': [
|
||||||
|
'align-items',
|
||||||
|
'justify-items',
|
||||||
|
],
|
||||||
|
'place-self': [
|
||||||
|
'align-self',
|
||||||
|
'justify-self',
|
||||||
|
],
|
||||||
|
'text-box': [
|
||||||
|
'text-box-trim',
|
||||||
|
'text-box-edge',
|
||||||
|
],
|
||||||
|
'text-decoration': [
|
||||||
|
'text-decoration-color',
|
||||||
|
'text-decoration-style',
|
||||||
|
'text-decoration-line',
|
||||||
|
],
|
||||||
|
'text-wrap': [
|
||||||
|
'text-wrap-mode',
|
||||||
|
'text-wrap-style',
|
||||||
|
],
|
||||||
|
'transition': [
|
||||||
|
'transition-delay',
|
||||||
|
'transition-duration',
|
||||||
|
'transition-property',
|
||||||
|
'transition-timing-function',
|
||||||
|
],
|
||||||
|
'text-emphasis': [
|
||||||
|
'text-emphasis-style',
|
||||||
|
'text-emphasis-color',
|
||||||
|
],
|
||||||
|
'font-synthesis': [
|
||||||
|
'font-synthesis-weight',
|
||||||
|
'font-synthesis-style',
|
||||||
|
'font-synthesis-small-caps',
|
||||||
|
'font-synthesis-position',
|
||||||
|
],
|
||||||
|
'-webkit-text-stroke': [
|
||||||
|
'-webkit-text-stroke-color',
|
||||||
|
'-webkit-text-stroke-width',
|
||||||
|
],
|
||||||
|
};
|
||||||
471
node_modules/css-declaration-sorter/src/orders/alphabetical.mjs
generated
vendored
Normal file
471
node_modules/css-declaration-sorter/src/orders/alphabetical.mjs
generated
vendored
Normal file
@@ -0,0 +1,471 @@
|
|||||||
|
export const properties = [
|
||||||
|
'all',
|
||||||
|
'-webkit-text-fill-color',
|
||||||
|
'-webkit-text-stroke',
|
||||||
|
'-webkit-text-stroke-color',
|
||||||
|
'-webkit-text-stroke-width',
|
||||||
|
'accent-color',
|
||||||
|
'align-content',
|
||||||
|
'align-items',
|
||||||
|
'align-self',
|
||||||
|
'alignment-baseline',
|
||||||
|
'anchor-name',
|
||||||
|
'animation',
|
||||||
|
'animation-composition',
|
||||||
|
'animation-delay',
|
||||||
|
'animation-direction',
|
||||||
|
'animation-duration',
|
||||||
|
'animation-fill-mode',
|
||||||
|
'animation-iteration-count',
|
||||||
|
'animation-name',
|
||||||
|
'animation-play-state',
|
||||||
|
'animation-range',
|
||||||
|
'animation-range-end',
|
||||||
|
'animation-range-start',
|
||||||
|
'animation-timeline',
|
||||||
|
'animation-timing-function',
|
||||||
|
'appearance',
|
||||||
|
'ascent-override',
|
||||||
|
'aspect-ratio',
|
||||||
|
'backdrop-filter',
|
||||||
|
'backface-visibility',
|
||||||
|
'background',
|
||||||
|
'background-attachment',
|
||||||
|
'background-blend-mode',
|
||||||
|
'background-clip',
|
||||||
|
'background-color',
|
||||||
|
'background-image',
|
||||||
|
'background-origin',
|
||||||
|
'background-position',
|
||||||
|
'background-position-x',
|
||||||
|
'background-position-y',
|
||||||
|
'background-repeat',
|
||||||
|
'background-size',
|
||||||
|
'block-size',
|
||||||
|
'border',
|
||||||
|
'border-block',
|
||||||
|
'border-block-color',
|
||||||
|
'border-block-end',
|
||||||
|
'border-block-end-color',
|
||||||
|
'border-block-end-style',
|
||||||
|
'border-block-end-width',
|
||||||
|
'border-block-start',
|
||||||
|
'border-block-start-color',
|
||||||
|
'border-block-start-style',
|
||||||
|
'border-block-start-width',
|
||||||
|
'border-block-style',
|
||||||
|
'border-block-width',
|
||||||
|
'border-bottom',
|
||||||
|
'border-bottom-color',
|
||||||
|
'border-bottom-left-radius',
|
||||||
|
'border-bottom-right-radius',
|
||||||
|
'border-bottom-style',
|
||||||
|
'border-bottom-width',
|
||||||
|
'border-collapse',
|
||||||
|
'border-color',
|
||||||
|
'border-end-end-radius',
|
||||||
|
'border-end-start-radius',
|
||||||
|
'border-image',
|
||||||
|
'border-image-outset',
|
||||||
|
'border-image-repeat',
|
||||||
|
'border-image-slice',
|
||||||
|
'border-image-source',
|
||||||
|
'border-image-width',
|
||||||
|
'border-inline',
|
||||||
|
'border-inline-color',
|
||||||
|
'border-inline-end',
|
||||||
|
'border-inline-end-color',
|
||||||
|
'border-inline-end-style',
|
||||||
|
'border-inline-end-width',
|
||||||
|
'border-inline-start',
|
||||||
|
'border-inline-start-color',
|
||||||
|
'border-inline-start-style',
|
||||||
|
'border-inline-start-width',
|
||||||
|
'border-inline-style',
|
||||||
|
'border-inline-width',
|
||||||
|
'border-left',
|
||||||
|
'border-left-color',
|
||||||
|
'border-left-style',
|
||||||
|
'border-left-width',
|
||||||
|
'border-radius',
|
||||||
|
'border-right',
|
||||||
|
'border-right-color',
|
||||||
|
'border-right-style',
|
||||||
|
'border-right-width',
|
||||||
|
'border-spacing',
|
||||||
|
'border-start-end-radius',
|
||||||
|
'border-start-start-radius',
|
||||||
|
'border-style',
|
||||||
|
'border-top',
|
||||||
|
'border-top-color',
|
||||||
|
'border-top-left-radius',
|
||||||
|
'border-top-right-radius',
|
||||||
|
'border-top-style',
|
||||||
|
'border-top-width',
|
||||||
|
'border-width',
|
||||||
|
'bottom',
|
||||||
|
'box-decoration-break',
|
||||||
|
'box-shadow',
|
||||||
|
'box-sizing',
|
||||||
|
'break-after',
|
||||||
|
'break-before',
|
||||||
|
'break-inside',
|
||||||
|
'caption-side',
|
||||||
|
'caret-color',
|
||||||
|
'clear',
|
||||||
|
'clip-path',
|
||||||
|
'clip-rule',
|
||||||
|
'color',
|
||||||
|
'color-interpolation',
|
||||||
|
'color-interpolation-filters',
|
||||||
|
'color-scheme',
|
||||||
|
'column-count',
|
||||||
|
'column-fill',
|
||||||
|
'column-gap',
|
||||||
|
'column-rule',
|
||||||
|
'column-rule-color',
|
||||||
|
'column-rule-style',
|
||||||
|
'column-rule-width',
|
||||||
|
'column-span',
|
||||||
|
'column-width',
|
||||||
|
'columns',
|
||||||
|
'contain',
|
||||||
|
'contain-intrinsic-block-size',
|
||||||
|
'contain-intrinsic-height',
|
||||||
|
'contain-intrinsic-inline-size',
|
||||||
|
'contain-intrinsic-size',
|
||||||
|
'contain-intrinsic-width',
|
||||||
|
'container',
|
||||||
|
'container-name',
|
||||||
|
'container-type',
|
||||||
|
'content',
|
||||||
|
'content-visibility',
|
||||||
|
'counter-increment',
|
||||||
|
'counter-reset',
|
||||||
|
'counter-set',
|
||||||
|
'cursor',
|
||||||
|
'cx',
|
||||||
|
'cy',
|
||||||
|
'd',
|
||||||
|
'descent-override',
|
||||||
|
'direction',
|
||||||
|
'display',
|
||||||
|
'dominant-baseline',
|
||||||
|
'empty-cells',
|
||||||
|
'fill',
|
||||||
|
'fill-opacity',
|
||||||
|
'fill-rule',
|
||||||
|
'filter',
|
||||||
|
'flex',
|
||||||
|
'flex-basis',
|
||||||
|
'flex-direction',
|
||||||
|
'flex-flow',
|
||||||
|
'flex-grow',
|
||||||
|
'flex-shrink',
|
||||||
|
'flex-wrap',
|
||||||
|
'float',
|
||||||
|
'flood-color',
|
||||||
|
'flood-opacity',
|
||||||
|
'font',
|
||||||
|
'font-display',
|
||||||
|
'font-family',
|
||||||
|
'font-feature-settings',
|
||||||
|
'font-kerning',
|
||||||
|
'font-language-override',
|
||||||
|
'font-optical-sizing',
|
||||||
|
'font-palette',
|
||||||
|
'font-size',
|
||||||
|
'font-size-adjust',
|
||||||
|
'font-stretch',
|
||||||
|
'font-style',
|
||||||
|
'font-synthesis',
|
||||||
|
'font-synthesis-position',
|
||||||
|
'font-synthesis-small-caps',
|
||||||
|
'font-synthesis-style',
|
||||||
|
'font-synthesis-weight',
|
||||||
|
'font-variant',
|
||||||
|
'font-variant-alternates',
|
||||||
|
'font-variant-caps',
|
||||||
|
'font-variant-east-asian',
|
||||||
|
'font-variant-emoji',
|
||||||
|
'font-variant-ligatures',
|
||||||
|
'font-variant-numeric',
|
||||||
|
'font-variant-position',
|
||||||
|
'font-variation-settings',
|
||||||
|
'font-weight',
|
||||||
|
'forced-color-adjust',
|
||||||
|
'gap',
|
||||||
|
'grid',
|
||||||
|
'grid-area',
|
||||||
|
'grid-auto-columns',
|
||||||
|
'grid-auto-flow',
|
||||||
|
'grid-auto-rows',
|
||||||
|
'grid-column',
|
||||||
|
'grid-column-end',
|
||||||
|
'grid-column-start',
|
||||||
|
'grid-row',
|
||||||
|
'grid-row-end',
|
||||||
|
'grid-row-start',
|
||||||
|
'grid-template',
|
||||||
|
'grid-template-areas',
|
||||||
|
'grid-template-columns',
|
||||||
|
'grid-template-rows',
|
||||||
|
'hanging-punctuation',
|
||||||
|
'height',
|
||||||
|
'hyphenate-character',
|
||||||
|
'hyphenate-limit-chars',
|
||||||
|
'hyphens',
|
||||||
|
'image-orientation',
|
||||||
|
'image-rendering',
|
||||||
|
'initial-letter',
|
||||||
|
'inline-size',
|
||||||
|
'inset',
|
||||||
|
'inset-block',
|
||||||
|
'inset-block-end',
|
||||||
|
'inset-block-start',
|
||||||
|
'inset-inline',
|
||||||
|
'inset-inline-end',
|
||||||
|
'inset-inline-start',
|
||||||
|
'isolation',
|
||||||
|
'justify-content',
|
||||||
|
'justify-items',
|
||||||
|
'justify-self',
|
||||||
|
'left',
|
||||||
|
'letter-spacing',
|
||||||
|
'lighting-color',
|
||||||
|
'line-break',
|
||||||
|
'line-clamp',
|
||||||
|
'line-gap-override',
|
||||||
|
'line-height',
|
||||||
|
'list-style',
|
||||||
|
'list-style-image',
|
||||||
|
'list-style-position',
|
||||||
|
'list-style-type',
|
||||||
|
'margin',
|
||||||
|
'margin-block',
|
||||||
|
'margin-block-end',
|
||||||
|
'margin-block-start',
|
||||||
|
'margin-bottom',
|
||||||
|
'margin-inline',
|
||||||
|
'margin-inline-end',
|
||||||
|
'margin-inline-start',
|
||||||
|
'margin-left',
|
||||||
|
'margin-right',
|
||||||
|
'margin-top',
|
||||||
|
'marker',
|
||||||
|
'marker-end',
|
||||||
|
'marker-mid',
|
||||||
|
'marker-start',
|
||||||
|
'mask',
|
||||||
|
'mask-border',
|
||||||
|
'mask-border-outset',
|
||||||
|
'mask-border-repeat',
|
||||||
|
'mask-border-slice',
|
||||||
|
'mask-border-source',
|
||||||
|
'mask-border-width',
|
||||||
|
'mask-clip',
|
||||||
|
'mask-composite',
|
||||||
|
'mask-image',
|
||||||
|
'mask-mode',
|
||||||
|
'mask-origin',
|
||||||
|
'mask-position',
|
||||||
|
'mask-repeat',
|
||||||
|
'mask-size',
|
||||||
|
'mask-type',
|
||||||
|
'math-depth',
|
||||||
|
'math-style',
|
||||||
|
'max-block-size',
|
||||||
|
'max-height',
|
||||||
|
'max-inline-size',
|
||||||
|
'max-width',
|
||||||
|
'min-block-size',
|
||||||
|
'min-height',
|
||||||
|
'min-inline-size',
|
||||||
|
'min-width',
|
||||||
|
'mix-blend-mode',
|
||||||
|
'object-fit',
|
||||||
|
'object-position',
|
||||||
|
'offset',
|
||||||
|
'offset-anchor',
|
||||||
|
'offset-distance',
|
||||||
|
'offset-path',
|
||||||
|
'offset-position',
|
||||||
|
'offset-rotate',
|
||||||
|
'opacity',
|
||||||
|
'order',
|
||||||
|
'orphans',
|
||||||
|
'outline',
|
||||||
|
'outline-color',
|
||||||
|
'outline-offset',
|
||||||
|
'outline-style',
|
||||||
|
'outline-width',
|
||||||
|
'overflow',
|
||||||
|
'overflow-anchor',
|
||||||
|
'overflow-block',
|
||||||
|
'overflow-clip-margin',
|
||||||
|
'overflow-inline',
|
||||||
|
'overflow-wrap',
|
||||||
|
'overflow-x',
|
||||||
|
'overflow-y',
|
||||||
|
'overscroll-behavior',
|
||||||
|
'overscroll-behavior-block',
|
||||||
|
'overscroll-behavior-inline',
|
||||||
|
'overscroll-behavior-x',
|
||||||
|
'overscroll-behavior-y',
|
||||||
|
'padding',
|
||||||
|
'padding-block',
|
||||||
|
'padding-block-end',
|
||||||
|
'padding-block-start',
|
||||||
|
'padding-bottom',
|
||||||
|
'padding-inline',
|
||||||
|
'padding-inline-end',
|
||||||
|
'padding-inline-start',
|
||||||
|
'padding-left',
|
||||||
|
'padding-right',
|
||||||
|
'padding-top',
|
||||||
|
'page',
|
||||||
|
'paint-order',
|
||||||
|
'perspective',
|
||||||
|
'perspective-origin',
|
||||||
|
'place-content',
|
||||||
|
'place-items',
|
||||||
|
'place-self',
|
||||||
|
'pointer-events',
|
||||||
|
'position',
|
||||||
|
'position-anchor',
|
||||||
|
'position-area',
|
||||||
|
'position-try',
|
||||||
|
'position-try-fallbacks',
|
||||||
|
'position-try-order',
|
||||||
|
'print-color-adjust',
|
||||||
|
'quotes',
|
||||||
|
'r',
|
||||||
|
'resize',
|
||||||
|
'right',
|
||||||
|
'rotate',
|
||||||
|
'row-gap',
|
||||||
|
'ruby-align',
|
||||||
|
'ruby-overhang',
|
||||||
|
'ruby-position',
|
||||||
|
'rx',
|
||||||
|
'ry',
|
||||||
|
'scale',
|
||||||
|
'scroll-behavior',
|
||||||
|
'scroll-margin',
|
||||||
|
'scroll-margin-block',
|
||||||
|
'scroll-margin-block-end',
|
||||||
|
'scroll-margin-block-start',
|
||||||
|
'scroll-margin-bottom',
|
||||||
|
'scroll-margin-inline',
|
||||||
|
'scroll-margin-inline-end',
|
||||||
|
'scroll-margin-inline-start',
|
||||||
|
'scroll-margin-left',
|
||||||
|
'scroll-margin-right',
|
||||||
|
'scroll-margin-top',
|
||||||
|
'scroll-padding',
|
||||||
|
'scroll-padding-block',
|
||||||
|
'scroll-padding-block-end',
|
||||||
|
'scroll-padding-block-start',
|
||||||
|
'scroll-padding-bottom',
|
||||||
|
'scroll-padding-inline',
|
||||||
|
'scroll-padding-inline-end',
|
||||||
|
'scroll-padding-inline-start',
|
||||||
|
'scroll-padding-left',
|
||||||
|
'scroll-padding-right',
|
||||||
|
'scroll-padding-top',
|
||||||
|
'scroll-snap-align',
|
||||||
|
'scroll-snap-stop',
|
||||||
|
'scroll-snap-type',
|
||||||
|
'scroll-timeline',
|
||||||
|
'scroll-timeline-axis',
|
||||||
|
'scroll-timeline-name',
|
||||||
|
'scrollbar-color',
|
||||||
|
'scrollbar-gutter',
|
||||||
|
'scrollbar-width',
|
||||||
|
'shape-image-threshold',
|
||||||
|
'shape-margin',
|
||||||
|
'shape-outside',
|
||||||
|
'shape-rendering',
|
||||||
|
'size-adjust',
|
||||||
|
'src',
|
||||||
|
'stop-color',
|
||||||
|
'stop-opacity',
|
||||||
|
'stroke',
|
||||||
|
'stroke-dasharray',
|
||||||
|
'stroke-dashoffset',
|
||||||
|
'stroke-linecap',
|
||||||
|
'stroke-linejoin',
|
||||||
|
'stroke-miterlimit',
|
||||||
|
'stroke-opacity',
|
||||||
|
'stroke-width',
|
||||||
|
'tab-size',
|
||||||
|
'table-layout',
|
||||||
|
'text-align',
|
||||||
|
'text-align-last',
|
||||||
|
'text-anchor',
|
||||||
|
'text-autospace',
|
||||||
|
'text-box',
|
||||||
|
'text-box-edge',
|
||||||
|
'text-box-trim',
|
||||||
|
'text-combine-upright',
|
||||||
|
'text-decoration',
|
||||||
|
'text-decoration-color',
|
||||||
|
'text-decoration-line',
|
||||||
|
'text-decoration-skip-ink',
|
||||||
|
'text-decoration-style',
|
||||||
|
'text-decoration-thickness',
|
||||||
|
'text-emphasis',
|
||||||
|
'text-emphasis-color',
|
||||||
|
'text-emphasis-position',
|
||||||
|
'text-emphasis-style',
|
||||||
|
'text-indent',
|
||||||
|
'text-justify',
|
||||||
|
'text-orientation',
|
||||||
|
'text-overflow',
|
||||||
|
'text-rendering',
|
||||||
|
'text-shadow',
|
||||||
|
'text-transform',
|
||||||
|
'text-underline-offset',
|
||||||
|
'text-underline-position',
|
||||||
|
'text-wrap',
|
||||||
|
'text-wrap-mode',
|
||||||
|
'text-wrap-style',
|
||||||
|
'timeline-scope',
|
||||||
|
'top',
|
||||||
|
'touch-action',
|
||||||
|
'transform',
|
||||||
|
'transform-box',
|
||||||
|
'transform-origin',
|
||||||
|
'transform-style',
|
||||||
|
'transition',
|
||||||
|
'transition-behavior',
|
||||||
|
'transition-delay',
|
||||||
|
'transition-duration',
|
||||||
|
'transition-property',
|
||||||
|
'transition-timing-function',
|
||||||
|
'translate',
|
||||||
|
'unicode-bidi',
|
||||||
|
'unicode-range',
|
||||||
|
'user-select',
|
||||||
|
'vector-effect',
|
||||||
|
'vertical-align',
|
||||||
|
'view-timeline',
|
||||||
|
'view-timeline-axis',
|
||||||
|
'view-timeline-inset',
|
||||||
|
'view-timeline-name',
|
||||||
|
'view-transition-class',
|
||||||
|
'view-transition-name',
|
||||||
|
'visibility',
|
||||||
|
'white-space',
|
||||||
|
'white-space-collapse',
|
||||||
|
'widows',
|
||||||
|
'width',
|
||||||
|
'will-change',
|
||||||
|
'word-break',
|
||||||
|
'word-spacing',
|
||||||
|
'writing-mode',
|
||||||
|
'x',
|
||||||
|
'y',
|
||||||
|
'z-index',
|
||||||
|
'zoom'
|
||||||
|
];
|
||||||
475
node_modules/css-declaration-sorter/src/orders/concentric-css.mjs
generated
vendored
Normal file
475
node_modules/css-declaration-sorter/src/orders/concentric-css.mjs
generated
vendored
Normal file
@@ -0,0 +1,475 @@
|
|||||||
|
export const properties = [
|
||||||
|
'all',
|
||||||
|
'display',
|
||||||
|
'position',
|
||||||
|
'position-anchor',
|
||||||
|
'position-area',
|
||||||
|
'position-try',
|
||||||
|
'position-try-order',
|
||||||
|
'position-try-fallbacks',
|
||||||
|
'anchor-name',
|
||||||
|
'top',
|
||||||
|
'right',
|
||||||
|
'bottom',
|
||||||
|
'left',
|
||||||
|
'offset',
|
||||||
|
'offset-anchor',
|
||||||
|
'offset-distance',
|
||||||
|
'offset-path',
|
||||||
|
'offset-position',
|
||||||
|
'offset-rotate',
|
||||||
|
'grid',
|
||||||
|
'grid-template-rows',
|
||||||
|
'grid-template-columns',
|
||||||
|
'grid-template-areas',
|
||||||
|
'grid-auto-rows',
|
||||||
|
'grid-auto-columns',
|
||||||
|
'grid-auto-flow',
|
||||||
|
'column-gap',
|
||||||
|
'row-gap',
|
||||||
|
'grid-area',
|
||||||
|
'grid-row',
|
||||||
|
'grid-row-start',
|
||||||
|
'grid-row-end',
|
||||||
|
'grid-column',
|
||||||
|
'grid-column-start',
|
||||||
|
'grid-column-end',
|
||||||
|
'grid-template',
|
||||||
|
'flex',
|
||||||
|
'flex-grow',
|
||||||
|
'flex-shrink',
|
||||||
|
'flex-basis',
|
||||||
|
'flex-direction',
|
||||||
|
'flex-flow',
|
||||||
|
'flex-wrap',
|
||||||
|
'box-decoration-break',
|
||||||
|
'place-content',
|
||||||
|
'align-content',
|
||||||
|
'justify-content',
|
||||||
|
'place-items',
|
||||||
|
'align-items',
|
||||||
|
'justify-items',
|
||||||
|
'place-self',
|
||||||
|
'align-self',
|
||||||
|
'justify-self',
|
||||||
|
'vertical-align',
|
||||||
|
'order',
|
||||||
|
'float',
|
||||||
|
'clear',
|
||||||
|
'shape-margin',
|
||||||
|
'shape-outside',
|
||||||
|
'shape-rendering',
|
||||||
|
'shape-image-threshold',
|
||||||
|
'orphans',
|
||||||
|
'gap',
|
||||||
|
'columns',
|
||||||
|
'column-fill',
|
||||||
|
'column-rule',
|
||||||
|
'column-rule-width',
|
||||||
|
'column-rule-style',
|
||||||
|
'column-rule-color',
|
||||||
|
'column-width',
|
||||||
|
'column-span',
|
||||||
|
'column-count',
|
||||||
|
'break-before',
|
||||||
|
'break-after',
|
||||||
|
'break-inside',
|
||||||
|
'page',
|
||||||
|
'transform',
|
||||||
|
'transform-box',
|
||||||
|
'transform-origin',
|
||||||
|
'transform-style',
|
||||||
|
'vector-effect',
|
||||||
|
'translate',
|
||||||
|
'rotate',
|
||||||
|
'scale',
|
||||||
|
'zoom',
|
||||||
|
|
||||||
|
'perspective',
|
||||||
|
'perspective-origin',
|
||||||
|
'appearance',
|
||||||
|
'visibility',
|
||||||
|
'content-visibility',
|
||||||
|
'opacity',
|
||||||
|
'z-index',
|
||||||
|
'paint-order',
|
||||||
|
'mix-blend-mode',
|
||||||
|
'backface-visibility',
|
||||||
|
'backdrop-filter',
|
||||||
|
'clip-path',
|
||||||
|
'clip-rule',
|
||||||
|
'mask',
|
||||||
|
'mask-border',
|
||||||
|
'mask-border-outset',
|
||||||
|
'mask-border-repeat',
|
||||||
|
'mask-border-slice',
|
||||||
|
'mask-border-source',
|
||||||
|
'mask-border-width',
|
||||||
|
'mask-image',
|
||||||
|
'mask-mode',
|
||||||
|
'mask-position',
|
||||||
|
'mask-size',
|
||||||
|
'mask-repeat',
|
||||||
|
'mask-origin',
|
||||||
|
'mask-clip',
|
||||||
|
'mask-composite',
|
||||||
|
'mask-type',
|
||||||
|
'filter',
|
||||||
|
'timeline-scope',
|
||||||
|
'scroll-timeline',
|
||||||
|
'scroll-timeline-name',
|
||||||
|
'scroll-timeline-axis',
|
||||||
|
'view-timeline',
|
||||||
|
'view-timeline-name',
|
||||||
|
'view-timeline-axis',
|
||||||
|
'view-timeline-inset',
|
||||||
|
'animation',
|
||||||
|
'animation-duration',
|
||||||
|
'animation-timing-function',
|
||||||
|
'animation-delay',
|
||||||
|
'animation-iteration-count',
|
||||||
|
'animation-direction',
|
||||||
|
'animation-fill-mode',
|
||||||
|
'animation-play-state',
|
||||||
|
'animation-name',
|
||||||
|
'animation-timeline',
|
||||||
|
'animation-composition',
|
||||||
|
'animation-range',
|
||||||
|
'animation-range-start',
|
||||||
|
'animation-range-end',
|
||||||
|
'view-transition-name',
|
||||||
|
'view-transition-class',
|
||||||
|
'transition',
|
||||||
|
'transition-behavior',
|
||||||
|
'transition-delay',
|
||||||
|
'transition-duration',
|
||||||
|
'transition-property',
|
||||||
|
'transition-timing-function',
|
||||||
|
'will-change',
|
||||||
|
'counter-increment',
|
||||||
|
'counter-reset',
|
||||||
|
'counter-set',
|
||||||
|
'cursor',
|
||||||
|
'fill',
|
||||||
|
'fill-opacity',
|
||||||
|
'fill-rule',
|
||||||
|
'flood-color',
|
||||||
|
'flood-opacity',
|
||||||
|
'lighting-color',
|
||||||
|
'marker',
|
||||||
|
'marker-end',
|
||||||
|
'marker-mid',
|
||||||
|
'marker-start',
|
||||||
|
'x',
|
||||||
|
'y',
|
||||||
|
'r',
|
||||||
|
'rx',
|
||||||
|
'ry',
|
||||||
|
'stop-color',
|
||||||
|
'stop-opacity',
|
||||||
|
'stroke',
|
||||||
|
'stroke-dasharray',
|
||||||
|
'stroke-dashoffset',
|
||||||
|
'stroke-linecap',
|
||||||
|
'stroke-linejoin',
|
||||||
|
'stroke-miterlimit',
|
||||||
|
'stroke-opacity',
|
||||||
|
'stroke-width',
|
||||||
|
|
||||||
|
'box-sizing',
|
||||||
|
'contain',
|
||||||
|
'contain-intrinsic-size',
|
||||||
|
'contain-intrinsic-width',
|
||||||
|
'contain-intrinsic-height',
|
||||||
|
'contain-intrinsic-inline-size',
|
||||||
|
'contain-intrinsic-block-size',
|
||||||
|
'container',
|
||||||
|
'container-name',
|
||||||
|
'container-type',
|
||||||
|
'cx',
|
||||||
|
'cy',
|
||||||
|
'd',
|
||||||
|
'margin',
|
||||||
|
'margin-top',
|
||||||
|
'margin-right',
|
||||||
|
'margin-bottom',
|
||||||
|
'margin-left',
|
||||||
|
'margin-inline',
|
||||||
|
'margin-inline-start',
|
||||||
|
'margin-inline-end',
|
||||||
|
'margin-block',
|
||||||
|
'margin-block-start',
|
||||||
|
'margin-block-end',
|
||||||
|
'inset',
|
||||||
|
'inset-block',
|
||||||
|
'inset-block-end',
|
||||||
|
'inset-block-start',
|
||||||
|
'inset-inline',
|
||||||
|
'inset-inline-end',
|
||||||
|
'inset-inline-start',
|
||||||
|
'outline',
|
||||||
|
'outline-color',
|
||||||
|
'outline-style',
|
||||||
|
'outline-width',
|
||||||
|
'outline-offset',
|
||||||
|
'box-shadow',
|
||||||
|
'border',
|
||||||
|
'border-top',
|
||||||
|
'border-right',
|
||||||
|
'border-bottom',
|
||||||
|
'border-left',
|
||||||
|
'border-width',
|
||||||
|
'border-top-width',
|
||||||
|
'border-right-width',
|
||||||
|
'border-bottom-width',
|
||||||
|
'border-left-width',
|
||||||
|
'border-style',
|
||||||
|
'border-top-style',
|
||||||
|
'border-right-style',
|
||||||
|
'border-bottom-style',
|
||||||
|
'border-left-style',
|
||||||
|
'border-color',
|
||||||
|
'border-top-color',
|
||||||
|
'border-right-color',
|
||||||
|
'border-bottom-color',
|
||||||
|
'border-left-color',
|
||||||
|
'border-radius',
|
||||||
|
'border-top-right-radius',
|
||||||
|
'border-top-left-radius',
|
||||||
|
'border-bottom-right-radius',
|
||||||
|
'border-bottom-left-radius',
|
||||||
|
'border-inline',
|
||||||
|
'border-inline-width',
|
||||||
|
'border-inline-style',
|
||||||
|
'border-inline-color',
|
||||||
|
'border-inline-start',
|
||||||
|
'border-inline-start-width',
|
||||||
|
'border-inline-start-style',
|
||||||
|
'border-inline-start-color',
|
||||||
|
'border-inline-end',
|
||||||
|
'border-inline-end-width',
|
||||||
|
'border-inline-end-style',
|
||||||
|
'border-inline-end-color',
|
||||||
|
'border-block',
|
||||||
|
'border-block-width',
|
||||||
|
'border-block-style',
|
||||||
|
'border-block-color',
|
||||||
|
'border-block-start',
|
||||||
|
'border-block-start-width',
|
||||||
|
'border-block-start-style',
|
||||||
|
'border-block-start-color',
|
||||||
|
'border-block-end',
|
||||||
|
'border-block-end-width',
|
||||||
|
'border-block-end-style',
|
||||||
|
'border-block-end-color',
|
||||||
|
'border-image',
|
||||||
|
'border-image-source',
|
||||||
|
'border-image-slice',
|
||||||
|
'border-image-width',
|
||||||
|
'border-image-outset',
|
||||||
|
'border-image-repeat',
|
||||||
|
'border-collapse',
|
||||||
|
'border-spacing',
|
||||||
|
'border-start-start-radius',
|
||||||
|
'border-start-end-radius',
|
||||||
|
'border-end-start-radius',
|
||||||
|
'border-end-end-radius',
|
||||||
|
'background',
|
||||||
|
'background-image',
|
||||||
|
'background-position',
|
||||||
|
'background-position-x',
|
||||||
|
'background-position-y',
|
||||||
|
'background-size',
|
||||||
|
'background-repeat',
|
||||||
|
'background-attachment',
|
||||||
|
'background-origin',
|
||||||
|
'background-clip',
|
||||||
|
'background-color',
|
||||||
|
'background-blend-mode',
|
||||||
|
'isolation',
|
||||||
|
'padding',
|
||||||
|
'padding-top',
|
||||||
|
'padding-right',
|
||||||
|
'padding-bottom',
|
||||||
|
'padding-left',
|
||||||
|
'padding-inline',
|
||||||
|
'padding-inline-start',
|
||||||
|
'padding-inline-end',
|
||||||
|
'padding-block',
|
||||||
|
'padding-block-start',
|
||||||
|
'padding-block-end',
|
||||||
|
'image-orientation',
|
||||||
|
'image-rendering',
|
||||||
|
|
||||||
|
'aspect-ratio',
|
||||||
|
'width',
|
||||||
|
'min-width',
|
||||||
|
'max-width',
|
||||||
|
'height',
|
||||||
|
'min-height',
|
||||||
|
'max-height',
|
||||||
|
'line-clamp',
|
||||||
|
'-webkit-text-fill-color',
|
||||||
|
'-webkit-text-stroke',
|
||||||
|
'-webkit-text-stroke-width',
|
||||||
|
'-webkit-text-stroke-color',
|
||||||
|
'inline-size',
|
||||||
|
'min-inline-size',
|
||||||
|
'max-inline-size',
|
||||||
|
'block-size',
|
||||||
|
'min-block-size',
|
||||||
|
'max-block-size',
|
||||||
|
'table-layout',
|
||||||
|
'caption-side',
|
||||||
|
'empty-cells',
|
||||||
|
'overflow',
|
||||||
|
'overflow-anchor',
|
||||||
|
'overflow-block',
|
||||||
|
'overflow-clip-margin',
|
||||||
|
'overflow-inline',
|
||||||
|
'overflow-x',
|
||||||
|
'overflow-y',
|
||||||
|
'overscroll-behavior',
|
||||||
|
'overscroll-behavior-block',
|
||||||
|
'overscroll-behavior-inline',
|
||||||
|
'overscroll-behavior-x',
|
||||||
|
'overscroll-behavior-y',
|
||||||
|
'resize',
|
||||||
|
'object-fit',
|
||||||
|
'object-position',
|
||||||
|
'scroll-behavior',
|
||||||
|
'scroll-margin',
|
||||||
|
'scroll-margin-block',
|
||||||
|
'scroll-margin-block-end',
|
||||||
|
'scroll-margin-block-start',
|
||||||
|
'scroll-margin-bottom',
|
||||||
|
'scroll-margin-inline',
|
||||||
|
'scroll-margin-inline-end',
|
||||||
|
'scroll-margin-inline-start',
|
||||||
|
'scroll-margin-left',
|
||||||
|
'scroll-margin-right',
|
||||||
|
'scroll-margin-top',
|
||||||
|
'scroll-padding',
|
||||||
|
'scroll-padding-block',
|
||||||
|
'scroll-padding-block-end',
|
||||||
|
'scroll-padding-block-start',
|
||||||
|
'scroll-padding-bottom',
|
||||||
|
'scroll-padding-inline',
|
||||||
|
'scroll-padding-inline-end',
|
||||||
|
'scroll-padding-inline-start',
|
||||||
|
'scroll-padding-left',
|
||||||
|
'scroll-padding-right',
|
||||||
|
'scroll-padding-top',
|
||||||
|
'scroll-snap-align',
|
||||||
|
'scroll-snap-stop',
|
||||||
|
'scroll-snap-type',
|
||||||
|
'scrollbar-color',
|
||||||
|
'scrollbar-gutter',
|
||||||
|
'scrollbar-width',
|
||||||
|
'touch-action',
|
||||||
|
'pointer-events',
|
||||||
|
|
||||||
|
'content',
|
||||||
|
'quotes',
|
||||||
|
'hanging-punctuation',
|
||||||
|
'color',
|
||||||
|
'color-interpolation',
|
||||||
|
'color-interpolation-filters',
|
||||||
|
'accent-color',
|
||||||
|
'print-color-adjust',
|
||||||
|
'forced-color-adjust',
|
||||||
|
'color-scheme',
|
||||||
|
'caret-color',
|
||||||
|
'font',
|
||||||
|
'font-style',
|
||||||
|
'font-variant',
|
||||||
|
'font-weight',
|
||||||
|
'font-stretch',
|
||||||
|
'font-size',
|
||||||
|
'size-adjust',
|
||||||
|
'line-height',
|
||||||
|
'src',
|
||||||
|
'font-family',
|
||||||
|
'font-display',
|
||||||
|
'font-kerning',
|
||||||
|
'font-language-override',
|
||||||
|
'font-optical-sizing',
|
||||||
|
'font-palette',
|
||||||
|
'font-size-adjust',
|
||||||
|
'font-synthesis',
|
||||||
|
'font-synthesis-weight',
|
||||||
|
'font-synthesis-style',
|
||||||
|
'font-synthesis-small-caps',
|
||||||
|
'font-synthesis-position',
|
||||||
|
'font-variant-alternates',
|
||||||
|
'font-variant-caps',
|
||||||
|
'font-variant-east-asian',
|
||||||
|
'font-variant-emoji',
|
||||||
|
'font-variant-ligatures',
|
||||||
|
'font-variant-numeric',
|
||||||
|
'font-variant-position',
|
||||||
|
'font-variation-settings',
|
||||||
|
'font-feature-settings',
|
||||||
|
'alignment-baseline',
|
||||||
|
'dominant-baseline',
|
||||||
|
'text-anchor',
|
||||||
|
'text-autospace',
|
||||||
|
'text-box',
|
||||||
|
'text-box-trim',
|
||||||
|
'text-box-edge',
|
||||||
|
'ascent-override',
|
||||||
|
'descent-override',
|
||||||
|
'line-gap-override',
|
||||||
|
'hyphens',
|
||||||
|
'hyphenate-character',
|
||||||
|
'hyphenate-limit-chars',
|
||||||
|
'initial-letter',
|
||||||
|
'letter-spacing',
|
||||||
|
'line-break',
|
||||||
|
'list-style',
|
||||||
|
'list-style-type',
|
||||||
|
'list-style-image',
|
||||||
|
'list-style-position',
|
||||||
|
'writing-mode',
|
||||||
|
'direction',
|
||||||
|
'unicode-bidi',
|
||||||
|
'unicode-range',
|
||||||
|
'user-select',
|
||||||
|
'ruby-align',
|
||||||
|
'ruby-overhang',
|
||||||
|
'ruby-position',
|
||||||
|
'math-depth',
|
||||||
|
'math-style',
|
||||||
|
'text-combine-upright',
|
||||||
|
'text-align',
|
||||||
|
'text-align-last',
|
||||||
|
'text-decoration',
|
||||||
|
'text-decoration-line',
|
||||||
|
'text-decoration-style',
|
||||||
|
'text-decoration-color',
|
||||||
|
'text-decoration-thickness',
|
||||||
|
'text-decoration-skip-ink',
|
||||||
|
'text-emphasis',
|
||||||
|
'text-emphasis-style',
|
||||||
|
'text-emphasis-color',
|
||||||
|
'text-emphasis-position',
|
||||||
|
'text-indent',
|
||||||
|
'text-justify',
|
||||||
|
'text-underline-position',
|
||||||
|
'text-underline-offset',
|
||||||
|
'text-orientation',
|
||||||
|
'text-overflow',
|
||||||
|
'text-rendering',
|
||||||
|
'text-shadow',
|
||||||
|
'text-transform',
|
||||||
|
'text-wrap',
|
||||||
|
'text-wrap-mode',
|
||||||
|
'text-wrap-style',
|
||||||
|
'white-space',
|
||||||
|
'white-space-collapse',
|
||||||
|
'word-break',
|
||||||
|
'word-spacing',
|
||||||
|
'overflow-wrap',
|
||||||
|
'tab-size',
|
||||||
|
'widows',
|
||||||
|
];
|
||||||
478
node_modules/css-declaration-sorter/src/orders/frakto.mjs
generated
vendored
Normal file
478
node_modules/css-declaration-sorter/src/orders/frakto.mjs
generated
vendored
Normal file
@@ -0,0 +1,478 @@
|
|||||||
|
export const properties = [
|
||||||
|
'position',
|
||||||
|
'position-anchor',
|
||||||
|
'position-area',
|
||||||
|
'position-try',
|
||||||
|
'position-try-order',
|
||||||
|
'position-try-fallbacks',
|
||||||
|
'anchor-name',
|
||||||
|
'top',
|
||||||
|
'right',
|
||||||
|
'bottom',
|
||||||
|
'left',
|
||||||
|
'inset',
|
||||||
|
'inset-block',
|
||||||
|
'inset-block-start',
|
||||||
|
'inset-block-end',
|
||||||
|
'inset-inline',
|
||||||
|
'inset-inline-start',
|
||||||
|
'inset-inline-end',
|
||||||
|
'z-index',
|
||||||
|
'float',
|
||||||
|
'clear',
|
||||||
|
|
||||||
|
'display',
|
||||||
|
'box-sizing',
|
||||||
|
'box-decoration-break',
|
||||||
|
'aspect-ratio',
|
||||||
|
'contain',
|
||||||
|
'contain-intrinsic-size',
|
||||||
|
'contain-intrinsic-width',
|
||||||
|
'contain-intrinsic-height',
|
||||||
|
'contain-intrinsic-block-size',
|
||||||
|
'contain-intrinsic-inline-size',
|
||||||
|
'container',
|
||||||
|
'container-name',
|
||||||
|
'container-type',
|
||||||
|
'width',
|
||||||
|
'min-width',
|
||||||
|
'max-width',
|
||||||
|
'height',
|
||||||
|
'min-height',
|
||||||
|
'max-height',
|
||||||
|
'block-size',
|
||||||
|
'min-block-size',
|
||||||
|
'max-block-size',
|
||||||
|
'inline-size',
|
||||||
|
'min-inline-size',
|
||||||
|
'max-inline-size',
|
||||||
|
'margin',
|
||||||
|
'margin-top',
|
||||||
|
'margin-right',
|
||||||
|
'margin-bottom',
|
||||||
|
'margin-left',
|
||||||
|
'margin-block',
|
||||||
|
'margin-block-start',
|
||||||
|
'margin-block-end',
|
||||||
|
'margin-inline',
|
||||||
|
'margin-inline-start',
|
||||||
|
'margin-inline-end',
|
||||||
|
'padding',
|
||||||
|
'padding-top',
|
||||||
|
'padding-right',
|
||||||
|
'padding-bottom',
|
||||||
|
'padding-left',
|
||||||
|
'padding-block',
|
||||||
|
'padding-block-start',
|
||||||
|
'padding-block-end',
|
||||||
|
'padding-inline',
|
||||||
|
'padding-inline-start',
|
||||||
|
'padding-inline-end',
|
||||||
|
|
||||||
|
'flex',
|
||||||
|
'flex-grow',
|
||||||
|
'flex-shrink',
|
||||||
|
'flex-basis',
|
||||||
|
'flex-flow',
|
||||||
|
'flex-direction',
|
||||||
|
'flex-wrap',
|
||||||
|
'grid',
|
||||||
|
'grid-area',
|
||||||
|
'grid-auto-columns',
|
||||||
|
'grid-auto-flow',
|
||||||
|
'grid-auto-rows',
|
||||||
|
'grid-column',
|
||||||
|
'grid-column-end',
|
||||||
|
'grid-column-start',
|
||||||
|
'grid-row',
|
||||||
|
'grid-row-end',
|
||||||
|
'grid-row-start',
|
||||||
|
'grid-template',
|
||||||
|
'grid-template-areas',
|
||||||
|
'grid-template-columns',
|
||||||
|
'grid-template-rows',
|
||||||
|
'align-content',
|
||||||
|
'align-items',
|
||||||
|
'align-self',
|
||||||
|
'justify-content',
|
||||||
|
'justify-items',
|
||||||
|
'justify-self',
|
||||||
|
'place-content',
|
||||||
|
'place-items',
|
||||||
|
'place-self',
|
||||||
|
'gap',
|
||||||
|
'row-gap',
|
||||||
|
'column-gap',
|
||||||
|
'order',
|
||||||
|
'orphans',
|
||||||
|
'widows',
|
||||||
|
'columns',
|
||||||
|
'column-width',
|
||||||
|
'column-count',
|
||||||
|
'column-span',
|
||||||
|
'column-rule',
|
||||||
|
'column-rule-width',
|
||||||
|
'column-rule-style',
|
||||||
|
'column-rule-color',
|
||||||
|
'column-fill',
|
||||||
|
'table-layout',
|
||||||
|
'caption-side',
|
||||||
|
|
||||||
|
'color',
|
||||||
|
'caret-color',
|
||||||
|
'font',
|
||||||
|
'font-style',
|
||||||
|
'font-variant',
|
||||||
|
'font-weight',
|
||||||
|
'font-stretch',
|
||||||
|
'font-size',
|
||||||
|
'font-family',
|
||||||
|
'font-size-adjust',
|
||||||
|
'font-display',
|
||||||
|
'font-optical-sizing',
|
||||||
|
'font-variation-settings',
|
||||||
|
'font-feature-settings',
|
||||||
|
'font-kerning',
|
||||||
|
'font-language-override',
|
||||||
|
'font-palette',
|
||||||
|
'font-synthesis',
|
||||||
|
'font-synthesis-weight',
|
||||||
|
'font-synthesis-style',
|
||||||
|
'font-synthesis-small-caps',
|
||||||
|
'font-synthesis-position',
|
||||||
|
'font-variant-alternates',
|
||||||
|
'font-variant-caps',
|
||||||
|
'font-variant-ligatures',
|
||||||
|
'font-variant-numeric',
|
||||||
|
'font-variant-position',
|
||||||
|
'font-variant-east-asian',
|
||||||
|
'font-variant-emoji',
|
||||||
|
'size-adjust',
|
||||||
|
'text-align',
|
||||||
|
'text-align-last',
|
||||||
|
'text-indent',
|
||||||
|
'text-justify',
|
||||||
|
'text-wrap',
|
||||||
|
'text-wrap-mode',
|
||||||
|
'text-wrap-style',
|
||||||
|
'text-overflow',
|
||||||
|
'text-transform',
|
||||||
|
'text-orientation',
|
||||||
|
'text-combine-upright',
|
||||||
|
'text-decoration',
|
||||||
|
'text-decoration-line',
|
||||||
|
'text-decoration-color',
|
||||||
|
'text-decoration-style',
|
||||||
|
'text-decoration-thickness',
|
||||||
|
'text-decoration-skip-ink',
|
||||||
|
'text-underline-offset',
|
||||||
|
'text-underline-position',
|
||||||
|
'text-emphasis',
|
||||||
|
'text-emphasis-style',
|
||||||
|
'text-emphasis-color',
|
||||||
|
'text-emphasis-position',
|
||||||
|
'text-shadow',
|
||||||
|
'text-rendering',
|
||||||
|
'text-anchor',
|
||||||
|
'text-autospace',
|
||||||
|
'text-box',
|
||||||
|
'text-box-trim',
|
||||||
|
'text-box-edge',
|
||||||
|
'-webkit-text-fill-color',
|
||||||
|
'-webkit-text-stroke',
|
||||||
|
'-webkit-text-stroke-width',
|
||||||
|
'-webkit-text-stroke-color',
|
||||||
|
'line-height',
|
||||||
|
'line-break',
|
||||||
|
'line-clamp',
|
||||||
|
'line-gap-override',
|
||||||
|
'white-space',
|
||||||
|
'white-space-collapse',
|
||||||
|
'word-break',
|
||||||
|
'word-spacing',
|
||||||
|
'letter-spacing',
|
||||||
|
'vertical-align',
|
||||||
|
'list-style',
|
||||||
|
'list-style-type',
|
||||||
|
'list-style-position',
|
||||||
|
'list-style-image',
|
||||||
|
'hyphens',
|
||||||
|
'hyphenate-character',
|
||||||
|
'hyphenate-limit-chars',
|
||||||
|
'hanging-punctuation',
|
||||||
|
'direction',
|
||||||
|
'unicode-bidi',
|
||||||
|
'unicode-range',
|
||||||
|
'writing-mode',
|
||||||
|
'dominant-baseline',
|
||||||
|
'alignment-baseline',
|
||||||
|
'ascent-override',
|
||||||
|
'descent-override',
|
||||||
|
'initial-letter',
|
||||||
|
'ruby-position',
|
||||||
|
'ruby-overhang',
|
||||||
|
'ruby-align',
|
||||||
|
'quotes',
|
||||||
|
|
||||||
|
'background',
|
||||||
|
'background-image',
|
||||||
|
'background-position',
|
||||||
|
'background-position-x',
|
||||||
|
'background-position-y',
|
||||||
|
'background-size',
|
||||||
|
'background-repeat',
|
||||||
|
'background-attachment',
|
||||||
|
'background-origin',
|
||||||
|
'background-clip',
|
||||||
|
'background-color',
|
||||||
|
'background-blend-mode',
|
||||||
|
'border',
|
||||||
|
'border-width',
|
||||||
|
'border-style',
|
||||||
|
'border-color',
|
||||||
|
'border-top',
|
||||||
|
'border-top-width',
|
||||||
|
'border-top-style',
|
||||||
|
'border-top-color',
|
||||||
|
'border-right',
|
||||||
|
'border-right-width',
|
||||||
|
'border-right-style',
|
||||||
|
'border-right-color',
|
||||||
|
'border-bottom',
|
||||||
|
'border-bottom-width',
|
||||||
|
'border-bottom-style',
|
||||||
|
'border-bottom-color',
|
||||||
|
'border-left',
|
||||||
|
'border-left-width',
|
||||||
|
'border-left-style',
|
||||||
|
'border-left-color',
|
||||||
|
'border-block',
|
||||||
|
'border-block-width',
|
||||||
|
'border-block-style',
|
||||||
|
'border-block-color',
|
||||||
|
'border-block-start',
|
||||||
|
'border-block-start-width',
|
||||||
|
'border-block-start-style',
|
||||||
|
'border-block-start-color',
|
||||||
|
'border-block-end',
|
||||||
|
'border-block-end-width',
|
||||||
|
'border-block-end-style',
|
||||||
|
'border-block-end-color',
|
||||||
|
'border-inline',
|
||||||
|
'border-inline-width',
|
||||||
|
'border-inline-style',
|
||||||
|
'border-inline-color',
|
||||||
|
'border-inline-start',
|
||||||
|
'border-inline-start-width',
|
||||||
|
'border-inline-start-style',
|
||||||
|
'border-inline-start-color',
|
||||||
|
'border-inline-end',
|
||||||
|
'border-inline-end-width',
|
||||||
|
'border-inline-end-style',
|
||||||
|
'border-inline-end-color',
|
||||||
|
'border-radius',
|
||||||
|
'border-top-left-radius',
|
||||||
|
'border-top-right-radius',
|
||||||
|
'border-bottom-right-radius',
|
||||||
|
'border-bottom-left-radius',
|
||||||
|
'border-start-start-radius',
|
||||||
|
'border-start-end-radius',
|
||||||
|
'border-end-end-radius',
|
||||||
|
'border-end-start-radius',
|
||||||
|
'border-image',
|
||||||
|
'border-image-source',
|
||||||
|
'border-image-slice',
|
||||||
|
'border-image-width',
|
||||||
|
'border-image-outset',
|
||||||
|
'border-image-repeat',
|
||||||
|
'border-collapse',
|
||||||
|
'border-spacing',
|
||||||
|
'outline',
|
||||||
|
'outline-width',
|
||||||
|
'outline-style',
|
||||||
|
'outline-color',
|
||||||
|
'outline-offset',
|
||||||
|
'box-shadow',
|
||||||
|
'opacity',
|
||||||
|
'visibility',
|
||||||
|
'object-fit',
|
||||||
|
'object-position',
|
||||||
|
'filter',
|
||||||
|
'backdrop-filter',
|
||||||
|
'backface-visibility',
|
||||||
|
'mask',
|
||||||
|
'mask-border',
|
||||||
|
'mask-border-outset',
|
||||||
|
'mask-border-repeat',
|
||||||
|
'mask-border-slice',
|
||||||
|
'mask-border-source',
|
||||||
|
'mask-border-width',
|
||||||
|
'mask-clip',
|
||||||
|
'mask-composite',
|
||||||
|
'mask-image',
|
||||||
|
'mask-mode',
|
||||||
|
'mask-origin',
|
||||||
|
'mask-position',
|
||||||
|
'mask-repeat',
|
||||||
|
'mask-size',
|
||||||
|
'mask-type',
|
||||||
|
'shape-outside',
|
||||||
|
'shape-margin',
|
||||||
|
'shape-rendering',
|
||||||
|
'shape-image-threshold',
|
||||||
|
'd',
|
||||||
|
'x',
|
||||||
|
'y',
|
||||||
|
'cx',
|
||||||
|
'cy',
|
||||||
|
'r',
|
||||||
|
'rx',
|
||||||
|
'ry',
|
||||||
|
'clip-path',
|
||||||
|
'clip-rule',
|
||||||
|
'fill',
|
||||||
|
'fill-opacity',
|
||||||
|
'fill-rule',
|
||||||
|
'flood-color',
|
||||||
|
'flood-opacity',
|
||||||
|
'lighting-color',
|
||||||
|
'marker',
|
||||||
|
'marker-start',
|
||||||
|
'marker-mid',
|
||||||
|
'marker-end',
|
||||||
|
'stroke',
|
||||||
|
'stroke-width',
|
||||||
|
'stroke-opacity',
|
||||||
|
'stroke-dasharray',
|
||||||
|
'stroke-dashoffset',
|
||||||
|
'stroke-linecap',
|
||||||
|
'stroke-linejoin',
|
||||||
|
'stroke-miterlimit',
|
||||||
|
'stop-color',
|
||||||
|
'stop-opacity',
|
||||||
|
'accent-color',
|
||||||
|
'mix-blend-mode',
|
||||||
|
'paint-order',
|
||||||
|
'isolation',
|
||||||
|
'appearance',
|
||||||
|
'color-scheme',
|
||||||
|
'color-interpolation',
|
||||||
|
'color-interpolation-filters',
|
||||||
|
|
||||||
|
'transform',
|
||||||
|
'transform-box',
|
||||||
|
'transform-origin',
|
||||||
|
'transform-style',
|
||||||
|
'translate',
|
||||||
|
'scale',
|
||||||
|
'rotate',
|
||||||
|
'zoom',
|
||||||
|
'vector-effect',
|
||||||
|
'transition',
|
||||||
|
'transition-property',
|
||||||
|
'transition-duration',
|
||||||
|
'transition-timing-function',
|
||||||
|
'transition-delay',
|
||||||
|
'transition-behavior',
|
||||||
|
'view-transition-name',
|
||||||
|
'view-transition-class',
|
||||||
|
'timeline-scope',
|
||||||
|
'scroll-timeline',
|
||||||
|
'scroll-timeline-name',
|
||||||
|
'scroll-timeline-axis',
|
||||||
|
'view-timeline',
|
||||||
|
'view-timeline-name',
|
||||||
|
'view-timeline-axis',
|
||||||
|
'view-timeline-inset',
|
||||||
|
'animation',
|
||||||
|
'animation-duration',
|
||||||
|
'animation-timing-function',
|
||||||
|
'animation-delay',
|
||||||
|
'animation-iteration-count',
|
||||||
|
'animation-direction',
|
||||||
|
'animation-fill-mode',
|
||||||
|
'animation-play-state',
|
||||||
|
'animation-name',
|
||||||
|
'animation-timeline',
|
||||||
|
'animation-composition',
|
||||||
|
'animation-range',
|
||||||
|
'animation-range-start',
|
||||||
|
'animation-range-end',
|
||||||
|
'offset',
|
||||||
|
'offset-path',
|
||||||
|
'offset-position',
|
||||||
|
'offset-distance',
|
||||||
|
'offset-rotate',
|
||||||
|
'offset-anchor',
|
||||||
|
'image-orientation',
|
||||||
|
'image-rendering',
|
||||||
|
'perspective',
|
||||||
|
'perspective-origin',
|
||||||
|
'will-change',
|
||||||
|
|
||||||
|
'cursor',
|
||||||
|
'pointer-events',
|
||||||
|
'touch-action',
|
||||||
|
'user-select',
|
||||||
|
'resize',
|
||||||
|
'overflow',
|
||||||
|
'overflow-x',
|
||||||
|
'overflow-y',
|
||||||
|
'overflow-block',
|
||||||
|
'overflow-inline',
|
||||||
|
'overflow-wrap',
|
||||||
|
'overflow-anchor',
|
||||||
|
'overflow-clip-margin',
|
||||||
|
'overscroll-behavior',
|
||||||
|
'overscroll-behavior-x',
|
||||||
|
'overscroll-behavior-y',
|
||||||
|
'overscroll-behavior-block',
|
||||||
|
'overscroll-behavior-inline',
|
||||||
|
'scroll-behavior',
|
||||||
|
'scroll-margin',
|
||||||
|
'scroll-margin-top',
|
||||||
|
'scroll-margin-right',
|
||||||
|
'scroll-margin-bottom',
|
||||||
|
'scroll-margin-left',
|
||||||
|
'scroll-margin-block',
|
||||||
|
'scroll-margin-block-start',
|
||||||
|
'scroll-margin-block-end',
|
||||||
|
'scroll-margin-inline',
|
||||||
|
'scroll-margin-inline-start',
|
||||||
|
'scroll-margin-inline-end',
|
||||||
|
'scroll-padding',
|
||||||
|
'scroll-padding-top',
|
||||||
|
'scroll-padding-right',
|
||||||
|
'scroll-padding-bottom',
|
||||||
|
'scroll-padding-left',
|
||||||
|
'scroll-padding-block',
|
||||||
|
'scroll-padding-block-start',
|
||||||
|
'scroll-padding-block-end',
|
||||||
|
'scroll-padding-inline',
|
||||||
|
'scroll-padding-inline-start',
|
||||||
|
'scroll-padding-inline-end',
|
||||||
|
'scroll-snap-type',
|
||||||
|
'scroll-snap-align',
|
||||||
|
'scroll-snap-stop',
|
||||||
|
'scrollbar-width',
|
||||||
|
'scrollbar-color',
|
||||||
|
'scrollbar-gutter',
|
||||||
|
|
||||||
|
'all',
|
||||||
|
'content',
|
||||||
|
'content-visibility',
|
||||||
|
'page',
|
||||||
|
'break-after',
|
||||||
|
'break-before',
|
||||||
|
'break-inside',
|
||||||
|
'counter-reset',
|
||||||
|
'counter-increment',
|
||||||
|
'counter-set',
|
||||||
|
'empty-cells',
|
||||||
|
'forced-color-adjust',
|
||||||
|
'print-color-adjust',
|
||||||
|
'math-depth',
|
||||||
|
'math-style',
|
||||||
|
'src',
|
||||||
|
'tab-size',
|
||||||
|
];
|
||||||
476
node_modules/css-declaration-sorter/src/orders/smacss.mjs
generated
vendored
Normal file
476
node_modules/css-declaration-sorter/src/orders/smacss.mjs
generated
vendored
Normal file
@@ -0,0 +1,476 @@
|
|||||||
|
export const properties = [
|
||||||
|
'all',
|
||||||
|
'box-sizing',
|
||||||
|
'contain',
|
||||||
|
'contain-intrinsic-size',
|
||||||
|
'contain-intrinsic-width',
|
||||||
|
'contain-intrinsic-height',
|
||||||
|
'contain-intrinsic-inline-size',
|
||||||
|
'contain-intrinsic-block-size',
|
||||||
|
'container',
|
||||||
|
'container-name',
|
||||||
|
'container-type',
|
||||||
|
'cx',
|
||||||
|
'cy',
|
||||||
|
'd',
|
||||||
|
'display',
|
||||||
|
'appearance',
|
||||||
|
'visibility',
|
||||||
|
'content-visibility',
|
||||||
|
'z-index',
|
||||||
|
'paint-order',
|
||||||
|
'position',
|
||||||
|
'position-anchor',
|
||||||
|
'position-area',
|
||||||
|
'position-try',
|
||||||
|
'position-try-order',
|
||||||
|
'position-try-fallbacks',
|
||||||
|
'anchor-name',
|
||||||
|
'top',
|
||||||
|
'right',
|
||||||
|
'bottom',
|
||||||
|
'left',
|
||||||
|
'offset',
|
||||||
|
'offset-anchor',
|
||||||
|
'offset-distance',
|
||||||
|
'offset-path',
|
||||||
|
'offset-position',
|
||||||
|
'offset-rotate',
|
||||||
|
|
||||||
|
'grid',
|
||||||
|
'grid-template-rows',
|
||||||
|
'grid-template-columns',
|
||||||
|
'grid-template-areas',
|
||||||
|
'grid-auto-rows',
|
||||||
|
'grid-auto-columns',
|
||||||
|
'grid-auto-flow',
|
||||||
|
'column-gap',
|
||||||
|
'row-gap',
|
||||||
|
'grid-area',
|
||||||
|
'grid-row',
|
||||||
|
'grid-row-start',
|
||||||
|
'grid-row-end',
|
||||||
|
'grid-column',
|
||||||
|
'grid-column-start',
|
||||||
|
'grid-column-end',
|
||||||
|
'grid-template',
|
||||||
|
'flex',
|
||||||
|
'flex-grow',
|
||||||
|
'flex-shrink',
|
||||||
|
'flex-basis',
|
||||||
|
'flex-direction',
|
||||||
|
'flex-flow',
|
||||||
|
'flex-wrap',
|
||||||
|
'box-decoration-break',
|
||||||
|
'place-content',
|
||||||
|
'place-items',
|
||||||
|
'place-self',
|
||||||
|
'align-content',
|
||||||
|
'align-items',
|
||||||
|
'align-self',
|
||||||
|
'justify-content',
|
||||||
|
'justify-items',
|
||||||
|
'justify-self',
|
||||||
|
'order',
|
||||||
|
'aspect-ratio',
|
||||||
|
'width',
|
||||||
|
'min-width',
|
||||||
|
'max-width',
|
||||||
|
'height',
|
||||||
|
'min-height',
|
||||||
|
'max-height',
|
||||||
|
'line-clamp',
|
||||||
|
'-webkit-text-fill-color',
|
||||||
|
'-webkit-text-stroke',
|
||||||
|
'-webkit-text-stroke-width',
|
||||||
|
'-webkit-text-stroke-color',
|
||||||
|
'inline-size',
|
||||||
|
'min-inline-size',
|
||||||
|
'max-inline-size',
|
||||||
|
'block-size',
|
||||||
|
'min-block-size',
|
||||||
|
'max-block-size',
|
||||||
|
'margin',
|
||||||
|
'margin-top',
|
||||||
|
'margin-right',
|
||||||
|
'margin-bottom',
|
||||||
|
'margin-left',
|
||||||
|
'margin-inline',
|
||||||
|
'margin-inline-start',
|
||||||
|
'margin-inline-end',
|
||||||
|
'margin-block',
|
||||||
|
'margin-block-start',
|
||||||
|
'margin-block-end',
|
||||||
|
'inset',
|
||||||
|
'inset-block',
|
||||||
|
'inset-block-end',
|
||||||
|
'inset-block-start',
|
||||||
|
'inset-inline',
|
||||||
|
'inset-inline-end',
|
||||||
|
'inset-inline-start',
|
||||||
|
'padding',
|
||||||
|
'padding-top',
|
||||||
|
'padding-right',
|
||||||
|
'padding-bottom',
|
||||||
|
'padding-left',
|
||||||
|
'padding-inline',
|
||||||
|
'padding-inline-start',
|
||||||
|
'padding-inline-end',
|
||||||
|
'padding-block',
|
||||||
|
'padding-block-start',
|
||||||
|
'padding-block-end',
|
||||||
|
'float',
|
||||||
|
'clear',
|
||||||
|
'overflow',
|
||||||
|
'overflow-anchor',
|
||||||
|
'overflow-block',
|
||||||
|
'overflow-clip-margin',
|
||||||
|
'overflow-inline',
|
||||||
|
'overflow-x',
|
||||||
|
'overflow-y',
|
||||||
|
'overscroll-behavior',
|
||||||
|
'overscroll-behavior-block',
|
||||||
|
'overscroll-behavior-inline',
|
||||||
|
'overscroll-behavior-x',
|
||||||
|
'overscroll-behavior-y',
|
||||||
|
'orphans',
|
||||||
|
'gap',
|
||||||
|
'columns',
|
||||||
|
'column-fill',
|
||||||
|
'column-rule',
|
||||||
|
'column-rule-color',
|
||||||
|
'column-rule-style',
|
||||||
|
'column-rule-width',
|
||||||
|
'column-span',
|
||||||
|
'column-count',
|
||||||
|
'column-width',
|
||||||
|
'object-fit',
|
||||||
|
'object-position',
|
||||||
|
'transform',
|
||||||
|
'transform-box',
|
||||||
|
'transform-origin',
|
||||||
|
'transform-style',
|
||||||
|
'vector-effect',
|
||||||
|
'translate',
|
||||||
|
'rotate',
|
||||||
|
'scale',
|
||||||
|
'zoom',
|
||||||
|
|
||||||
|
'border',
|
||||||
|
'border-top',
|
||||||
|
'border-right',
|
||||||
|
'border-bottom',
|
||||||
|
'border-left',
|
||||||
|
'border-width',
|
||||||
|
'border-top-width',
|
||||||
|
'border-right-width',
|
||||||
|
'border-bottom-width',
|
||||||
|
'border-left-width',
|
||||||
|
'border-style',
|
||||||
|
'border-top-style',
|
||||||
|
'border-right-style',
|
||||||
|
'border-bottom-style',
|
||||||
|
'border-left-style',
|
||||||
|
'border-radius',
|
||||||
|
'border-top-right-radius',
|
||||||
|
'border-top-left-radius',
|
||||||
|
'border-bottom-right-radius',
|
||||||
|
'border-bottom-left-radius',
|
||||||
|
'border-inline',
|
||||||
|
'border-inline-color',
|
||||||
|
'border-inline-style',
|
||||||
|
'border-inline-width',
|
||||||
|
'border-inline-start',
|
||||||
|
'border-inline-start-color',
|
||||||
|
'border-inline-start-style',
|
||||||
|
'border-inline-start-width',
|
||||||
|
'border-inline-end',
|
||||||
|
'border-inline-end-color',
|
||||||
|
'border-inline-end-style',
|
||||||
|
'border-inline-end-width',
|
||||||
|
'border-block',
|
||||||
|
'border-block-color',
|
||||||
|
'border-block-style',
|
||||||
|
'border-block-width',
|
||||||
|
'border-block-start',
|
||||||
|
'border-block-start-color',
|
||||||
|
'border-block-start-style',
|
||||||
|
'border-block-start-width',
|
||||||
|
'border-block-end',
|
||||||
|
'border-block-end-color',
|
||||||
|
'border-block-end-style',
|
||||||
|
'border-block-end-width',
|
||||||
|
'border-color',
|
||||||
|
'border-image',
|
||||||
|
'border-image-outset',
|
||||||
|
'border-image-repeat',
|
||||||
|
'border-image-slice',
|
||||||
|
'border-image-source',
|
||||||
|
'border-image-width',
|
||||||
|
'border-top-color',
|
||||||
|
'border-right-color',
|
||||||
|
'border-bottom-color',
|
||||||
|
'border-left-color',
|
||||||
|
'border-collapse',
|
||||||
|
'border-spacing',
|
||||||
|
'border-start-start-radius',
|
||||||
|
'border-start-end-radius',
|
||||||
|
'border-end-start-radius',
|
||||||
|
'border-end-end-radius',
|
||||||
|
'outline',
|
||||||
|
'outline-color',
|
||||||
|
'outline-style',
|
||||||
|
'outline-width',
|
||||||
|
'outline-offset',
|
||||||
|
|
||||||
|
'backdrop-filter',
|
||||||
|
'backface-visibility',
|
||||||
|
'background',
|
||||||
|
'background-image',
|
||||||
|
'background-position',
|
||||||
|
'background-position-x',
|
||||||
|
'background-position-y',
|
||||||
|
'background-size',
|
||||||
|
'background-repeat',
|
||||||
|
'background-attachment',
|
||||||
|
'background-origin',
|
||||||
|
'background-clip',
|
||||||
|
'background-color',
|
||||||
|
'background-blend-mode',
|
||||||
|
'box-shadow',
|
||||||
|
'isolation',
|
||||||
|
'fill',
|
||||||
|
'fill-opacity',
|
||||||
|
'fill-rule',
|
||||||
|
'flood-color',
|
||||||
|
'flood-opacity',
|
||||||
|
'lighting-color',
|
||||||
|
'marker',
|
||||||
|
'marker-end',
|
||||||
|
'marker-mid',
|
||||||
|
'marker-start',
|
||||||
|
'x',
|
||||||
|
'y',
|
||||||
|
'r',
|
||||||
|
'rx',
|
||||||
|
'ry',
|
||||||
|
'stop-color',
|
||||||
|
'stop-opacity',
|
||||||
|
'stroke',
|
||||||
|
'stroke-dasharray',
|
||||||
|
'stroke-dashoffset',
|
||||||
|
'stroke-linecap',
|
||||||
|
'stroke-linejoin',
|
||||||
|
'stroke-miterlimit',
|
||||||
|
'stroke-opacity',
|
||||||
|
'stroke-width',
|
||||||
|
|
||||||
|
'content',
|
||||||
|
'quotes',
|
||||||
|
'hanging-punctuation',
|
||||||
|
'color',
|
||||||
|
'color-interpolation',
|
||||||
|
'color-interpolation-filters',
|
||||||
|
'accent-color',
|
||||||
|
'print-color-adjust',
|
||||||
|
'forced-color-adjust',
|
||||||
|
'color-scheme',
|
||||||
|
'caret-color',
|
||||||
|
'font',
|
||||||
|
'font-style',
|
||||||
|
'font-variant',
|
||||||
|
'font-weight',
|
||||||
|
'src',
|
||||||
|
'font-stretch',
|
||||||
|
'font-size',
|
||||||
|
'size-adjust',
|
||||||
|
'line-height',
|
||||||
|
'font-family',
|
||||||
|
'font-display',
|
||||||
|
'font-kerning',
|
||||||
|
'font-language-override',
|
||||||
|
'font-optical-sizing',
|
||||||
|
'font-palette',
|
||||||
|
'font-size-adjust',
|
||||||
|
'font-synthesis',
|
||||||
|
'font-synthesis-weight',
|
||||||
|
'font-synthesis-style',
|
||||||
|
'font-synthesis-small-caps',
|
||||||
|
'font-synthesis-position',
|
||||||
|
'font-variant-alternates',
|
||||||
|
'font-variant-caps',
|
||||||
|
'font-variant-east-asian',
|
||||||
|
'font-variant-emoji',
|
||||||
|
'font-variant-ligatures',
|
||||||
|
'font-variant-numeric',
|
||||||
|
'font-variant-position',
|
||||||
|
'font-variation-settings',
|
||||||
|
'font-feature-settings',
|
||||||
|
'alignment-baseline',
|
||||||
|
'dominant-baseline',
|
||||||
|
'text-anchor',
|
||||||
|
'text-autospace',
|
||||||
|
'text-box',
|
||||||
|
'text-box-trim',
|
||||||
|
'text-box-edge',
|
||||||
|
'ascent-override',
|
||||||
|
'descent-override',
|
||||||
|
'line-gap-override',
|
||||||
|
'hyphens',
|
||||||
|
'hyphenate-character',
|
||||||
|
'hyphenate-limit-chars',
|
||||||
|
'initial-letter',
|
||||||
|
'letter-spacing',
|
||||||
|
'line-break',
|
||||||
|
'list-style',
|
||||||
|
'list-style-image',
|
||||||
|
'list-style-position',
|
||||||
|
'list-style-type',
|
||||||
|
'direction',
|
||||||
|
'text-align',
|
||||||
|
'text-align-last',
|
||||||
|
'text-decoration',
|
||||||
|
'text-decoration-line',
|
||||||
|
'text-decoration-style',
|
||||||
|
'text-decoration-color',
|
||||||
|
'text-decoration-thickness',
|
||||||
|
'text-decoration-skip-ink',
|
||||||
|
'text-emphasis',
|
||||||
|
'text-emphasis-style',
|
||||||
|
'text-emphasis-color',
|
||||||
|
'text-emphasis-position',
|
||||||
|
'text-indent',
|
||||||
|
'text-justify',
|
||||||
|
'text-underline-position',
|
||||||
|
'text-underline-offset',
|
||||||
|
'text-orientation',
|
||||||
|
'text-overflow',
|
||||||
|
'text-rendering',
|
||||||
|
'text-shadow',
|
||||||
|
'text-transform',
|
||||||
|
'text-wrap',
|
||||||
|
'text-wrap-mode',
|
||||||
|
'text-wrap-style',
|
||||||
|
'vertical-align',
|
||||||
|
'white-space',
|
||||||
|
'white-space-collapse',
|
||||||
|
'word-break',
|
||||||
|
'word-spacing',
|
||||||
|
'overflow-wrap',
|
||||||
|
|
||||||
|
'timeline-scope',
|
||||||
|
'scroll-timeline',
|
||||||
|
'scroll-timeline-name',
|
||||||
|
'scroll-timeline-axis',
|
||||||
|
'view-timeline',
|
||||||
|
'view-timeline-name',
|
||||||
|
'view-timeline-axis',
|
||||||
|
'view-timeline-inset',
|
||||||
|
'animation',
|
||||||
|
'animation-duration',
|
||||||
|
'animation-timing-function',
|
||||||
|
'animation-delay',
|
||||||
|
'animation-iteration-count',
|
||||||
|
'animation-direction',
|
||||||
|
'animation-fill-mode',
|
||||||
|
'animation-play-state',
|
||||||
|
'animation-name',
|
||||||
|
'animation-timeline',
|
||||||
|
'animation-composition',
|
||||||
|
'animation-range',
|
||||||
|
'animation-range-start',
|
||||||
|
'animation-range-end',
|
||||||
|
'view-transition-name',
|
||||||
|
'view-transition-class',
|
||||||
|
'mix-blend-mode',
|
||||||
|
'break-before',
|
||||||
|
'break-after',
|
||||||
|
'break-inside',
|
||||||
|
'page',
|
||||||
|
'caption-side',
|
||||||
|
'clip-path',
|
||||||
|
'clip-rule',
|
||||||
|
'counter-increment',
|
||||||
|
'counter-reset',
|
||||||
|
'counter-set',
|
||||||
|
'cursor',
|
||||||
|
'empty-cells',
|
||||||
|
'filter',
|
||||||
|
'image-orientation',
|
||||||
|
'image-rendering',
|
||||||
|
'mask',
|
||||||
|
'mask-border',
|
||||||
|
'mask-border-outset',
|
||||||
|
'mask-border-repeat',
|
||||||
|
'mask-border-slice',
|
||||||
|
'mask-border-source',
|
||||||
|
'mask-border-width',
|
||||||
|
'mask-clip',
|
||||||
|
'mask-composite',
|
||||||
|
'mask-image',
|
||||||
|
'mask-mode',
|
||||||
|
'mask-origin',
|
||||||
|
'mask-position',
|
||||||
|
'mask-repeat',
|
||||||
|
'mask-size',
|
||||||
|
'mask-type',
|
||||||
|
'opacity',
|
||||||
|
'perspective',
|
||||||
|
'perspective-origin',
|
||||||
|
'pointer-events',
|
||||||
|
'resize',
|
||||||
|
'scroll-behavior',
|
||||||
|
'scroll-margin',
|
||||||
|
'scroll-margin-block',
|
||||||
|
'scroll-margin-block-end',
|
||||||
|
'scroll-margin-block-start',
|
||||||
|
'scroll-margin-bottom',
|
||||||
|
'scroll-margin-inline',
|
||||||
|
'scroll-margin-inline-end',
|
||||||
|
'scroll-margin-inline-start',
|
||||||
|
'scroll-margin-left',
|
||||||
|
'scroll-margin-right',
|
||||||
|
'scroll-margin-top',
|
||||||
|
'scroll-padding',
|
||||||
|
'scroll-padding-block',
|
||||||
|
'scroll-padding-block-end',
|
||||||
|
'scroll-padding-block-start',
|
||||||
|
'scroll-padding-bottom',
|
||||||
|
'scroll-padding-inline',
|
||||||
|
'scroll-padding-inline-end',
|
||||||
|
'scroll-padding-inline-start',
|
||||||
|
'scroll-padding-left',
|
||||||
|
'scroll-padding-right',
|
||||||
|
'scroll-padding-top',
|
||||||
|
'scroll-snap-align',
|
||||||
|
'scroll-snap-stop',
|
||||||
|
'scroll-snap-type',
|
||||||
|
'scrollbar-color',
|
||||||
|
'scrollbar-gutter',
|
||||||
|
'scrollbar-width',
|
||||||
|
'shape-image-threshold',
|
||||||
|
'shape-margin',
|
||||||
|
'shape-outside',
|
||||||
|
'shape-rendering',
|
||||||
|
'tab-size',
|
||||||
|
'table-layout',
|
||||||
|
'ruby-align',
|
||||||
|
'ruby-overhang',
|
||||||
|
'ruby-position',
|
||||||
|
'math-depth',
|
||||||
|
'math-style',
|
||||||
|
'text-combine-upright',
|
||||||
|
'touch-action',
|
||||||
|
'transition',
|
||||||
|
'transition-behavior',
|
||||||
|
'transition-delay',
|
||||||
|
'transition-duration',
|
||||||
|
'transition-property',
|
||||||
|
'transition-timing-function',
|
||||||
|
'will-change',
|
||||||
|
'unicode-bidi',
|
||||||
|
'unicode-range',
|
||||||
|
'user-select',
|
||||||
|
'widows',
|
||||||
|
'writing-mode',
|
||||||
|
];
|
||||||
29
node_modules/css-select/README.md
generated
vendored
29
node_modules/css-select/README.md
generated
vendored
@@ -22,19 +22,21 @@ below.
|
|||||||
- 🧪 Partial implementation of jQuery/Sizzle extensions (see
|
- 🧪 Partial implementation of jQuery/Sizzle extensions (see
|
||||||
[cheerio-select](https://github.com/cheeriojs/cheerio-select) for the
|
[cheerio-select](https://github.com/cheeriojs/cheerio-select) for the
|
||||||
remaining selectors)
|
remaining selectors)
|
||||||
- 🧑🔬 High test coverage, including the full test suites from Sizzle, Qwery and
|
- 🧑🔬 High test coverage, including the full test suites from
|
||||||
NWMatcher.
|
[`Sizzle`](https://github.com/jquery/sizzle),
|
||||||
|
[`Qwery`](https://github.com/ded/qwery) and
|
||||||
|
[`NWMatcher`](https://github.com/dperini/nwmatcher/) and .
|
||||||
- 🥼 Reliably great performance
|
- 🥼 Reliably great performance
|
||||||
|
|
||||||
## Why?
|
## Why?
|
||||||
|
|
||||||
Most CSS engines written in JavaScript execute selectors left-to-right. That
|
Most CSS engines written in JavaScript execute selectors left-to-right. That
|
||||||
means thet execute every component of the selector in order, from left to right
|
means thet execute every component of the selector in order, from left to right.
|
||||||
_(duh)_. As an example: For the selector `a b`, these engines will first query
|
As an example: For the selector `a b`, these engines will first query for `a`
|
||||||
for `a` elements, then search these for `b` elements. (That's the approach of
|
elements, then search these for `b` elements. (That's the approach of eg.
|
||||||
eg. [`Sizzle`](https://github.com/jquery/sizzle),
|
[`Sizzle`](https://github.com/jquery/sizzle),
|
||||||
[`nwmatcher`](https://github.com/dperini/nwmatcher/) and
|
[`Qwery`](https://github.com/ded/qwery) and
|
||||||
[`qwery`](https://github.com/ded/qwery).)
|
[`NWMatcher`](https://github.com/dperini/nwmatcher/).)
|
||||||
|
|
||||||
While this works, it has some downsides: Children of `a`s will be checked
|
While this works, it has some downsides: Children of `a`s will be checked
|
||||||
multiple times; first, to check if they are also `a`s, then, for every superior
|
multiple times; first, to check if they are also `a`s, then, for every superior
|
||||||
@@ -100,7 +102,7 @@ _//TODO: More in-depth description. Implementation details. Build a spaceship._
|
|||||||
const CSSselect = require("css-select");
|
const CSSselect = require("css-select");
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note:** css-select throws errors when invalid selectors are passed to it.This
|
**Note:** css-select throws errors when invalid selectors are passed to it. This
|
||||||
is done to aid with writing css selectors, but can be unexpected when processing
|
is done to aid with writing css selectors, but can be unexpected when processing
|
||||||
arbitrary strings.
|
arbitrary strings.
|
||||||
|
|
||||||
@@ -139,11 +141,18 @@ All options are optional.
|
|||||||
- `adapter`: The adapter to use when interacting with the backing DOM
|
- `adapter`: The adapter to use when interacting with the backing DOM
|
||||||
structure. By default it uses the `domutils` module.
|
structure. By default it uses the `domutils` module.
|
||||||
- `context`: The context of the current query. Used to limit the scope of
|
- `context`: The context of the current query. Used to limit the scope of
|
||||||
searches. Can be matched directly using the `:scope` pseudo-selector.
|
searches. Can be matched directly using the `:scope` pseudo-class.
|
||||||
|
- `relativeSelector`: By default, selectors are relative to the `context`,
|
||||||
|
which means that no parent elements of the context will be matched. (Eg.
|
||||||
|
`a b c` with context `b` will never give any results.) If `relativeSelector`
|
||||||
|
is set to `false`, selectors won't be
|
||||||
|
[absolutized](http://www.w3.org/TR/selectors4/#absolutizing) and selectors
|
||||||
|
can test for parent elements outside of the `context`.
|
||||||
- `cacheResults`: Allow css-select to cache results for some selectors,
|
- `cacheResults`: Allow css-select to cache results for some selectors,
|
||||||
sometimes greatly improving querying performance. Disable this if your
|
sometimes greatly improving querying performance. Disable this if your
|
||||||
document can change in between queries with the same compiled selector.
|
document can change in between queries with the same compiled selector.
|
||||||
Default: `true`.
|
Default: `true`.
|
||||||
|
- `pseudos`: A map of pseudo-class names to functions or strings.
|
||||||
|
|
||||||
#### Custom Adapters
|
#### Custom Adapters
|
||||||
|
|
||||||
|
|||||||
2
node_modules/css-select/lib/attributes.d.ts
generated
vendored
2
node_modules/css-select/lib/attributes.d.ts
generated
vendored
@@ -1,4 +1,4 @@
|
|||||||
import { CompiledQuery, InternalOptions } from "./types";
|
import type { CompiledQuery, InternalOptions } from "./types.js";
|
||||||
import type { AttributeSelector, AttributeAction } from "css-what";
|
import type { AttributeSelector, AttributeAction } from "css-what";
|
||||||
/**
|
/**
|
||||||
* Attribute selectors
|
* Attribute selectors
|
||||||
|
|||||||
2
node_modules/css-select/lib/attributes.d.ts.map
generated
vendored
2
node_modules/css-select/lib/attributes.d.ts.map
generated
vendored
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"attributes.d.ts","sourceRoot":"","sources":["../src/attributes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AA+EnE;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,CAC/B,eAAe,EACf,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EAC3B,IAAI,EAAE,aAAa,CAAC,WAAW,CAAC,EAChC,IAAI,EAAE,iBAAiB,EACvB,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,WAAW,CAAC,KAC1C,aAAa,CAAC,WAAW,CAAC,CAsLlC,CAAC"}
|
{"version":3,"file":"attributes.d.ts","sourceRoot":"https://raw.githubusercontent.com/fb55/css-select/93caad96c807da1d48f08166ef14cf26916b9364/src/","sources":["attributes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AA+EnE;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,CAC/B,eAAe,EACf,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EAC3B,IAAI,EAAE,aAAa,CAAC,WAAW,CAAC,EAChC,IAAI,EAAE,iBAAiB,EACvB,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,WAAW,CAAC,KAC1C,aAAa,CAAC,WAAW,CAAC,CAsLlC,CAAC"}
|
||||||
14
node_modules/css-select/lib/attributes.js
generated
vendored
14
node_modules/css-select/lib/attributes.js
generated
vendored
@@ -1,7 +1,10 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.attributeRules = void 0;
|
exports.attributeRules = void 0;
|
||||||
var boolbase_1 = require("boolbase");
|
var boolbase_1 = __importDefault(require("boolbase"));
|
||||||
/**
|
/**
|
||||||
* All reserved characters in a regex, used for escaping.
|
* All reserved characters in a regex, used for escaping.
|
||||||
*
|
*
|
||||||
@@ -122,7 +125,7 @@ exports.attributeRules = {
|
|||||||
var adapter = options.adapter;
|
var adapter = options.adapter;
|
||||||
var name = data.name, value = data.value;
|
var name = data.name, value = data.value;
|
||||||
if (/\s/.test(value)) {
|
if (/\s/.test(value)) {
|
||||||
return boolbase_1.falseFunc;
|
return boolbase_1.default.falseFunc;
|
||||||
}
|
}
|
||||||
var regex = new RegExp("(?:^|\\s)".concat(escapeRegex(value), "(?:$|\\s)"), shouldIgnoreCase(data, options) ? "i" : "");
|
var regex = new RegExp("(?:^|\\s)".concat(escapeRegex(value), "(?:$|\\s)"), shouldIgnoreCase(data, options) ? "i" : "");
|
||||||
return function element(elem) {
|
return function element(elem) {
|
||||||
@@ -144,7 +147,7 @@ exports.attributeRules = {
|
|||||||
var value = data.value;
|
var value = data.value;
|
||||||
var len = value.length;
|
var len = value.length;
|
||||||
if (len === 0) {
|
if (len === 0) {
|
||||||
return boolbase_1.falseFunc;
|
return boolbase_1.default.falseFunc;
|
||||||
}
|
}
|
||||||
if (shouldIgnoreCase(data, options)) {
|
if (shouldIgnoreCase(data, options)) {
|
||||||
value = value.toLowerCase();
|
value = value.toLowerCase();
|
||||||
@@ -168,7 +171,7 @@ exports.attributeRules = {
|
|||||||
var value = data.value;
|
var value = data.value;
|
||||||
var len = -value.length;
|
var len = -value.length;
|
||||||
if (len === 0) {
|
if (len === 0) {
|
||||||
return boolbase_1.falseFunc;
|
return boolbase_1.default.falseFunc;
|
||||||
}
|
}
|
||||||
if (shouldIgnoreCase(data, options)) {
|
if (shouldIgnoreCase(data, options)) {
|
||||||
value = value.toLowerCase();
|
value = value.toLowerCase();
|
||||||
@@ -188,7 +191,7 @@ exports.attributeRules = {
|
|||||||
var adapter = options.adapter;
|
var adapter = options.adapter;
|
||||||
var name = data.name, value = data.value;
|
var name = data.name, value = data.value;
|
||||||
if (value === "") {
|
if (value === "") {
|
||||||
return boolbase_1.falseFunc;
|
return boolbase_1.default.falseFunc;
|
||||||
}
|
}
|
||||||
if (shouldIgnoreCase(data, options)) {
|
if (shouldIgnoreCase(data, options)) {
|
||||||
var regex_1 = new RegExp(escapeRegex(value), "i");
|
var regex_1 = new RegExp(escapeRegex(value), "i");
|
||||||
@@ -230,3 +233,4 @@ exports.attributeRules = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
//# sourceMappingURL=attributes.js.map
|
||||||
1
node_modules/css-select/lib/attributes.js.map
generated
vendored
Executable file
1
node_modules/css-select/lib/attributes.js.map
generated
vendored
Executable file
File diff suppressed because one or more lines are too long
3
node_modules/css-select/lib/compile.d.ts
generated
vendored
3
node_modules/css-select/lib/compile.d.ts
generated
vendored
@@ -1,6 +1,5 @@
|
|||||||
import { InternalSelector } from "./types";
|
|
||||||
import { Selector } from "css-what";
|
import { Selector } from "css-what";
|
||||||
import type { CompiledQuery, InternalOptions } from "./types";
|
import type { CompiledQuery, InternalOptions, InternalSelector } from "./types.js";
|
||||||
/**
|
/**
|
||||||
* Compiles a selector to an executable function.
|
* Compiles a selector to an executable function.
|
||||||
*
|
*
|
||||||
|
|||||||
2
node_modules/css-select/lib/compile.d.ts.map
generated
vendored
2
node_modules/css-select/lib/compile.d.ts.map
generated
vendored
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../src/compile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAS,QAAQ,EAAgB,MAAM,UAAU,CAAC;AASzD,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE9D;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EAClD,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,EAAE,EAC/B,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,WAAW,CAAC,EAC3C,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,GACxB,aAAa,CAAC,IAAI,CAAC,CAGrB;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EACxD,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,EAAE,EAC/B,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,WAAW,CAAC,EAC3C,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,GACxB,aAAa,CAAC,WAAW,CAAC,CAG5B;AAiDD,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EACvD,KAAK,EAAE,gBAAgB,EAAE,EAAE,EAC3B,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,WAAW,CAAC,EAC3C,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,GACxB,aAAa,CAAC,WAAW,CAAC,CA2C5B"}
|
{"version":3,"file":"compile.d.ts","sourceRoot":"https://raw.githubusercontent.com/fb55/css-select/93caad96c807da1d48f08166ef14cf26916b9364/src/","sources":["compile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,QAAQ,EAAgB,MAAM,UAAU,CAAC;AAQzD,OAAO,KAAK,EACR,aAAa,EACb,eAAe,EACf,gBAAgB,EACnB,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EAClD,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,EAAE,EAC/B,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,WAAW,CAAC,EAC3C,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,GACxB,aAAa,CAAC,IAAI,CAAC,CAGrB;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EACxD,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,EAAE,EAC/B,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,WAAW,CAAC,EAC3C,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,GACxB,aAAa,CAAC,WAAW,CAAC,CAG5B;AAqDD,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EACvD,KAAK,EAAE,gBAAgB,EAAE,EAAE,EAC3B,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,WAAW,CAAC,EAC3C,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,GACxB,aAAa,CAAC,WAAW,CAAC,CAsD5B"}
|
||||||
78
node_modules/css-select/lib/compile.js
generated
vendored
78
node_modules/css-select/lib/compile.js
generated
vendored
@@ -1,15 +1,37 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||||
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||||
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||||
|
}
|
||||||
|
Object.defineProperty(o, k2, desc);
|
||||||
|
}) : (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
o[k2] = m[k];
|
||||||
|
}));
|
||||||
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||||
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||||
|
}) : function(o, v) {
|
||||||
|
o["default"] = v;
|
||||||
|
});
|
||||||
|
var __importStar = (this && this.__importStar) || function (mod) {
|
||||||
|
if (mod && mod.__esModule) return mod;
|
||||||
|
var result = {};
|
||||||
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||||
|
__setModuleDefault(result, mod);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.compileToken = exports.compileUnsafe = exports.compile = void 0;
|
exports.compileToken = exports.compileUnsafe = exports.compile = void 0;
|
||||||
var css_what_1 = require("css-what");
|
var css_what_1 = require("css-what");
|
||||||
var boolbase_1 = require("boolbase");
|
var boolbase_1 = __importDefault(require("boolbase"));
|
||||||
var sort_1 = __importDefault(require("./sort"));
|
var sort_js_1 = __importStar(require("./sort.js"));
|
||||||
var procedure_1 = require("./procedure");
|
var general_js_1 = require("./general.js");
|
||||||
var general_1 = require("./general");
|
var subselects_js_1 = require("./pseudo-selectors/subselects.js");
|
||||||
var subselects_1 = require("./pseudo-selectors/subselects");
|
|
||||||
/**
|
/**
|
||||||
* Compiles a selector to an executable function.
|
* Compiles a selector to an executable function.
|
||||||
*
|
*
|
||||||
@@ -19,7 +41,7 @@ var subselects_1 = require("./pseudo-selectors/subselects");
|
|||||||
*/
|
*/
|
||||||
function compile(selector, options, context) {
|
function compile(selector, options, context) {
|
||||||
var next = compileUnsafe(selector, options, context);
|
var next = compileUnsafe(selector, options, context);
|
||||||
return (0, subselects_1.ensureIsTag)(next, options.adapter);
|
return (0, subselects_js_1.ensureIsTag)(next, options.adapter);
|
||||||
}
|
}
|
||||||
exports.compile = compile;
|
exports.compile = compile;
|
||||||
function compileUnsafe(selector, options, context) {
|
function compileUnsafe(selector, options, context) {
|
||||||
@@ -28,7 +50,7 @@ function compileUnsafe(selector, options, context) {
|
|||||||
}
|
}
|
||||||
exports.compileUnsafe = compileUnsafe;
|
exports.compileUnsafe = compileUnsafe;
|
||||||
function includesScopePseudo(t) {
|
function includesScopePseudo(t) {
|
||||||
return (t.type === "pseudo" &&
|
return (t.type === css_what_1.SelectorType.Pseudo &&
|
||||||
(t.name === "scope" ||
|
(t.name === "scope" ||
|
||||||
(Array.isArray(t.data) &&
|
(Array.isArray(t.data) &&
|
||||||
t.data.some(function (data) { return data.some(includesScopePseudo); }))));
|
t.data.some(function (data) { return data.some(includesScopePseudo); }))));
|
||||||
@@ -43,7 +65,7 @@ var SCOPE_TOKEN = {
|
|||||||
data: null,
|
data: null,
|
||||||
};
|
};
|
||||||
/*
|
/*
|
||||||
* CSS 4 Spec (Draft): 3.3.1. Absolutizing a Scope-relative Selector
|
* CSS 4 Spec (Draft): 3.4.1. Absolutizing a Relative Selector
|
||||||
* http://www.w3.org/TR/selectors4/#absolutizing
|
* http://www.w3.org/TR/selectors4/#absolutizing
|
||||||
*/
|
*/
|
||||||
function absolutize(token, _a, context) {
|
function absolutize(token, _a, context) {
|
||||||
@@ -51,11 +73,13 @@ function absolutize(token, _a, context) {
|
|||||||
// TODO Use better check if the context is a document
|
// TODO Use better check if the context is a document
|
||||||
var hasContext = !!(context === null || context === void 0 ? void 0 : context.every(function (e) {
|
var hasContext = !!(context === null || context === void 0 ? void 0 : context.every(function (e) {
|
||||||
var parent = adapter.isTag(e) && adapter.getParent(e);
|
var parent = adapter.isTag(e) && adapter.getParent(e);
|
||||||
return e === subselects_1.PLACEHOLDER_ELEMENT || (parent && adapter.isTag(parent));
|
return e === subselects_js_1.PLACEHOLDER_ELEMENT || (parent && adapter.isTag(parent));
|
||||||
}));
|
}));
|
||||||
for (var _i = 0, token_1 = token; _i < token_1.length; _i++) {
|
for (var _i = 0, token_1 = token; _i < token_1.length; _i++) {
|
||||||
var t = token_1[_i];
|
var t = token_1[_i];
|
||||||
if (t.length > 0 && (0, procedure_1.isTraversal)(t[0]) && t[0].type !== "descendant") {
|
if (t.length > 0 &&
|
||||||
|
(0, sort_js_1.isTraversal)(t[0]) &&
|
||||||
|
t[0].type !== css_what_1.SelectorType.Descendant) {
|
||||||
// Don't continue in else branch
|
// Don't continue in else branch
|
||||||
}
|
}
|
||||||
else if (hasContext && !t.some(includesScopePseudo)) {
|
else if (hasContext && !t.some(includesScopePseudo)) {
|
||||||
@@ -69,31 +93,38 @@ function absolutize(token, _a, context) {
|
|||||||
}
|
}
|
||||||
function compileToken(token, options, context) {
|
function compileToken(token, options, context) {
|
||||||
var _a;
|
var _a;
|
||||||
token = token.filter(function (t) { return t.length > 0; });
|
token.forEach(sort_js_1.default);
|
||||||
token.forEach(sort_1.default);
|
|
||||||
context = (_a = options.context) !== null && _a !== void 0 ? _a : context;
|
context = (_a = options.context) !== null && _a !== void 0 ? _a : context;
|
||||||
var isArrayContext = Array.isArray(context);
|
var isArrayContext = Array.isArray(context);
|
||||||
var finalContext = context && (Array.isArray(context) ? context : [context]);
|
var finalContext = context && (Array.isArray(context) ? context : [context]);
|
||||||
|
// Check if the selector is relative
|
||||||
|
if (options.relativeSelector !== false) {
|
||||||
absolutize(token, options, finalContext);
|
absolutize(token, options, finalContext);
|
||||||
|
}
|
||||||
|
else if (token.some(function (t) { return t.length > 0 && (0, sort_js_1.isTraversal)(t[0]); })) {
|
||||||
|
throw new Error("Relative selectors are not allowed when the `relativeSelector` option is disabled");
|
||||||
|
}
|
||||||
var shouldTestNextSiblings = false;
|
var shouldTestNextSiblings = false;
|
||||||
var query = token
|
var query = token
|
||||||
.map(function (rules) {
|
.map(function (rules) {
|
||||||
if (rules.length >= 2) {
|
if (rules.length >= 2) {
|
||||||
var first = rules[0], second = rules[1];
|
var first = rules[0], second = rules[1];
|
||||||
if (first.type !== "pseudo" || first.name !== "scope") {
|
if (first.type !== css_what_1.SelectorType.Pseudo ||
|
||||||
|
first.name !== "scope") {
|
||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
else if (isArrayContext && second.type === "descendant") {
|
else if (isArrayContext &&
|
||||||
|
second.type === css_what_1.SelectorType.Descendant) {
|
||||||
rules[1] = FLEXIBLE_DESCENDANT_TOKEN;
|
rules[1] = FLEXIBLE_DESCENDANT_TOKEN;
|
||||||
}
|
}
|
||||||
else if (second.type === "adjacent" ||
|
else if (second.type === css_what_1.SelectorType.Adjacent ||
|
||||||
second.type === "sibling") {
|
second.type === css_what_1.SelectorType.Sibling) {
|
||||||
shouldTestNextSiblings = true;
|
shouldTestNextSiblings = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return compileRules(rules, options, finalContext);
|
return compileRules(rules, options, finalContext);
|
||||||
})
|
})
|
||||||
.reduce(reduceRules, boolbase_1.falseFunc);
|
.reduce(reduceRules, boolbase_1.default.falseFunc);
|
||||||
query.shouldTestNextSiblings = shouldTestNextSiblings;
|
query.shouldTestNextSiblings = shouldTestNextSiblings;
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
@@ -101,19 +132,20 @@ exports.compileToken = compileToken;
|
|||||||
function compileRules(rules, options, context) {
|
function compileRules(rules, options, context) {
|
||||||
var _a;
|
var _a;
|
||||||
return rules.reduce(function (previous, rule) {
|
return rules.reduce(function (previous, rule) {
|
||||||
return previous === boolbase_1.falseFunc
|
return previous === boolbase_1.default.falseFunc
|
||||||
? boolbase_1.falseFunc
|
? boolbase_1.default.falseFunc
|
||||||
: (0, general_1.compileGeneralSelector)(previous, rule, options, context, compileToken);
|
: (0, general_js_1.compileGeneralSelector)(previous, rule, options, context, compileToken);
|
||||||
}, (_a = options.rootFunc) !== null && _a !== void 0 ? _a : boolbase_1.trueFunc);
|
}, (_a = options.rootFunc) !== null && _a !== void 0 ? _a : boolbase_1.default.trueFunc);
|
||||||
}
|
}
|
||||||
function reduceRules(a, b) {
|
function reduceRules(a, b) {
|
||||||
if (b === boolbase_1.falseFunc || a === boolbase_1.trueFunc) {
|
if (b === boolbase_1.default.falseFunc || a === boolbase_1.default.trueFunc) {
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
if (a === boolbase_1.falseFunc || b === boolbase_1.trueFunc) {
|
if (a === boolbase_1.default.falseFunc || b === boolbase_1.default.trueFunc) {
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
return function combine(elem) {
|
return function combine(elem) {
|
||||||
return a(elem) || b(elem);
|
return a(elem) || b(elem);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
//# sourceMappingURL=compile.js.map
|
||||||
1
node_modules/css-select/lib/compile.js.map
generated
vendored
Executable file
1
node_modules/css-select/lib/compile.js.map
generated
vendored
Executable file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"compile.js","sourceRoot":"https://raw.githubusercontent.com/fb55/css-select/93caad96c807da1d48f08166ef14cf26916b9364/src/","sources":["compile.ts"],"names":[],"mappings":"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"}
|
||||||
7
node_modules/css-select/lib/esm/attributes.d.ts
generated
vendored
Executable file
7
node_modules/css-select/lib/esm/attributes.d.ts
generated
vendored
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { CompiledQuery, InternalOptions } from "./types.js";
|
||||||
|
import type { AttributeSelector, AttributeAction } from "css-what";
|
||||||
|
/**
|
||||||
|
* Attribute selectors
|
||||||
|
*/
|
||||||
|
export declare const attributeRules: Record<AttributeAction, <Node, ElementNode extends Node>(next: CompiledQuery<ElementNode>, data: AttributeSelector, options: InternalOptions<Node, ElementNode>) => CompiledQuery<ElementNode>>;
|
||||||
|
//# sourceMappingURL=attributes.d.ts.map
|
||||||
1
node_modules/css-select/lib/esm/attributes.d.ts.map
generated
vendored
Executable file
1
node_modules/css-select/lib/esm/attributes.d.ts.map
generated
vendored
Executable file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"attributes.d.ts","sourceRoot":"https://raw.githubusercontent.com/fb55/css-select/93caad96c807da1d48f08166ef14cf26916b9364/src/","sources":["attributes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AA+EnE;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,CAC/B,eAAe,EACf,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EAC3B,IAAI,EAAE,aAAa,CAAC,WAAW,CAAC,EAChC,IAAI,EAAE,iBAAiB,EACvB,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,WAAW,CAAC,KAC1C,aAAa,CAAC,WAAW,CAAC,CAsLlC,CAAC"}
|
||||||
222
node_modules/css-select/lib/esm/attributes.js
generated
vendored
Executable file
222
node_modules/css-select/lib/esm/attributes.js
generated
vendored
Executable file
@@ -0,0 +1,222 @@
|
|||||||
|
import boolbase from "boolbase";
|
||||||
|
/**
|
||||||
|
* All reserved characters in a regex, used for escaping.
|
||||||
|
*
|
||||||
|
* Taken from XRegExp, (c) 2007-2020 Steven Levithan under the MIT license
|
||||||
|
* https://github.com/slevithan/xregexp/blob/95eeebeb8fac8754d54eafe2b4743661ac1cf028/src/xregexp.js#L794
|
||||||
|
*/
|
||||||
|
const reChars = /[-[\]{}()*+?.,\\^$|#\s]/g;
|
||||||
|
function escapeRegex(value) {
|
||||||
|
return value.replace(reChars, "\\$&");
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Attributes that are case-insensitive in HTML.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @see https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors
|
||||||
|
*/
|
||||||
|
const caseInsensitiveAttributes = new Set([
|
||||||
|
"accept",
|
||||||
|
"accept-charset",
|
||||||
|
"align",
|
||||||
|
"alink",
|
||||||
|
"axis",
|
||||||
|
"bgcolor",
|
||||||
|
"charset",
|
||||||
|
"checked",
|
||||||
|
"clear",
|
||||||
|
"codetype",
|
||||||
|
"color",
|
||||||
|
"compact",
|
||||||
|
"declare",
|
||||||
|
"defer",
|
||||||
|
"dir",
|
||||||
|
"direction",
|
||||||
|
"disabled",
|
||||||
|
"enctype",
|
||||||
|
"face",
|
||||||
|
"frame",
|
||||||
|
"hreflang",
|
||||||
|
"http-equiv",
|
||||||
|
"lang",
|
||||||
|
"language",
|
||||||
|
"link",
|
||||||
|
"media",
|
||||||
|
"method",
|
||||||
|
"multiple",
|
||||||
|
"nohref",
|
||||||
|
"noresize",
|
||||||
|
"noshade",
|
||||||
|
"nowrap",
|
||||||
|
"readonly",
|
||||||
|
"rel",
|
||||||
|
"rev",
|
||||||
|
"rules",
|
||||||
|
"scope",
|
||||||
|
"scrolling",
|
||||||
|
"selected",
|
||||||
|
"shape",
|
||||||
|
"target",
|
||||||
|
"text",
|
||||||
|
"type",
|
||||||
|
"valign",
|
||||||
|
"valuetype",
|
||||||
|
"vlink",
|
||||||
|
]);
|
||||||
|
function shouldIgnoreCase(selector, options) {
|
||||||
|
return typeof selector.ignoreCase === "boolean"
|
||||||
|
? selector.ignoreCase
|
||||||
|
: selector.ignoreCase === "quirks"
|
||||||
|
? !!options.quirksMode
|
||||||
|
: !options.xmlMode && caseInsensitiveAttributes.has(selector.name);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Attribute selectors
|
||||||
|
*/
|
||||||
|
export const attributeRules = {
|
||||||
|
equals(next, data, options) {
|
||||||
|
const { adapter } = options;
|
||||||
|
const { name } = data;
|
||||||
|
let { value } = data;
|
||||||
|
if (shouldIgnoreCase(data, options)) {
|
||||||
|
value = value.toLowerCase();
|
||||||
|
return (elem) => {
|
||||||
|
const attr = adapter.getAttributeValue(elem, name);
|
||||||
|
return (attr != null &&
|
||||||
|
attr.length === value.length &&
|
||||||
|
attr.toLowerCase() === value &&
|
||||||
|
next(elem));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return (elem) => adapter.getAttributeValue(elem, name) === value && next(elem);
|
||||||
|
},
|
||||||
|
hyphen(next, data, options) {
|
||||||
|
const { adapter } = options;
|
||||||
|
const { name } = data;
|
||||||
|
let { value } = data;
|
||||||
|
const len = value.length;
|
||||||
|
if (shouldIgnoreCase(data, options)) {
|
||||||
|
value = value.toLowerCase();
|
||||||
|
return function hyphenIC(elem) {
|
||||||
|
const attr = adapter.getAttributeValue(elem, name);
|
||||||
|
return (attr != null &&
|
||||||
|
(attr.length === len || attr.charAt(len) === "-") &&
|
||||||
|
attr.substr(0, len).toLowerCase() === value &&
|
||||||
|
next(elem));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return function hyphen(elem) {
|
||||||
|
const attr = adapter.getAttributeValue(elem, name);
|
||||||
|
return (attr != null &&
|
||||||
|
(attr.length === len || attr.charAt(len) === "-") &&
|
||||||
|
attr.substr(0, len) === value &&
|
||||||
|
next(elem));
|
||||||
|
};
|
||||||
|
},
|
||||||
|
element(next, data, options) {
|
||||||
|
const { adapter } = options;
|
||||||
|
const { name, value } = data;
|
||||||
|
if (/\s/.test(value)) {
|
||||||
|
return boolbase.falseFunc;
|
||||||
|
}
|
||||||
|
const regex = new RegExp(`(?:^|\\s)${escapeRegex(value)}(?:$|\\s)`, shouldIgnoreCase(data, options) ? "i" : "");
|
||||||
|
return function element(elem) {
|
||||||
|
const attr = adapter.getAttributeValue(elem, name);
|
||||||
|
return (attr != null &&
|
||||||
|
attr.length >= value.length &&
|
||||||
|
regex.test(attr) &&
|
||||||
|
next(elem));
|
||||||
|
};
|
||||||
|
},
|
||||||
|
exists(next, { name }, { adapter }) {
|
||||||
|
return (elem) => adapter.hasAttrib(elem, name) && next(elem);
|
||||||
|
},
|
||||||
|
start(next, data, options) {
|
||||||
|
const { adapter } = options;
|
||||||
|
const { name } = data;
|
||||||
|
let { value } = data;
|
||||||
|
const len = value.length;
|
||||||
|
if (len === 0) {
|
||||||
|
return boolbase.falseFunc;
|
||||||
|
}
|
||||||
|
if (shouldIgnoreCase(data, options)) {
|
||||||
|
value = value.toLowerCase();
|
||||||
|
return (elem) => {
|
||||||
|
const attr = adapter.getAttributeValue(elem, name);
|
||||||
|
return (attr != null &&
|
||||||
|
attr.length >= len &&
|
||||||
|
attr.substr(0, len).toLowerCase() === value &&
|
||||||
|
next(elem));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return (elem) => {
|
||||||
|
var _a;
|
||||||
|
return !!((_a = adapter.getAttributeValue(elem, name)) === null || _a === void 0 ? void 0 : _a.startsWith(value)) &&
|
||||||
|
next(elem);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
end(next, data, options) {
|
||||||
|
const { adapter } = options;
|
||||||
|
const { name } = data;
|
||||||
|
let { value } = data;
|
||||||
|
const len = -value.length;
|
||||||
|
if (len === 0) {
|
||||||
|
return boolbase.falseFunc;
|
||||||
|
}
|
||||||
|
if (shouldIgnoreCase(data, options)) {
|
||||||
|
value = value.toLowerCase();
|
||||||
|
return (elem) => {
|
||||||
|
var _a;
|
||||||
|
return ((_a = adapter
|
||||||
|
.getAttributeValue(elem, name)) === null || _a === void 0 ? void 0 : _a.substr(len).toLowerCase()) === value && next(elem);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return (elem) => {
|
||||||
|
var _a;
|
||||||
|
return !!((_a = adapter.getAttributeValue(elem, name)) === null || _a === void 0 ? void 0 : _a.endsWith(value)) &&
|
||||||
|
next(elem);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
any(next, data, options) {
|
||||||
|
const { adapter } = options;
|
||||||
|
const { name, value } = data;
|
||||||
|
if (value === "") {
|
||||||
|
return boolbase.falseFunc;
|
||||||
|
}
|
||||||
|
if (shouldIgnoreCase(data, options)) {
|
||||||
|
const regex = new RegExp(escapeRegex(value), "i");
|
||||||
|
return function anyIC(elem) {
|
||||||
|
const attr = adapter.getAttributeValue(elem, name);
|
||||||
|
return (attr != null &&
|
||||||
|
attr.length >= value.length &&
|
||||||
|
regex.test(attr) &&
|
||||||
|
next(elem));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return (elem) => {
|
||||||
|
var _a;
|
||||||
|
return !!((_a = adapter.getAttributeValue(elem, name)) === null || _a === void 0 ? void 0 : _a.includes(value)) &&
|
||||||
|
next(elem);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
not(next, data, options) {
|
||||||
|
const { adapter } = options;
|
||||||
|
const { name } = data;
|
||||||
|
let { value } = data;
|
||||||
|
if (value === "") {
|
||||||
|
return (elem) => !!adapter.getAttributeValue(elem, name) && next(elem);
|
||||||
|
}
|
||||||
|
else if (shouldIgnoreCase(data, options)) {
|
||||||
|
value = value.toLowerCase();
|
||||||
|
return (elem) => {
|
||||||
|
const attr = adapter.getAttributeValue(elem, name);
|
||||||
|
return ((attr == null ||
|
||||||
|
attr.length !== value.length ||
|
||||||
|
attr.toLowerCase() !== value) &&
|
||||||
|
next(elem));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return (elem) => adapter.getAttributeValue(elem, name) !== value && next(elem);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
//# sourceMappingURL=attributes.js.map
|
||||||
1
node_modules/css-select/lib/esm/attributes.js.map
generated
vendored
Executable file
1
node_modules/css-select/lib/esm/attributes.js.map
generated
vendored
Executable file
File diff suppressed because one or more lines are too long
13
node_modules/css-select/lib/esm/compile.d.ts
generated
vendored
Executable file
13
node_modules/css-select/lib/esm/compile.d.ts
generated
vendored
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
import { Selector } from "css-what";
|
||||||
|
import type { CompiledQuery, InternalOptions, InternalSelector } from "./types.js";
|
||||||
|
/**
|
||||||
|
* Compiles a selector to an executable function.
|
||||||
|
*
|
||||||
|
* @param selector Selector to compile.
|
||||||
|
* @param options Compilation options.
|
||||||
|
* @param context Optional context for the selector.
|
||||||
|
*/
|
||||||
|
export declare function compile<Node, ElementNode extends Node>(selector: string | Selector[][], options: InternalOptions<Node, ElementNode>, context?: Node[] | Node): CompiledQuery<Node>;
|
||||||
|
export declare function compileUnsafe<Node, ElementNode extends Node>(selector: string | Selector[][], options: InternalOptions<Node, ElementNode>, context?: Node[] | Node): CompiledQuery<ElementNode>;
|
||||||
|
export declare function compileToken<Node, ElementNode extends Node>(token: InternalSelector[][], options: InternalOptions<Node, ElementNode>, context?: Node[] | Node): CompiledQuery<ElementNode>;
|
||||||
|
//# sourceMappingURL=compile.d.ts.map
|
||||||
1
node_modules/css-select/lib/esm/compile.d.ts.map
generated
vendored
Executable file
1
node_modules/css-select/lib/esm/compile.d.ts.map
generated
vendored
Executable file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"compile.d.ts","sourceRoot":"https://raw.githubusercontent.com/fb55/css-select/93caad96c807da1d48f08166ef14cf26916b9364/src/","sources":["compile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,QAAQ,EAAgB,MAAM,UAAU,CAAC;AAQzD,OAAO,KAAK,EACR,aAAa,EACb,eAAe,EACf,gBAAgB,EACnB,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EAClD,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,EAAE,EAC/B,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,WAAW,CAAC,EAC3C,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,GACxB,aAAa,CAAC,IAAI,CAAC,CAGrB;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EACxD,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,EAAE,EAC/B,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,WAAW,CAAC,EAC3C,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,GACxB,aAAa,CAAC,WAAW,CAAC,CAG5B;AAqDD,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EACvD,KAAK,EAAE,gBAAgB,EAAE,EAAE,EAC3B,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,WAAW,CAAC,EAC3C,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,GACxB,aAAa,CAAC,WAAW,CAAC,CAsD5B"}
|
||||||
115
node_modules/css-select/lib/esm/compile.js
generated
vendored
Executable file
115
node_modules/css-select/lib/esm/compile.js
generated
vendored
Executable file
@@ -0,0 +1,115 @@
|
|||||||
|
import { parse, SelectorType } from "css-what";
|
||||||
|
import boolbase from "boolbase";
|
||||||
|
import sortRules, { isTraversal } from "./sort.js";
|
||||||
|
import { compileGeneralSelector } from "./general.js";
|
||||||
|
import { ensureIsTag, PLACEHOLDER_ELEMENT, } from "./pseudo-selectors/subselects.js";
|
||||||
|
/**
|
||||||
|
* Compiles a selector to an executable function.
|
||||||
|
*
|
||||||
|
* @param selector Selector to compile.
|
||||||
|
* @param options Compilation options.
|
||||||
|
* @param context Optional context for the selector.
|
||||||
|
*/
|
||||||
|
export function compile(selector, options, context) {
|
||||||
|
const next = compileUnsafe(selector, options, context);
|
||||||
|
return ensureIsTag(next, options.adapter);
|
||||||
|
}
|
||||||
|
export function compileUnsafe(selector, options, context) {
|
||||||
|
const token = typeof selector === "string" ? parse(selector) : selector;
|
||||||
|
return compileToken(token, options, context);
|
||||||
|
}
|
||||||
|
function includesScopePseudo(t) {
|
||||||
|
return (t.type === SelectorType.Pseudo &&
|
||||||
|
(t.name === "scope" ||
|
||||||
|
(Array.isArray(t.data) &&
|
||||||
|
t.data.some((data) => data.some(includesScopePseudo)))));
|
||||||
|
}
|
||||||
|
const DESCENDANT_TOKEN = { type: SelectorType.Descendant };
|
||||||
|
const FLEXIBLE_DESCENDANT_TOKEN = {
|
||||||
|
type: "_flexibleDescendant",
|
||||||
|
};
|
||||||
|
const SCOPE_TOKEN = {
|
||||||
|
type: SelectorType.Pseudo,
|
||||||
|
name: "scope",
|
||||||
|
data: null,
|
||||||
|
};
|
||||||
|
/*
|
||||||
|
* CSS 4 Spec (Draft): 3.4.1. Absolutizing a Relative Selector
|
||||||
|
* http://www.w3.org/TR/selectors4/#absolutizing
|
||||||
|
*/
|
||||||
|
function absolutize(token, { adapter }, context) {
|
||||||
|
// TODO Use better check if the context is a document
|
||||||
|
const hasContext = !!(context === null || context === void 0 ? void 0 : context.every((e) => {
|
||||||
|
const parent = adapter.isTag(e) && adapter.getParent(e);
|
||||||
|
return e === PLACEHOLDER_ELEMENT || (parent && adapter.isTag(parent));
|
||||||
|
}));
|
||||||
|
for (const t of token) {
|
||||||
|
if (t.length > 0 &&
|
||||||
|
isTraversal(t[0]) &&
|
||||||
|
t[0].type !== SelectorType.Descendant) {
|
||||||
|
// Don't continue in else branch
|
||||||
|
}
|
||||||
|
else if (hasContext && !t.some(includesScopePseudo)) {
|
||||||
|
t.unshift(DESCENDANT_TOKEN);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
t.unshift(SCOPE_TOKEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function compileToken(token, options, context) {
|
||||||
|
var _a;
|
||||||
|
token.forEach(sortRules);
|
||||||
|
context = (_a = options.context) !== null && _a !== void 0 ? _a : context;
|
||||||
|
const isArrayContext = Array.isArray(context);
|
||||||
|
const finalContext = context && (Array.isArray(context) ? context : [context]);
|
||||||
|
// Check if the selector is relative
|
||||||
|
if (options.relativeSelector !== false) {
|
||||||
|
absolutize(token, options, finalContext);
|
||||||
|
}
|
||||||
|
else if (token.some((t) => t.length > 0 && isTraversal(t[0]))) {
|
||||||
|
throw new Error("Relative selectors are not allowed when the `relativeSelector` option is disabled");
|
||||||
|
}
|
||||||
|
let shouldTestNextSiblings = false;
|
||||||
|
const query = token
|
||||||
|
.map((rules) => {
|
||||||
|
if (rules.length >= 2) {
|
||||||
|
const [first, second] = rules;
|
||||||
|
if (first.type !== SelectorType.Pseudo ||
|
||||||
|
first.name !== "scope") {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
else if (isArrayContext &&
|
||||||
|
second.type === SelectorType.Descendant) {
|
||||||
|
rules[1] = FLEXIBLE_DESCENDANT_TOKEN;
|
||||||
|
}
|
||||||
|
else if (second.type === SelectorType.Adjacent ||
|
||||||
|
second.type === SelectorType.Sibling) {
|
||||||
|
shouldTestNextSiblings = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return compileRules(rules, options, finalContext);
|
||||||
|
})
|
||||||
|
.reduce(reduceRules, boolbase.falseFunc);
|
||||||
|
query.shouldTestNextSiblings = shouldTestNextSiblings;
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
function compileRules(rules, options, context) {
|
||||||
|
var _a;
|
||||||
|
return rules.reduce((previous, rule) => previous === boolbase.falseFunc
|
||||||
|
? boolbase.falseFunc
|
||||||
|
: compileGeneralSelector(previous, rule, options, context, compileToken), (_a = options.rootFunc) !== null && _a !== void 0 ? _a : boolbase.trueFunc);
|
||||||
|
}
|
||||||
|
function reduceRules(a, b) {
|
||||||
|
if (b === boolbase.falseFunc || a === boolbase.trueFunc) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
if (a === boolbase.falseFunc || b === boolbase.trueFunc) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
return function combine(elem) {
|
||||||
|
return a(elem) || b(elem);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=compile.js.map
|
||||||
1
node_modules/css-select/lib/esm/compile.js.map
generated
vendored
Executable file
1
node_modules/css-select/lib/esm/compile.js.map
generated
vendored
Executable file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"compile.js","sourceRoot":"https://raw.githubusercontent.com/fb55/css-select/93caad96c807da1d48f08166ef14cf26916b9364/src/","sources":["compile.ts"],"names":[],"mappings":"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"}
|
||||||
3
node_modules/css-select/lib/esm/general.d.ts
generated
vendored
Executable file
3
node_modules/css-select/lib/esm/general.d.ts
generated
vendored
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
import type { CompiledQuery, InternalOptions, InternalSelector, CompileToken } from "./types.js";
|
||||||
|
export declare function compileGeneralSelector<Node, ElementNode extends Node>(next: CompiledQuery<ElementNode>, selector: InternalSelector, options: InternalOptions<Node, ElementNode>, context: Node[] | undefined, compileToken: CompileToken<Node, ElementNode>): CompiledQuery<ElementNode>;
|
||||||
|
//# sourceMappingURL=general.d.ts.map
|
||||||
1
node_modules/css-select/lib/esm/general.d.ts.map
generated
vendored
Executable file
1
node_modules/css-select/lib/esm/general.d.ts.map
generated
vendored
Executable file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"general.d.ts","sourceRoot":"https://raw.githubusercontent.com/fb55/css-select/93caad96c807da1d48f08166ef14cf26916b9364/src/","sources":["general.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAER,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,YAAY,EACf,MAAM,YAAY,CAAC;AAkBpB,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EACjE,IAAI,EAAE,aAAa,CAAC,WAAW,CAAC,EAChC,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,WAAW,CAAC,EAC3C,OAAO,EAAE,IAAI,EAAE,GAAG,SAAS,EAC3B,YAAY,EAAE,YAAY,CAAC,IAAI,EAAE,WAAW,CAAC,GAC9C,aAAa,CAAC,WAAW,CAAC,CAiK5B"}
|
||||||
144
node_modules/css-select/lib/esm/general.js
generated
vendored
Executable file
144
node_modules/css-select/lib/esm/general.js
generated
vendored
Executable file
@@ -0,0 +1,144 @@
|
|||||||
|
import { attributeRules } from "./attributes.js";
|
||||||
|
import { compilePseudoSelector } from "./pseudo-selectors/index.js";
|
||||||
|
import { SelectorType } from "css-what";
|
||||||
|
function getElementParent(node, adapter) {
|
||||||
|
const parent = adapter.getParent(node);
|
||||||
|
if (parent && adapter.isTag(parent)) {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* All available rules
|
||||||
|
*/
|
||||||
|
export function compileGeneralSelector(next, selector, options, context, compileToken) {
|
||||||
|
const { adapter, equals } = options;
|
||||||
|
switch (selector.type) {
|
||||||
|
case SelectorType.PseudoElement: {
|
||||||
|
throw new Error("Pseudo-elements are not supported by css-select");
|
||||||
|
}
|
||||||
|
case SelectorType.ColumnCombinator: {
|
||||||
|
throw new Error("Column combinators are not yet supported by css-select");
|
||||||
|
}
|
||||||
|
case SelectorType.Attribute: {
|
||||||
|
if (selector.namespace != null) {
|
||||||
|
throw new Error("Namespaced attributes are not yet supported by css-select");
|
||||||
|
}
|
||||||
|
if (!options.xmlMode || options.lowerCaseAttributeNames) {
|
||||||
|
selector.name = selector.name.toLowerCase();
|
||||||
|
}
|
||||||
|
return attributeRules[selector.action](next, selector, options);
|
||||||
|
}
|
||||||
|
case SelectorType.Pseudo: {
|
||||||
|
return compilePseudoSelector(next, selector, options, context, compileToken);
|
||||||
|
}
|
||||||
|
// Tags
|
||||||
|
case SelectorType.Tag: {
|
||||||
|
if (selector.namespace != null) {
|
||||||
|
throw new Error("Namespaced tag names are not yet supported by css-select");
|
||||||
|
}
|
||||||
|
let { name } = selector;
|
||||||
|
if (!options.xmlMode || options.lowerCaseTags) {
|
||||||
|
name = name.toLowerCase();
|
||||||
|
}
|
||||||
|
return function tag(elem) {
|
||||||
|
return adapter.getName(elem) === name && next(elem);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Traversal
|
||||||
|
case SelectorType.Descendant: {
|
||||||
|
if (options.cacheResults === false ||
|
||||||
|
typeof WeakSet === "undefined") {
|
||||||
|
return function descendant(elem) {
|
||||||
|
let current = elem;
|
||||||
|
while ((current = getElementParent(current, adapter))) {
|
||||||
|
if (next(current)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// @ts-expect-error `ElementNode` is not extending object
|
||||||
|
const isFalseCache = new WeakSet();
|
||||||
|
return function cachedDescendant(elem) {
|
||||||
|
let current = elem;
|
||||||
|
while ((current = getElementParent(current, adapter))) {
|
||||||
|
if (!isFalseCache.has(current)) {
|
||||||
|
if (adapter.isTag(current) && next(current)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
isFalseCache.add(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "_flexibleDescendant": {
|
||||||
|
// Include element itself, only used while querying an array
|
||||||
|
return function flexibleDescendant(elem) {
|
||||||
|
let current = elem;
|
||||||
|
do {
|
||||||
|
if (next(current))
|
||||||
|
return true;
|
||||||
|
} while ((current = getElementParent(current, adapter)));
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case SelectorType.Parent: {
|
||||||
|
return function parent(elem) {
|
||||||
|
return adapter
|
||||||
|
.getChildren(elem)
|
||||||
|
.some((elem) => adapter.isTag(elem) && next(elem));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case SelectorType.Child: {
|
||||||
|
return function child(elem) {
|
||||||
|
const parent = adapter.getParent(elem);
|
||||||
|
return parent != null && adapter.isTag(parent) && next(parent);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case SelectorType.Sibling: {
|
||||||
|
return function sibling(elem) {
|
||||||
|
const siblings = adapter.getSiblings(elem);
|
||||||
|
for (let i = 0; i < siblings.length; i++) {
|
||||||
|
const currentSibling = siblings[i];
|
||||||
|
if (equals(elem, currentSibling))
|
||||||
|
break;
|
||||||
|
if (adapter.isTag(currentSibling) && next(currentSibling)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case SelectorType.Adjacent: {
|
||||||
|
if (adapter.prevElementSibling) {
|
||||||
|
return function adjacent(elem) {
|
||||||
|
const previous = adapter.prevElementSibling(elem);
|
||||||
|
return previous != null && next(previous);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return function adjacent(elem) {
|
||||||
|
const siblings = adapter.getSiblings(elem);
|
||||||
|
let lastElement;
|
||||||
|
for (let i = 0; i < siblings.length; i++) {
|
||||||
|
const currentSibling = siblings[i];
|
||||||
|
if (equals(elem, currentSibling))
|
||||||
|
break;
|
||||||
|
if (adapter.isTag(currentSibling)) {
|
||||||
|
lastElement = currentSibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !!lastElement && next(lastElement);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case SelectorType.Universal: {
|
||||||
|
if (selector.namespace != null && selector.namespace !== "*") {
|
||||||
|
throw new Error("Namespaced universal selectors are not yet supported by css-select");
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=general.js.map
|
||||||
1
node_modules/css-select/lib/esm/general.js.map
generated
vendored
Executable file
1
node_modules/css-select/lib/esm/general.js.map
generated
vendored
Executable file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"general.js","sourceRoot":"https://raw.githubusercontent.com/fb55/css-select/93caad96c807da1d48f08166ef14cf26916b9364/src/","sources":["general.ts"],"names":[],"mappings":"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"}
|
||||||
12
node_modules/css-select/lib/esm/helpers/cache.d.ts
generated
vendored
Normal file
12
node_modules/css-select/lib/esm/helpers/cache.d.ts
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import type { CompiledQuery, InternalOptions } from "../types.js";
|
||||||
|
/**
|
||||||
|
* Some selectors such as `:contains` and (non-relative) `:has` will only be
|
||||||
|
* able to match elements if their parents match the selector (as they contain
|
||||||
|
* a subset of the elements that the parent contains).
|
||||||
|
*
|
||||||
|
* This function wraps the given `matches` function in a function that caches
|
||||||
|
* the results of the parent elements, so that the `matches` function only
|
||||||
|
* needs to be called once for each subtree.
|
||||||
|
*/
|
||||||
|
export declare function cacheParentResults<Node, ElementNode extends Node>(next: CompiledQuery<ElementNode>, { adapter, cacheResults }: InternalOptions<Node, ElementNode>, matches: (elem: ElementNode) => boolean): CompiledQuery<ElementNode>;
|
||||||
|
//# sourceMappingURL=cache.d.ts.map
|
||||||
1
node_modules/css-select/lib/esm/helpers/cache.d.ts.map
generated
vendored
Normal file
1
node_modules/css-select/lib/esm/helpers/cache.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"cache.d.ts","sourceRoot":"https://raw.githubusercontent.com/fb55/css-select/b3f14ecdb644827d0a7ea8f441abe2063c7ce2e7/src/","sources":["helpers/cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGlE;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EAC7D,IAAI,EAAE,aAAa,CAAC,WAAW,CAAC,EAChC,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,eAAe,CAAC,IAAI,EAAE,WAAW,CAAC,EAC7D,OAAO,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,GACxC,aAAa,CAAC,WAAW,CAAC,CAsC5B"}
|
||||||
41
node_modules/css-select/lib/esm/helpers/cache.js
generated
vendored
Normal file
41
node_modules/css-select/lib/esm/helpers/cache.js
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { getElementParent } from "./querying.js";
|
||||||
|
/**
|
||||||
|
* Some selectors such as `:contains` and (non-relative) `:has` will only be
|
||||||
|
* able to match elements if their parents match the selector (as they contain
|
||||||
|
* a subset of the elements that the parent contains).
|
||||||
|
*
|
||||||
|
* This function wraps the given `matches` function in a function that caches
|
||||||
|
* the results of the parent elements, so that the `matches` function only
|
||||||
|
* needs to be called once for each subtree.
|
||||||
|
*/
|
||||||
|
export function cacheParentResults(next, { adapter, cacheResults }, matches) {
|
||||||
|
if (cacheResults === false || typeof WeakMap === "undefined") {
|
||||||
|
return (elem) => next(elem) && matches(elem);
|
||||||
|
}
|
||||||
|
// Use a cache to avoid re-checking children of an element.
|
||||||
|
// @ts-expect-error `Node` is not extending object
|
||||||
|
const resultCache = new WeakMap();
|
||||||
|
function addResultToCache(elem) {
|
||||||
|
const result = matches(elem);
|
||||||
|
resultCache.set(elem, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return function cachedMatcher(elem) {
|
||||||
|
if (!next(elem))
|
||||||
|
return false;
|
||||||
|
if (resultCache.has(elem)) {
|
||||||
|
return resultCache.get(elem);
|
||||||
|
}
|
||||||
|
// Check all of the element's parents.
|
||||||
|
let node = elem;
|
||||||
|
do {
|
||||||
|
const parent = getElementParent(node, adapter);
|
||||||
|
if (parent === null) {
|
||||||
|
return addResultToCache(elem);
|
||||||
|
}
|
||||||
|
node = parent;
|
||||||
|
} while (!resultCache.has(node));
|
||||||
|
return resultCache.get(node) && addResultToCache(elem);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=cache.js.map
|
||||||
1
node_modules/css-select/lib/esm/helpers/cache.js.map
generated
vendored
Normal file
1
node_modules/css-select/lib/esm/helpers/cache.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"cache.js","sourceRoot":"https://raw.githubusercontent.com/fb55/css-select/b3f14ecdb644827d0a7ea8f441abe2063c7ce2e7/src/","sources":["helpers/cache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAC9B,IAAgC,EAChC,EAAE,OAAO,EAAE,YAAY,EAAsC,EAC7D,OAAuC;IAEvC,IAAI,YAAY,KAAK,KAAK,IAAI,OAAO,OAAO,KAAK,WAAW,EAAE,CAAC;QAC3D,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,CAAC;IAED,2DAA2D;IAE3D,kDAAkD;IAClD,MAAM,WAAW,GAAG,IAAI,OAAO,EAAiB,CAAC;IAEjD,SAAS,gBAAgB,CAAC,IAAiB;QACvC,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAE7B,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC9B,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,OAAO,SAAS,aAAa,CAAC,IAAI;QAC9B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QAC9B,IAAI,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACxB,OAAO,WAAW,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC;QAClC,CAAC;QAED,sCAAsC;QACtC,IAAI,IAAI,GAAG,IAAI,CAAC;QAEhB,GAAG,CAAC;YACA,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAE/C,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;gBAClB,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC;YAClC,CAAC;YAED,IAAI,GAAG,MAAM,CAAC;QAClB,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;QAEjC,OAAO,WAAW,CAAC,GAAG,CAAC,IAAI,CAAE,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAC5D,CAAC,CAAC;AACN,CAAC"}
|
||||||
24
node_modules/css-select/lib/esm/helpers/querying.d.ts
generated
vendored
Normal file
24
node_modules/css-select/lib/esm/helpers/querying.d.ts
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import type { InternalOptions, Predicate, Adapter } from "../types.js";
|
||||||
|
/**
|
||||||
|
* Find all elements matching the query. If not in XML mode, the query will ignore
|
||||||
|
* the contents of `<template>` elements.
|
||||||
|
*
|
||||||
|
* @param query - Function that returns true if the element matches the query.
|
||||||
|
* @param elems - Nodes to query. If a node is an element, its children will be queried.
|
||||||
|
* @param options - Options for querying the document.
|
||||||
|
* @returns All matching elements.
|
||||||
|
*/
|
||||||
|
export declare function findAll<Node, ElementNode extends Node>(query: Predicate<ElementNode>, elems: Node[], options: InternalOptions<Node, ElementNode>): ElementNode[];
|
||||||
|
/**
|
||||||
|
* Find the first element matching the query. If not in XML mode, the query will ignore
|
||||||
|
* the contents of `<template>` elements.
|
||||||
|
*
|
||||||
|
* @param query - Function that returns true if the element matches the query.
|
||||||
|
* @param elems - Nodes to query. If a node is an element, its children will be queried.
|
||||||
|
* @param options - Options for querying the document.
|
||||||
|
* @returns The first matching element, or null if there was no match.
|
||||||
|
*/
|
||||||
|
export declare function findOne<Node, ElementNode extends Node>(query: Predicate<ElementNode>, elems: Node[], options: InternalOptions<Node, ElementNode>): ElementNode | null;
|
||||||
|
export declare function getNextSiblings<Node, ElementNode extends Node>(elem: Node, adapter: Adapter<Node, ElementNode>): ElementNode[];
|
||||||
|
export declare function getElementParent<Node, ElementNode extends Node>(node: ElementNode, adapter: Adapter<Node, ElementNode>): ElementNode | null;
|
||||||
|
//# sourceMappingURL=querying.d.ts.map
|
||||||
1
node_modules/css-select/lib/esm/helpers/querying.d.ts.map
generated
vendored
Normal file
1
node_modules/css-select/lib/esm/helpers/querying.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"querying.d.ts","sourceRoot":"https://raw.githubusercontent.com/fb55/css-select/b3f14ecdb644827d0a7ea8f441abe2063c7ce2e7/src/","sources":["helpers/querying.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEvE;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EAClD,KAAK,EAAE,SAAS,CAAC,WAAW,CAAC,EAC7B,KAAK,EAAE,IAAI,EAAE,EACb,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,WAAW,CAAC,GAC5C,WAAW,EAAE,CAyCf;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EAClD,KAAK,EAAE,SAAS,CAAC,WAAW,CAAC,EAC7B,KAAK,EAAE,IAAI,EAAE,EACb,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,WAAW,CAAC,GAC5C,WAAW,GAAG,IAAI,CAwCpB;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EAC1D,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,GACpC,WAAW,EAAE,CAMf;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EAC3D,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,GACpC,WAAW,GAAG,IAAI,CAGpB"}
|
||||||
105
node_modules/css-select/lib/esm/helpers/querying.js
generated
vendored
Normal file
105
node_modules/css-select/lib/esm/helpers/querying.js
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
/**
|
||||||
|
* Find all elements matching the query. If not in XML mode, the query will ignore
|
||||||
|
* the contents of `<template>` elements.
|
||||||
|
*
|
||||||
|
* @param query - Function that returns true if the element matches the query.
|
||||||
|
* @param elems - Nodes to query. If a node is an element, its children will be queried.
|
||||||
|
* @param options - Options for querying the document.
|
||||||
|
* @returns All matching elements.
|
||||||
|
*/
|
||||||
|
export function findAll(query, elems, options) {
|
||||||
|
const { adapter, xmlMode = false } = options;
|
||||||
|
const result = [];
|
||||||
|
/** Stack of the arrays we are looking at. */
|
||||||
|
const nodeStack = [elems];
|
||||||
|
/** Stack of the indices within the arrays. */
|
||||||
|
const indexStack = [0];
|
||||||
|
for (;;) {
|
||||||
|
// First, check if the current array has any more elements to look at.
|
||||||
|
if (indexStack[0] >= nodeStack[0].length) {
|
||||||
|
// If we have no more arrays to look at, we are done.
|
||||||
|
if (nodeStack.length === 1) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
nodeStack.shift();
|
||||||
|
indexStack.shift();
|
||||||
|
// Loop back to the start to continue with the next array.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const elem = nodeStack[0][indexStack[0]++];
|
||||||
|
if (!adapter.isTag(elem))
|
||||||
|
continue;
|
||||||
|
if (query(elem))
|
||||||
|
result.push(elem);
|
||||||
|
if (xmlMode || adapter.getName(elem) !== "template") {
|
||||||
|
/*
|
||||||
|
* Add the children to the stack. We are depth-first, so this is
|
||||||
|
* the next array we look at.
|
||||||
|
*/
|
||||||
|
const children = adapter.getChildren(elem);
|
||||||
|
if (children.length > 0) {
|
||||||
|
nodeStack.unshift(children);
|
||||||
|
indexStack.unshift(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Find the first element matching the query. If not in XML mode, the query will ignore
|
||||||
|
* the contents of `<template>` elements.
|
||||||
|
*
|
||||||
|
* @param query - Function that returns true if the element matches the query.
|
||||||
|
* @param elems - Nodes to query. If a node is an element, its children will be queried.
|
||||||
|
* @param options - Options for querying the document.
|
||||||
|
* @returns The first matching element, or null if there was no match.
|
||||||
|
*/
|
||||||
|
export function findOne(query, elems, options) {
|
||||||
|
const { adapter, xmlMode = false } = options;
|
||||||
|
/** Stack of the arrays we are looking at. */
|
||||||
|
const nodeStack = [elems];
|
||||||
|
/** Stack of the indices within the arrays. */
|
||||||
|
const indexStack = [0];
|
||||||
|
for (;;) {
|
||||||
|
// First, check if the current array has any more elements to look at.
|
||||||
|
if (indexStack[0] >= nodeStack[0].length) {
|
||||||
|
// If we have no more arrays to look at, we are done.
|
||||||
|
if (nodeStack.length === 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
nodeStack.shift();
|
||||||
|
indexStack.shift();
|
||||||
|
// Loop back to the start to continue with the next array.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const elem = nodeStack[0][indexStack[0]++];
|
||||||
|
if (!adapter.isTag(elem))
|
||||||
|
continue;
|
||||||
|
if (query(elem))
|
||||||
|
return elem;
|
||||||
|
if (xmlMode || adapter.getName(elem) !== "template") {
|
||||||
|
/*
|
||||||
|
* Add the children to the stack. We are depth-first, so this is
|
||||||
|
* the next array we look at.
|
||||||
|
*/
|
||||||
|
const children = adapter.getChildren(elem);
|
||||||
|
if (children.length > 0) {
|
||||||
|
nodeStack.unshift(children);
|
||||||
|
indexStack.unshift(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function getNextSiblings(elem, adapter) {
|
||||||
|
const siblings = adapter.getSiblings(elem);
|
||||||
|
if (siblings.length <= 1)
|
||||||
|
return [];
|
||||||
|
const elemIndex = siblings.indexOf(elem);
|
||||||
|
if (elemIndex < 0 || elemIndex === siblings.length - 1)
|
||||||
|
return [];
|
||||||
|
return siblings.slice(elemIndex + 1).filter(adapter.isTag);
|
||||||
|
}
|
||||||
|
export function getElementParent(node, adapter) {
|
||||||
|
const parent = adapter.getParent(node);
|
||||||
|
return parent != null && adapter.isTag(parent) ? parent : null;
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=querying.js.map
|
||||||
1
node_modules/css-select/lib/esm/helpers/querying.js.map
generated
vendored
Normal file
1
node_modules/css-select/lib/esm/helpers/querying.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"querying.js","sourceRoot":"https://raw.githubusercontent.com/fb55/css-select/b3f14ecdb644827d0a7ea8f441abe2063c7ce2e7/src/","sources":["helpers/querying.ts"],"names":[],"mappings":"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"}
|
||||||
20
node_modules/css-select/lib/esm/helpers/selectors.d.ts
generated
vendored
Normal file
20
node_modules/css-select/lib/esm/helpers/selectors.d.ts
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import type { InternalSelector } from "../types.js";
|
||||||
|
import { type Traversal } from "css-what";
|
||||||
|
export declare function isTraversal(token: InternalSelector): token is Traversal;
|
||||||
|
/**
|
||||||
|
* Sort the parts of the passed selector, as there is potential for
|
||||||
|
* optimization (some types of selectors are faster than others).
|
||||||
|
*
|
||||||
|
* @param arr Selector to sort
|
||||||
|
*/
|
||||||
|
export declare function sortRules(arr: InternalSelector[]): void;
|
||||||
|
/**
|
||||||
|
* Determine the quality of the passed token. The higher the number, the
|
||||||
|
* faster the token is to execute.
|
||||||
|
*
|
||||||
|
* @param token Token to get the quality of.
|
||||||
|
* @returns The token's quality.
|
||||||
|
*/
|
||||||
|
export declare function getQuality(token: InternalSelector): number;
|
||||||
|
export declare function includesScopePseudo(t: InternalSelector): boolean;
|
||||||
|
//# sourceMappingURL=selectors.d.ts.map
|
||||||
1
node_modules/css-select/lib/esm/helpers/selectors.d.ts.map
generated
vendored
Normal file
1
node_modules/css-select/lib/esm/helpers/selectors.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"selectors.d.ts","sourceRoot":"https://raw.githubusercontent.com/fb55/css-select/b3f14ecdb644827d0a7ea8f441abe2063c7ce2e7/src/","sources":["helpers/selectors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAKH,KAAK,SAAS,EACjB,MAAM,UAAU,CAAC;AAElB,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,KAAK,IAAI,SAAS,CAEvE;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,gBAAgB,EAAE,GAAG,IAAI,CAgBvD;AAgCD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAyC1D;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAOhE"}
|
||||||
103
node_modules/css-select/lib/esm/helpers/selectors.js
generated
vendored
Normal file
103
node_modules/css-select/lib/esm/helpers/selectors.js
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import { AttributeAction, SelectorType, isTraversal as isTraversalBase, } from "css-what";
|
||||||
|
export function isTraversal(token) {
|
||||||
|
return token.type === "_flexibleDescendant" || isTraversalBase(token);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Sort the parts of the passed selector, as there is potential for
|
||||||
|
* optimization (some types of selectors are faster than others).
|
||||||
|
*
|
||||||
|
* @param arr Selector to sort
|
||||||
|
*/
|
||||||
|
export function sortRules(arr) {
|
||||||
|
const ratings = arr.map(getQuality);
|
||||||
|
for (let i = 1; i < arr.length; i++) {
|
||||||
|
const procNew = ratings[i];
|
||||||
|
if (procNew < 0)
|
||||||
|
continue;
|
||||||
|
// Use insertion sort to move the token to the correct position.
|
||||||
|
for (let j = i; j > 0 && procNew < ratings[j - 1]; j--) {
|
||||||
|
const token = arr[j];
|
||||||
|
arr[j] = arr[j - 1];
|
||||||
|
arr[j - 1] = token;
|
||||||
|
ratings[j] = ratings[j - 1];
|
||||||
|
ratings[j - 1] = procNew;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getAttributeQuality(token) {
|
||||||
|
switch (token.action) {
|
||||||
|
case AttributeAction.Exists: {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
case AttributeAction.Equals: {
|
||||||
|
// Prefer ID selectors (eg. #ID)
|
||||||
|
return token.name === "id" ? 9 : 8;
|
||||||
|
}
|
||||||
|
case AttributeAction.Not: {
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
case AttributeAction.Start: {
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
case AttributeAction.End: {
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
case AttributeAction.Any: {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
case AttributeAction.Hyphen: {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
case AttributeAction.Element: {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Determine the quality of the passed token. The higher the number, the
|
||||||
|
* faster the token is to execute.
|
||||||
|
*
|
||||||
|
* @param token Token to get the quality of.
|
||||||
|
* @returns The token's quality.
|
||||||
|
*/
|
||||||
|
export function getQuality(token) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
|
||||||
|
switch (token.type) {
|
||||||
|
case SelectorType.Universal: {
|
||||||
|
return 50;
|
||||||
|
}
|
||||||
|
case SelectorType.Tag: {
|
||||||
|
return 30;
|
||||||
|
}
|
||||||
|
case SelectorType.Attribute: {
|
||||||
|
return Math.floor(getAttributeQuality(token) /
|
||||||
|
// `ignoreCase` adds some overhead, half the result if applicable.
|
||||||
|
(token.ignoreCase ? 2 : 1));
|
||||||
|
}
|
||||||
|
case SelectorType.Pseudo: {
|
||||||
|
return !token.data
|
||||||
|
? 3
|
||||||
|
: token.name === "has" ||
|
||||||
|
token.name === "contains" ||
|
||||||
|
token.name === "icontains"
|
||||||
|
? // Expensive in any case — run as late as possible.
|
||||||
|
0
|
||||||
|
: Array.isArray(token.data)
|
||||||
|
? // Eg. `:is`, `:not`
|
||||||
|
Math.max(
|
||||||
|
// If we have traversals, try to avoid executing this selector
|
||||||
|
0, Math.min(...token.data.map((d) => Math.min(...d.map(getQuality)))))
|
||||||
|
: 2;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function includesScopePseudo(t) {
|
||||||
|
return (t.type === SelectorType.Pseudo &&
|
||||||
|
(t.name === "scope" ||
|
||||||
|
(Array.isArray(t.data) &&
|
||||||
|
t.data.some((data) => data.some(includesScopePseudo)))));
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=selectors.js.map
|
||||||
1
node_modules/css-select/lib/esm/helpers/selectors.js.map
generated
vendored
Normal file
1
node_modules/css-select/lib/esm/helpers/selectors.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"selectors.js","sourceRoot":"https://raw.githubusercontent.com/fb55/css-select/b3f14ecdb644827d0a7ea8f441abe2063c7ce2e7/src/","sources":["helpers/selectors.ts"],"names":[],"mappings":"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"}
|
||||||
50
node_modules/css-select/lib/esm/index.d.ts
generated
vendored
Executable file
50
node_modules/css-select/lib/esm/index.d.ts
generated
vendored
Executable file
@@ -0,0 +1,50 @@
|
|||||||
|
import type { CompiledQuery, Options, Query, Adapter } from "./types.js";
|
||||||
|
export type { Options };
|
||||||
|
/**
|
||||||
|
* Compiles the query, returns a function.
|
||||||
|
*/
|
||||||
|
export declare const compile: <Node, ElementNode extends Node>(selector: string | import("css-what").Selector[][], options?: Options<Node, ElementNode> | undefined, context?: Node | Node[] | undefined) => CompiledQuery<Node>;
|
||||||
|
export declare const _compileUnsafe: <Node, ElementNode extends Node>(selector: string | import("css-what").Selector[][], options?: Options<Node, ElementNode> | undefined, context?: Node | Node[] | undefined) => CompiledQuery<ElementNode>;
|
||||||
|
export declare const _compileToken: <Node, ElementNode extends Node>(selector: import("./types.js").InternalSelector[][], options?: Options<Node, ElementNode> | undefined, context?: Node | Node[] | undefined) => CompiledQuery<ElementNode>;
|
||||||
|
export declare function prepareContext<Node, ElementNode extends Node>(elems: Node | Node[], adapter: Adapter<Node, ElementNode>, shouldTestNextSiblings?: boolean): Node[];
|
||||||
|
/**
|
||||||
|
* @template Node The generic Node type for the DOM adapter being used.
|
||||||
|
* @template ElementNode The Node type for elements for the DOM adapter being used.
|
||||||
|
* @param elems Elements to query. If it is an element, its children will be queried..
|
||||||
|
* @param query can be either a CSS selector string or a compiled query function.
|
||||||
|
* @param [options] options for querying the document.
|
||||||
|
* @see compile for supported selector queries.
|
||||||
|
* @returns All matching elements.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export declare const selectAll: <Node, ElementNode extends Node>(query: Query<ElementNode>, elements: Node | Node[], options?: Options<Node, ElementNode> | undefined) => ElementNode[];
|
||||||
|
/**
|
||||||
|
* @template Node The generic Node type for the DOM adapter being used.
|
||||||
|
* @template ElementNode The Node type for elements for the DOM adapter being used.
|
||||||
|
* @param elems Elements to query. If it is an element, its children will be queried..
|
||||||
|
* @param query can be either a CSS selector string or a compiled query function.
|
||||||
|
* @param [options] options for querying the document.
|
||||||
|
* @see compile for supported selector queries.
|
||||||
|
* @returns the first match, or null if there was no match.
|
||||||
|
*/
|
||||||
|
export declare const selectOne: <Node, ElementNode extends Node>(query: Query<ElementNode>, elements: Node | Node[], options?: Options<Node, ElementNode> | undefined) => ElementNode | null;
|
||||||
|
/**
|
||||||
|
* Tests whether or not an element is matched by query.
|
||||||
|
*
|
||||||
|
* @template Node The generic Node type for the DOM adapter being used.
|
||||||
|
* @template ElementNode The Node type for elements for the DOM adapter being used.
|
||||||
|
* @param elem The element to test if it matches the query.
|
||||||
|
* @param query can be either a CSS selector string or a compiled query function.
|
||||||
|
* @param [options] options for querying the document.
|
||||||
|
* @see compile for supported selector queries.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export declare function is<Node, ElementNode extends Node>(elem: ElementNode, query: Query<ElementNode>, options?: Options<Node, ElementNode>): boolean;
|
||||||
|
/**
|
||||||
|
* Alias for selectAll(query, elems, options).
|
||||||
|
* @see [compile] for supported selector queries.
|
||||||
|
*/
|
||||||
|
export default selectAll;
|
||||||
|
/** @deprecated Use the `pseudos` option instead. */
|
||||||
|
export { filters, pseudos, aliases } from "./pseudo-selectors/index.js";
|
||||||
|
//# sourceMappingURL=index.d.ts.map
|
||||||
1
node_modules/css-select/lib/esm/index.d.ts.map
generated
vendored
Executable file
1
node_modules/css-select/lib/esm/index.d.ts.map
generated
vendored
Executable file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"index.d.ts","sourceRoot":"https://raw.githubusercontent.com/fb55/css-select/93caad96c807da1d48f08166ef14cf26916b9364/src/","sources":["index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACR,aAAa,EACb,OAAO,EAEP,KAAK,EACL,OAAO,EAEV,MAAM,YAAY,CAAC;AAGpB,YAAY,EAAE,OAAO,EAAE,CAAC;AA0CxB;;GAEG;AACH,eAAO,MAAM,OAAO,oMAA0B,CAAC;AAC/C,eAAO,MAAM,cAAc,2MAA6B,CAAC;AACzD,eAAO,MAAM,aAAa,4MAA4B,CAAC;AA6BvD,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EACzD,KAAK,EAAE,IAAI,GAAG,IAAI,EAAE,EACpB,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,EACnC,sBAAsB,UAAQ,GAC/B,IAAI,EAAE,CAYR;AAiBD;;;;;;;;;GASG;AACH,eAAO,MAAM,SAAS,yJASrB,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,8JASrB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,EAAE,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EAC7C,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,KAAK,CAAC,WAAW,CAAC,EACzB,OAAO,CAAC,EAAE,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,GACrC,OAAO,CAKT;AAED;;;GAGG;AACH,eAAe,SAAS,CAAC;AAGzB,oDAAoD;AACpD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC"}
|
||||||
115
node_modules/css-select/lib/esm/index.js
generated
vendored
Executable file
115
node_modules/css-select/lib/esm/index.js
generated
vendored
Executable file
@@ -0,0 +1,115 @@
|
|||||||
|
import * as DomUtils from "domutils";
|
||||||
|
import boolbase from "boolbase";
|
||||||
|
import { compile as compileRaw, compileUnsafe, compileToken, } from "./compile.js";
|
||||||
|
import { getNextSiblings } from "./pseudo-selectors/subselects.js";
|
||||||
|
const defaultEquals = (a, b) => a === b;
|
||||||
|
const defaultOptions = {
|
||||||
|
adapter: DomUtils,
|
||||||
|
equals: defaultEquals,
|
||||||
|
};
|
||||||
|
function convertOptionFormats(options) {
|
||||||
|
var _a, _b, _c, _d;
|
||||||
|
/*
|
||||||
|
* We force one format of options to the other one.
|
||||||
|
*/
|
||||||
|
// @ts-expect-error Default options may have incompatible `Node` / `ElementNode`.
|
||||||
|
const opts = options !== null && options !== void 0 ? options : defaultOptions;
|
||||||
|
// @ts-expect-error Same as above.
|
||||||
|
(_a = opts.adapter) !== null && _a !== void 0 ? _a : (opts.adapter = DomUtils);
|
||||||
|
// @ts-expect-error `equals` does not exist on `Options`
|
||||||
|
(_b = opts.equals) !== null && _b !== void 0 ? _b : (opts.equals = (_d = (_c = opts.adapter) === null || _c === void 0 ? void 0 : _c.equals) !== null && _d !== void 0 ? _d : defaultEquals);
|
||||||
|
return opts;
|
||||||
|
}
|
||||||
|
function wrapCompile(func) {
|
||||||
|
return function addAdapter(selector, options, context) {
|
||||||
|
const opts = convertOptionFormats(options);
|
||||||
|
return func(selector, opts, context);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Compiles the query, returns a function.
|
||||||
|
*/
|
||||||
|
export const compile = wrapCompile(compileRaw);
|
||||||
|
export const _compileUnsafe = wrapCompile(compileUnsafe);
|
||||||
|
export const _compileToken = wrapCompile(compileToken);
|
||||||
|
function getSelectorFunc(searchFunc) {
|
||||||
|
return function select(query, elements, options) {
|
||||||
|
const opts = convertOptionFormats(options);
|
||||||
|
if (typeof query !== "function") {
|
||||||
|
query = compileUnsafe(query, opts, elements);
|
||||||
|
}
|
||||||
|
const filteredElements = prepareContext(elements, opts.adapter, query.shouldTestNextSiblings);
|
||||||
|
return searchFunc(query, filteredElements, opts);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function prepareContext(elems, adapter, shouldTestNextSiblings = false) {
|
||||||
|
/*
|
||||||
|
* Add siblings if the query requires them.
|
||||||
|
* See https://github.com/fb55/css-select/pull/43#issuecomment-225414692
|
||||||
|
*/
|
||||||
|
if (shouldTestNextSiblings) {
|
||||||
|
elems = appendNextSiblings(elems, adapter);
|
||||||
|
}
|
||||||
|
return Array.isArray(elems)
|
||||||
|
? adapter.removeSubsets(elems)
|
||||||
|
: adapter.getChildren(elems);
|
||||||
|
}
|
||||||
|
function appendNextSiblings(elem, adapter) {
|
||||||
|
// Order matters because jQuery seems to check the children before the siblings
|
||||||
|
const elems = Array.isArray(elem) ? elem.slice(0) : [elem];
|
||||||
|
const elemsLength = elems.length;
|
||||||
|
for (let i = 0; i < elemsLength; i++) {
|
||||||
|
const nextSiblings = getNextSiblings(elems[i], adapter);
|
||||||
|
elems.push(...nextSiblings);
|
||||||
|
}
|
||||||
|
return elems;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @template Node The generic Node type for the DOM adapter being used.
|
||||||
|
* @template ElementNode The Node type for elements for the DOM adapter being used.
|
||||||
|
* @param elems Elements to query. If it is an element, its children will be queried..
|
||||||
|
* @param query can be either a CSS selector string or a compiled query function.
|
||||||
|
* @param [options] options for querying the document.
|
||||||
|
* @see compile for supported selector queries.
|
||||||
|
* @returns All matching elements.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const selectAll = getSelectorFunc((query, elems, options) => query === boolbase.falseFunc || !elems || elems.length === 0
|
||||||
|
? []
|
||||||
|
: options.adapter.findAll(query, elems));
|
||||||
|
/**
|
||||||
|
* @template Node The generic Node type for the DOM adapter being used.
|
||||||
|
* @template ElementNode The Node type for elements for the DOM adapter being used.
|
||||||
|
* @param elems Elements to query. If it is an element, its children will be queried..
|
||||||
|
* @param query can be either a CSS selector string or a compiled query function.
|
||||||
|
* @param [options] options for querying the document.
|
||||||
|
* @see compile for supported selector queries.
|
||||||
|
* @returns the first match, or null if there was no match.
|
||||||
|
*/
|
||||||
|
export const selectOne = getSelectorFunc((query, elems, options) => query === boolbase.falseFunc || !elems || elems.length === 0
|
||||||
|
? null
|
||||||
|
: options.adapter.findOne(query, elems));
|
||||||
|
/**
|
||||||
|
* Tests whether or not an element is matched by query.
|
||||||
|
*
|
||||||
|
* @template Node The generic Node type for the DOM adapter being used.
|
||||||
|
* @template ElementNode The Node type for elements for the DOM adapter being used.
|
||||||
|
* @param elem The element to test if it matches the query.
|
||||||
|
* @param query can be either a CSS selector string or a compiled query function.
|
||||||
|
* @param [options] options for querying the document.
|
||||||
|
* @see compile for supported selector queries.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function is(elem, query, options) {
|
||||||
|
const opts = convertOptionFormats(options);
|
||||||
|
return (typeof query === "function" ? query : compileRaw(query, opts))(elem);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Alias for selectAll(query, elems, options).
|
||||||
|
* @see [compile] for supported selector queries.
|
||||||
|
*/
|
||||||
|
export default selectAll;
|
||||||
|
// Export filters, pseudos and aliases to allow users to supply their own.
|
||||||
|
/** @deprecated Use the `pseudos` option instead. */
|
||||||
|
export { filters, pseudos, aliases } from "./pseudo-selectors/index.js";
|
||||||
|
//# sourceMappingURL=index.js.map
|
||||||
1
node_modules/css-select/lib/esm/index.js.map
generated
vendored
Executable file
1
node_modules/css-select/lib/esm/index.js.map
generated
vendored
Executable file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"index.js","sourceRoot":"https://raw.githubusercontent.com/fb55/css-select/93caad96c807da1d48f08166ef14cf26916b9364/src/","sources":["index.ts"],"names":[],"mappings":"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"}
|
||||||
1
node_modules/css-select/lib/esm/package.json
generated
vendored
Executable file
1
node_modules/css-select/lib/esm/package.json
generated
vendored
Executable file
@@ -0,0 +1 @@
|
|||||||
|
{"type":"module"}
|
||||||
5
node_modules/css-select/lib/esm/pseudo-selectors/aliases.d.ts
generated
vendored
Executable file
5
node_modules/css-select/lib/esm/pseudo-selectors/aliases.d.ts
generated
vendored
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
/**
|
||||||
|
* Aliases are pseudos that are expressed as selectors.
|
||||||
|
*/
|
||||||
|
export declare const aliases: Record<string, string>;
|
||||||
|
//# sourceMappingURL=aliases.d.ts.map
|
||||||
1
node_modules/css-select/lib/esm/pseudo-selectors/aliases.d.ts.map
generated
vendored
Executable file
1
node_modules/css-select/lib/esm/pseudo-selectors/aliases.d.ts.map
generated
vendored
Executable file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"aliases.d.ts","sourceRoot":"https://raw.githubusercontent.com/fb55/css-select/93caad96c807da1d48f08166ef14cf26916b9364/src/","sources":["pseudo-selectors/aliases.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAwC1C,CAAC"}
|
||||||
35
node_modules/css-select/lib/esm/pseudo-selectors/aliases.js
generated
vendored
Executable file
35
node_modules/css-select/lib/esm/pseudo-selectors/aliases.js
generated
vendored
Executable file
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Aliases are pseudos that are expressed as selectors.
|
||||||
|
*/
|
||||||
|
export const aliases = {
|
||||||
|
// Links
|
||||||
|
"any-link": ":is(a, area, link)[href]",
|
||||||
|
link: ":any-link:not(:visited)",
|
||||||
|
// Forms
|
||||||
|
// https://html.spec.whatwg.org/multipage/scripting.html#disabled-elements
|
||||||
|
disabled: `:is(
|
||||||
|
:is(button, input, select, textarea, optgroup, option)[disabled],
|
||||||
|
optgroup[disabled] > option,
|
||||||
|
fieldset[disabled]:not(fieldset[disabled] legend:first-of-type *)
|
||||||
|
)`,
|
||||||
|
enabled: ":not(:disabled)",
|
||||||
|
checked: ":is(:is(input[type=radio], input[type=checkbox])[checked], option:selected)",
|
||||||
|
required: ":is(input, select, textarea)[required]",
|
||||||
|
optional: ":is(input, select, textarea):not([required])",
|
||||||
|
// JQuery extensions
|
||||||
|
// https://html.spec.whatwg.org/multipage/form-elements.html#concept-option-selectedness
|
||||||
|
selected: "option:is([selected], select:not([multiple]):not(:has(> option[selected])) > :first-of-type)",
|
||||||
|
checkbox: "[type=checkbox]",
|
||||||
|
file: "[type=file]",
|
||||||
|
password: "[type=password]",
|
||||||
|
radio: "[type=radio]",
|
||||||
|
reset: "[type=reset]",
|
||||||
|
image: "[type=image]",
|
||||||
|
submit: "[type=submit]",
|
||||||
|
parent: ":not(:empty)",
|
||||||
|
header: ":is(h1, h2, h3, h4, h5, h6)",
|
||||||
|
button: ":is(button, input[type=button])",
|
||||||
|
input: ":is(input, textarea, select, button)",
|
||||||
|
text: "input:is(:not([type!='']), [type=text])",
|
||||||
|
};
|
||||||
|
//# sourceMappingURL=aliases.js.map
|
||||||
1
node_modules/css-select/lib/esm/pseudo-selectors/aliases.js.map
generated
vendored
Executable file
1
node_modules/css-select/lib/esm/pseudo-selectors/aliases.js.map
generated
vendored
Executable file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"aliases.js","sourceRoot":"https://raw.githubusercontent.com/fb55/css-select/93caad96c807da1d48f08166ef14cf26916b9364/src/","sources":["pseudo-selectors/aliases.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,MAAM,OAAO,GAA2B;IAC3C,QAAQ;IAER,UAAU,EAAE,0BAA0B;IACtC,IAAI,EAAE,yBAAyB;IAE/B,QAAQ;IAER,0EAA0E;IAC1E,QAAQ,EAAE;;;;MAIR;IACF,OAAO,EAAE,iBAAiB;IAC1B,OAAO,EACH,6EAA6E;IACjF,QAAQ,EAAE,wCAAwC;IAClD,QAAQ,EAAE,8CAA8C;IAExD,oBAAoB;IAEpB,wFAAwF;IACxF,QAAQ,EACJ,8FAA8F;IAElG,QAAQ,EAAE,iBAAiB;IAC3B,IAAI,EAAE,aAAa;IACnB,QAAQ,EAAE,iBAAiB;IAC3B,KAAK,EAAE,cAAc;IACrB,KAAK,EAAE,cAAc;IACrB,KAAK,EAAE,cAAc;IACrB,MAAM,EAAE,eAAe;IAEvB,MAAM,EAAE,cAAc;IACtB,MAAM,EAAE,6BAA6B;IAErC,MAAM,EAAE,iCAAiC;IACzC,KAAK,EAAE,sCAAsC;IAC7C,IAAI,EAAE,yCAAyC;CAClD,CAAC"}
|
||||||
4
node_modules/css-select/lib/esm/pseudo-selectors/filters.d.ts
generated
vendored
Executable file
4
node_modules/css-select/lib/esm/pseudo-selectors/filters.d.ts
generated
vendored
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
import type { CompiledQuery, InternalOptions } from "../types.js";
|
||||||
|
export declare type Filter = <Node, ElementNode extends Node>(next: CompiledQuery<ElementNode>, text: string, options: InternalOptions<Node, ElementNode>, context?: Node[]) => CompiledQuery<ElementNode>;
|
||||||
|
export declare const filters: Record<string, Filter>;
|
||||||
|
//# sourceMappingURL=filters.d.ts.map
|
||||||
1
node_modules/css-select/lib/esm/pseudo-selectors/filters.d.ts.map
generated
vendored
Executable file
1
node_modules/css-select/lib/esm/pseudo-selectors/filters.d.ts.map
generated
vendored
Executable file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"filters.d.ts","sourceRoot":"https://raw.githubusercontent.com/fb55/css-select/93caad96c807da1d48f08166ef14cf26916b9364/src/","sources":["pseudo-selectors/filters.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAW,MAAM,aAAa,CAAC;AAE3E,oBAAY,MAAM,GAAG,CAAC,IAAI,EAAE,WAAW,SAAS,IAAI,EAChD,IAAI,EAAE,aAAa,CAAC,WAAW,CAAC,EAChC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,WAAW,CAAC,EAC3C,OAAO,CAAC,EAAE,IAAI,EAAE,KACf,aAAa,CAAC,WAAW,CAAC,CAAC;AAYhC,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CA2I1C,CAAC"}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user