Refactor bin scripts: shared CLI bootstrap, robust module migrate, and runtime locking
This commit is contained in:
42
bin/cli-bootstrap.php
Normal file
42
bin/cli-bootstrap.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use MintyPHP\App\AppContainer;
|
||||||
|
use MintyPHP\Http\RequestContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared app bootstrap for non-module CLI commands.
|
||||||
|
*/
|
||||||
|
function cliBootstrapApp(string $channel = 'cli', string $path = 'bin/cli'): AppContainer
|
||||||
|
{
|
||||||
|
static $booted = false;
|
||||||
|
/** @var AppContainer|null $container */
|
||||||
|
static $container = null;
|
||||||
|
|
||||||
|
if (!$booted) {
|
||||||
|
chdir(__DIR__ . '/..');
|
||||||
|
require_once 'vendor/autoload.php';
|
||||||
|
require_once 'config/config.php';
|
||||||
|
require_once 'lib/Support/helpers.php';
|
||||||
|
|
||||||
|
/** @var AppContainer $resolved */
|
||||||
|
$resolved = require 'lib/App/registerContainer.php';
|
||||||
|
setAppContainer($resolved);
|
||||||
|
|
||||||
|
$container = $resolved;
|
||||||
|
$booted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestContext::start([
|
||||||
|
'channel' => $channel,
|
||||||
|
'method' => 'CLI',
|
||||||
|
'path' => $path,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$container instanceof AppContainer) {
|
||||||
|
throw new RuntimeException('CLI app container bootstrap failed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $container;
|
||||||
|
}
|
||||||
@@ -6,30 +6,19 @@ declare(strict_types=1);
|
|||||||
use MintyPHP\App\AppContainer;
|
use MintyPHP\App\AppContainer;
|
||||||
use MintyPHP\App\Bootstrap\EnvValidator;
|
use MintyPHP\App\Bootstrap\EnvValidator;
|
||||||
use MintyPHP\DB;
|
use MintyPHP\DB;
|
||||||
use MintyPHP\Http\RequestContext;
|
|
||||||
use MintyPHP\Service\Access\AuthorizationService;
|
use MintyPHP\Service\Access\AuthorizationService;
|
||||||
use MintyPHP\Service\Access\PermissionService;
|
use MintyPHP\Service\Access\PermissionService;
|
||||||
use MintyPHP\Service\Access\UiAccessService;
|
use MintyPHP\Service\Access\UiAccessService;
|
||||||
use MintyPHP\Service\Audit\SystemAuditService;
|
use MintyPHP\Service\Audit\SystemAuditService;
|
||||||
use MintyPHP\Service\Auth\AuthService;
|
use MintyPHP\Service\Auth\AuthService;
|
||||||
|
|
||||||
chdir(__DIR__ . '/..');
|
|
||||||
|
|
||||||
error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED);
|
error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED);
|
||||||
ini_set('display_errors', '0');
|
ini_set('display_errors', '0');
|
||||||
|
|
||||||
require 'vendor/autoload.php';
|
require_once __DIR__ . '/cli-bootstrap.php';
|
||||||
require 'config/config.php';
|
|
||||||
require 'lib/Support/helpers.php';
|
|
||||||
|
|
||||||
/** @var AppContainer $container */
|
/** @var AppContainer $container */
|
||||||
$container = require 'lib/App/registerContainer.php';
|
$container = cliBootstrapApp('cli', 'bin/doctor.php');
|
||||||
setAppContainer($container);
|
|
||||||
RequestContext::start([
|
|
||||||
'channel' => 'cli',
|
|
||||||
'method' => 'CLI',
|
|
||||||
'path' => 'bin/doctor.php',
|
|
||||||
]);
|
|
||||||
$startedAt = microtime(true);
|
$startedAt = microtime(true);
|
||||||
|
|
||||||
/** @var list<array{status: string, name: string, message: string}> $results */
|
/** @var list<array{status: string, name: string, message: string}> $results */
|
||||||
|
|||||||
@@ -24,55 +24,35 @@ require_once __DIR__ . '/module-cli-bootstrap.php';
|
|||||||
|
|
||||||
function moduleAssetsSyncRun(): int
|
function moduleAssetsSyncRun(): int
|
||||||
{
|
{
|
||||||
$warningsBefore = moduleCliVendorWarningsIgnoredTotal();
|
return moduleCliRunStep('module-assets-sync', static function (): int {
|
||||||
moduleCliBootstrapApp();
|
$runtimeModulesDir = __DIR__ . '/../web/modules';
|
||||||
|
$lockFile = __DIR__ . '/../storage/runtime/.module-assets-sync.lock';
|
||||||
$exitCode = 0;
|
$mode = (string) getenv('APP_MODULE_ASSET_MODE');
|
||||||
$runtimeModulesDir = __DIR__ . '/../web/modules';
|
if (trim($mode) === '') {
|
||||||
$lockFile = __DIR__ . '/../storage/runtime/.module-assets-sync.lock';
|
$mode = 'symlink';
|
||||||
$mode = (string) getenv('APP_MODULE_ASSET_MODE');
|
|
||||||
if (trim($mode) === '') {
|
|
||||||
$mode = 'symlink';
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$lockDir = dirname($lockFile);
|
|
||||||
if (!is_dir($lockDir) && !mkdir($lockDir, 0775, true) && !is_dir($lockDir)) {
|
|
||||||
throw new RuntimeException("Failed to create lock directory: {$lockDir}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$lock = fopen($lockFile, 'c');
|
return moduleCliWithFileLock(
|
||||||
if ($lock === false || !flock($lock, LOCK_EX | LOCK_NB)) {
|
$lockFile,
|
||||||
fwrite(STDERR, "module-assets-sync: another sync is in progress, skipping.\n");
|
'module-assets-sync: another sync is in progress, skipping.',
|
||||||
return 0;
|
static function () use ($runtimeModulesDir, $mode): int {
|
||||||
}
|
$registry = app(ModuleRegistry::class);
|
||||||
|
$publisher = new ModuleRuntimeAssetPublisher();
|
||||||
|
$result = $publisher->publish($runtimeModulesDir, $registry->getModules(), $mode);
|
||||||
|
|
||||||
$registry = app(ModuleRegistry::class);
|
fwrite(STDOUT, sprintf(
|
||||||
$publisher = new ModuleRuntimeAssetPublisher();
|
"module-assets-sync: mode=%s created=%d updated=%d removed=%d unchanged=%d\n",
|
||||||
$result = $publisher->publish($runtimeModulesDir, $registry->getModules(), $mode);
|
$mode,
|
||||||
|
$result['created'],
|
||||||
|
$result['updated'],
|
||||||
|
$result['removed'],
|
||||||
|
$result['unchanged']
|
||||||
|
));
|
||||||
|
|
||||||
fwrite(STDOUT, sprintf(
|
return 0;
|
||||||
"module-assets-sync: mode=%s created=%d updated=%d removed=%d unchanged=%d\n",
|
}
|
||||||
$mode,
|
);
|
||||||
$result['created'],
|
});
|
||||||
$result['updated'],
|
|
||||||
$result['removed'],
|
|
||||||
$result['unchanged']
|
|
||||||
));
|
|
||||||
} catch (Throwable $exception) {
|
|
||||||
fwrite(STDERR, sprintf("module-assets-sync: FAILED: %s\n", $exception->getMessage()));
|
|
||||||
$exitCode = 1;
|
|
||||||
} finally {
|
|
||||||
if (isset($lock) && is_resource($lock)) {
|
|
||||||
flock($lock, LOCK_UN);
|
|
||||||
fclose($lock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$vendorWarningsIgnored = moduleCliVendorWarningsIgnoredSince($warningsBefore);
|
|
||||||
fwrite(STDOUT, sprintf("module-assets-sync: vendor_warnings_ignored=%d\n", $vendorWarningsIgnored));
|
|
||||||
|
|
||||||
return $exitCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PHP_SAPI === 'cli' && realpath((string) ($_SERVER['SCRIPT_FILENAME'] ?? '')) === __FILE__) {
|
if (PHP_SAPI === 'cli' && realpath((string) ($_SERVER['SCRIPT_FILENAME'] ?? '')) === __FILE__) {
|
||||||
|
|||||||
@@ -25,57 +25,41 @@ require_once __DIR__ . '/module-cli-bootstrap.php';
|
|||||||
|
|
||||||
function moduleBuildRun(): int
|
function moduleBuildRun(): int
|
||||||
{
|
{
|
||||||
$warningsBefore = moduleCliVendorWarningsIgnoredTotal();
|
return moduleCliRunStep('module-build', static function (): int {
|
||||||
moduleCliBootstrapApp();
|
$runtimeDir = __DIR__ . '/../storage/runtime/pages';
|
||||||
|
$lockFile = __DIR__ . '/../storage/runtime/.module-build.lock';
|
||||||
|
|
||||||
$exitCode = 0;
|
|
||||||
$runtimeDir = __DIR__ . '/../storage/runtime/pages';
|
|
||||||
$lockFile = __DIR__ . '/../storage/runtime/.module-build.lock';
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Ensure runtime directory exists
|
// Ensure runtime directory exists
|
||||||
$runtimeParent = dirname($runtimeDir);
|
$runtimeParent = dirname($runtimeDir);
|
||||||
if (!is_dir($runtimeParent) && !mkdir($runtimeParent, 0775, true) && !is_dir($runtimeParent)) {
|
if (!is_dir($runtimeParent) && !mkdir($runtimeParent, 0775, true) && !is_dir($runtimeParent)) {
|
||||||
throw new \RuntimeException("Failed to create runtime directory: {$runtimeParent}");
|
throw new \RuntimeException("Failed to create runtime directory: {$runtimeParent}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock to prevent concurrent builds
|
return moduleCliWithFileLock(
|
||||||
$lock = fopen($lockFile, 'c');
|
$lockFile,
|
||||||
if ($lock === false || !flock($lock, LOCK_EX | LOCK_NB)) {
|
'module-build: another build is in progress, skipping.',
|
||||||
fwrite(STDERR, "module-build: another build is in progress, skipping.\n");
|
static function () use ($runtimeDir): int {
|
||||||
return 0;
|
$registry = app(ModuleRegistry::class);
|
||||||
}
|
$modules = $registry->getModules();
|
||||||
|
$builder = new ModuleRuntimePageBuilder();
|
||||||
|
$result = $builder->build($runtimeDir, __DIR__ . '/../pages', $modules);
|
||||||
|
|
||||||
$registry = app(ModuleRegistry::class);
|
ModuleRuntimePageBuilder::writeFingerprint($runtimeDir, $modules);
|
||||||
$modules = $registry->getModules();
|
|
||||||
$builder = new ModuleRuntimePageBuilder();
|
|
||||||
$result = $builder->build($runtimeDir, __DIR__ . '/../pages', $modules);
|
|
||||||
|
|
||||||
ModuleRuntimePageBuilder::writeFingerprint($runtimeDir, $modules);
|
if (count($modules) === 0) {
|
||||||
|
fwrite(STDOUT, "module-build: no modules, runtime -> core pages (symlink).\n");
|
||||||
|
} else {
|
||||||
|
fwrite(STDOUT, sprintf(
|
||||||
|
"module-build: runtime pages built - %d core entries + %d module entries.\n",
|
||||||
|
(int) ($result['core_entries'] ?? 0),
|
||||||
|
(int) ($result['module_entries'] ?? 0)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
if (count($modules) === 0) {
|
return 0;
|
||||||
fwrite(STDOUT, "module-build: no modules, runtime → core pages (symlink).\n");
|
}
|
||||||
} else {
|
);
|
||||||
fwrite(STDOUT, sprintf(
|
});
|
||||||
"module-build: runtime pages built — %d core entries + %d module entries.\n",
|
|
||||||
(int) ($result['core_entries'] ?? 0),
|
|
||||||
(int) ($result['module_entries'] ?? 0)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} catch (Throwable $exception) {
|
|
||||||
fwrite(STDERR, sprintf("module-build: FAILED: %s\n", $exception->getMessage()));
|
|
||||||
$exitCode = 1;
|
|
||||||
} finally {
|
|
||||||
if (isset($lock) && is_resource($lock)) {
|
|
||||||
flock($lock, LOCK_UN);
|
|
||||||
fclose($lock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$vendorWarningsIgnored = moduleCliVendorWarningsIgnoredSince($warningsBefore);
|
|
||||||
fwrite(STDOUT, sprintf("module-build: vendor_warnings_ignored=%d\n", $vendorWarningsIgnored));
|
|
||||||
|
|
||||||
return $exitCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PHP_SAPI === 'cli' && realpath((string) ($_SERVER['SCRIPT_FILENAME'] ?? '')) === __FILE__) {
|
if (PHP_SAPI === 'cli' && realpath((string) ($_SERVER['SCRIPT_FILENAME'] ?? '')) === __FILE__) {
|
||||||
|
|||||||
@@ -114,3 +114,53 @@ function moduleCliBootstrapApp(): void
|
|||||||
|
|
||||||
$booted = true;
|
$booted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs one module CLI step with shared bootstrap, failure handling and vendor warning summary.
|
||||||
|
*
|
||||||
|
* @param callable():int $runner
|
||||||
|
*/
|
||||||
|
function moduleCliRunStep(string $stepName, callable $runner): int
|
||||||
|
{
|
||||||
|
$warningsBefore = moduleCliVendorWarningsIgnoredTotal();
|
||||||
|
moduleCliBootstrapApp();
|
||||||
|
|
||||||
|
$exitCode = 0;
|
||||||
|
try {
|
||||||
|
$exitCode = $runner();
|
||||||
|
} catch (Throwable $exception) {
|
||||||
|
fwrite(STDERR, sprintf("%s: FAILED: %s\n", $stepName, $exception->getMessage()));
|
||||||
|
$exitCode = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$vendorWarningsIgnored = moduleCliVendorWarningsIgnoredSince($warningsBefore);
|
||||||
|
fwrite(STDOUT, sprintf("%s: vendor_warnings_ignored=%d\n", $stepName, $vendorWarningsIgnored));
|
||||||
|
|
||||||
|
return $exitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a callable under a non-blocking file lock.
|
||||||
|
*
|
||||||
|
* @param callable():int $runner
|
||||||
|
*/
|
||||||
|
function moduleCliWithFileLock(string $lockFile, string $busyMessage, callable $runner): int
|
||||||
|
{
|
||||||
|
$lockDir = dirname($lockFile);
|
||||||
|
if (!is_dir($lockDir) && !mkdir($lockDir, 0775, true) && !is_dir($lockDir)) {
|
||||||
|
throw new RuntimeException("Failed to create lock directory: {$lockDir}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$lock = fopen($lockFile, 'c');
|
||||||
|
if ($lock === false || !flock($lock, LOCK_EX | LOCK_NB)) {
|
||||||
|
fwrite(STDERR, $busyMessage . PHP_EOL);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return $runner();
|
||||||
|
} finally {
|
||||||
|
flock($lock, LOCK_UN);
|
||||||
|
fclose($lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,111 +24,225 @@ require_once __DIR__ . '/module-cli-bootstrap.php';
|
|||||||
|
|
||||||
function moduleMigrateRun(): int
|
function moduleMigrateRun(): int
|
||||||
{
|
{
|
||||||
$warningsBefore = moduleCliVendorWarningsIgnoredTotal();
|
return moduleCliRunStep('module-migrate', static function (): int {
|
||||||
moduleCliBootstrapApp();
|
$lockFile = __DIR__ . '/../storage/runtime/.module-migrate.lock';
|
||||||
|
|
||||||
$exitCode = 0;
|
return moduleCliWithFileLock(
|
||||||
$totalApplied = 0;
|
$lockFile,
|
||||||
|
'module-migrate: another migration run is in progress, skipping.',
|
||||||
|
static function (): int {
|
||||||
|
$totalApplied = 0;
|
||||||
|
$registry = app(ModuleRegistry::class);
|
||||||
|
$modules = $registry->getModules();
|
||||||
|
$hasModules = count($modules) > 0;
|
||||||
|
|
||||||
try {
|
if (!$hasModules) {
|
||||||
$registry = app(ModuleRegistry::class);
|
fwrite(STDOUT, "module-migrate: no modules enabled, nothing to do.\n");
|
||||||
$modules = $registry->getModules();
|
return 0;
|
||||||
$hasModules = count($modules) > 0;
|
|
||||||
|
|
||||||
if (!$hasModules) {
|
|
||||||
fwrite(STDOUT, "module-migrate: no modules enabled, nothing to do.\n");
|
|
||||||
} else {
|
|
||||||
// Ensure tracking table exists (safe if already created by db/updates/ script)
|
|
||||||
DB::query(
|
|
||||||
'CREATE TABLE IF NOT EXISTS `module_migrations` (
|
|
||||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
||||||
`module_id` VARCHAR(64) NOT NULL,
|
|
||||||
`filename` VARCHAR(255) NOT NULL,
|
|
||||||
`applied_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `uq_module_migration` (`module_id`, `filename`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($modules as $manifest) {
|
|
||||||
$migrationsPath = $manifest->migrationsPath;
|
|
||||||
if ($migrationsPath === null || !is_dir($migrationsPath)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Discover SQL files
|
|
||||||
$files = glob($migrationsPath . '/*.sql');
|
|
||||||
if ($files === false || count($files) === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
sort($files); // alphabetical order
|
|
||||||
|
|
||||||
// Get already-applied filenames
|
|
||||||
$applied = [];
|
|
||||||
$rows = DB::select('SELECT filename FROM module_migrations WHERE module_id = ?', [$manifest->id]);
|
|
||||||
foreach ($rows as $row) {
|
|
||||||
$applied[$row['filename']] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($files as $file) {
|
|
||||||
$filename = basename($file);
|
|
||||||
if (isset($applied[$filename])) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql = file_get_contents($file);
|
// Ensure tracking table exists (safe if already created by db/updates/ script)
|
||||||
if ($sql === false || trim($sql) === '') {
|
DB::query(
|
||||||
fwrite(STDERR, sprintf("module-migrate: WARNING skipping empty file %s/%s\n", $manifest->id, $filename));
|
'CREATE TABLE IF NOT EXISTS `module_migrations` (
|
||||||
continue;
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
}
|
`module_id` VARCHAR(64) NOT NULL,
|
||||||
|
`filename` VARCHAR(255) NOT NULL,
|
||||||
|
`applied_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uq_module_migration` (`module_id`, `filename`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci'
|
||||||
|
);
|
||||||
|
|
||||||
fwrite(STDOUT, sprintf("module-migrate: applying %s/%s ... ", $manifest->id, $filename));
|
foreach ($modules as $manifest) {
|
||||||
|
$migrationsPath = $manifest->migrationsPath;
|
||||||
// Execute migration inside a transaction so partial failures don't leave the DB inconsistent
|
if ($migrationsPath === null || !is_dir($migrationsPath)) {
|
||||||
DB::query('START TRANSACTION');
|
continue;
|
||||||
try {
|
|
||||||
$statements = array_filter(array_map('trim', explode(';', $sql)), static fn (string $s): bool => $s !== '');
|
|
||||||
foreach ($statements as $statement) {
|
|
||||||
DB::query($statement);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record as applied
|
// Discover SQL files
|
||||||
DB::query(
|
$files = glob($migrationsPath . '/*.sql');
|
||||||
'INSERT INTO module_migrations (module_id, filename) VALUES (?, ?)',
|
if ($files === false || count($files) === 0) {
|
||||||
[$manifest->id, $filename]
|
continue;
|
||||||
);
|
}
|
||||||
DB::query('COMMIT');
|
sort($files); // alphabetical order
|
||||||
} catch (\Throwable $migrationError) {
|
|
||||||
DB::query('ROLLBACK');
|
// Get already-applied filenames
|
||||||
throw new \RuntimeException(
|
$applied = [];
|
||||||
sprintf("Migration %s/%s failed: %s", $manifest->id, $filename, $migrationError->getMessage()),
|
$rows = DB::select('SELECT filename FROM module_migrations WHERE module_id = ?', (string) $manifest->id);
|
||||||
0,
|
foreach ((array) $rows as $row) {
|
||||||
$migrationError
|
if (!is_array($row)) {
|
||||||
);
|
continue;
|
||||||
|
}
|
||||||
|
$filename = trim((string) (($row['module_migrations']['filename'] ?? $row['filename'] ?? '')));
|
||||||
|
if ($filename !== '') {
|
||||||
|
$applied[$filename] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$filename = basename($file);
|
||||||
|
if (isset($applied[$filename])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = file_get_contents($file);
|
||||||
|
if ($sql === false || trim($sql) === '') {
|
||||||
|
fwrite(STDERR, sprintf("module-migrate: WARNING skipping empty file %s/%s\n", $manifest->id, $filename));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite(STDOUT, sprintf("module-migrate: applying %s/%s ... ", $manifest->id, $filename));
|
||||||
|
|
||||||
|
// Execute migration inside a transaction so partial failures don't leave the DB inconsistent.
|
||||||
|
DB::query('START TRANSACTION');
|
||||||
|
try {
|
||||||
|
$statements = moduleMigrateSplitSqlStatements($sql);
|
||||||
|
foreach ($statements as $statement) {
|
||||||
|
DB::query($statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record as applied
|
||||||
|
DB::query(
|
||||||
|
'INSERT INTO module_migrations (module_id, filename) VALUES (?, ?)',
|
||||||
|
(string) $manifest->id,
|
||||||
|
$filename
|
||||||
|
);
|
||||||
|
DB::query('COMMIT');
|
||||||
|
} catch (\Throwable $migrationError) {
|
||||||
|
DB::query('ROLLBACK');
|
||||||
|
throw new \RuntimeException(
|
||||||
|
sprintf("Migration %s/%s failed: %s", $manifest->id, $filename, $migrationError->getMessage()),
|
||||||
|
0,
|
||||||
|
$migrationError
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite(STDOUT, "OK\n");
|
||||||
|
$totalApplied++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fwrite(STDOUT, "OK\n");
|
if ($totalApplied === 0) {
|
||||||
$totalApplied++;
|
fwrite(STDOUT, "module-migrate: all modules up to date, 0 new migrations.\n");
|
||||||
|
} else {
|
||||||
|
fwrite(STDOUT, sprintf("module-migrate: applied %d migration(s).\n", $totalApplied));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<string>
|
||||||
|
*/
|
||||||
|
function moduleMigrateSplitSqlStatements(string $sql): array
|
||||||
|
{
|
||||||
|
if (preg_match('/^\s*DELIMITER\s+/mi', $sql) === 1) {
|
||||||
|
throw new RuntimeException('DELIMITER directives are not supported in module migration SQL files.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$statements = [];
|
||||||
|
$buffer = '';
|
||||||
|
$length = strlen($sql);
|
||||||
|
$inSingleQuote = false;
|
||||||
|
$inDoubleQuote = false;
|
||||||
|
$inBacktick = false;
|
||||||
|
$inLineComment = false;
|
||||||
|
$inBlockComment = false;
|
||||||
|
$escapeNext = false;
|
||||||
|
|
||||||
|
for ($i = 0; $i < $length; $i++) {
|
||||||
|
$char = $sql[$i];
|
||||||
|
$next = $i + 1 < $length ? $sql[$i + 1] : '';
|
||||||
|
|
||||||
|
if ($inLineComment) {
|
||||||
|
$buffer .= $char;
|
||||||
|
if ($char === "\n") {
|
||||||
|
$inLineComment = false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($inBlockComment) {
|
||||||
|
$buffer .= $char;
|
||||||
|
if ($char === '*' && $next === '/') {
|
||||||
|
$buffer .= $next;
|
||||||
|
$i++;
|
||||||
|
$inBlockComment = false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$inSingleQuote && !$inDoubleQuote && !$inBacktick) {
|
||||||
|
if ($char === '-' && $next === '-' && ($i + 2 >= $length || ctype_space($sql[$i + 2]))) {
|
||||||
|
$buffer .= $char . $next;
|
||||||
|
$i++;
|
||||||
|
$inLineComment = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($char === '#') {
|
||||||
|
$buffer .= $char;
|
||||||
|
$inLineComment = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($char === '/' && $next === '*') {
|
||||||
|
$buffer .= $char . $next;
|
||||||
|
$i++;
|
||||||
|
$inBlockComment = true;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$hasModules) {
|
if ($escapeNext) {
|
||||||
// Already reported above.
|
$buffer .= $char;
|
||||||
} elseif ($totalApplied === 0) {
|
$escapeNext = false;
|
||||||
fwrite(STDOUT, "module-migrate: all modules up to date, 0 new migrations.\n");
|
continue;
|
||||||
} else {
|
|
||||||
fwrite(STDOUT, sprintf("module-migrate: applied %d migration(s).\n", $totalApplied));
|
|
||||||
}
|
}
|
||||||
} catch (Throwable $exception) {
|
|
||||||
fwrite(STDERR, sprintf("module-migrate: FAILED: %s\n", $exception->getMessage()));
|
if (($inSingleQuote || $inDoubleQuote) && $char === '\\') {
|
||||||
$exitCode = 1;
|
$buffer .= $char;
|
||||||
|
$escapeNext = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$inDoubleQuote && !$inBacktick && $char === '\'') {
|
||||||
|
$inSingleQuote = !$inSingleQuote;
|
||||||
|
$buffer .= $char;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$inSingleQuote && !$inBacktick && $char === '"') {
|
||||||
|
$inDoubleQuote = !$inDoubleQuote;
|
||||||
|
$buffer .= $char;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$inSingleQuote && !$inDoubleQuote && $char === '`') {
|
||||||
|
$inBacktick = !$inBacktick;
|
||||||
|
$buffer .= $char;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$inSingleQuote && !$inDoubleQuote && !$inBacktick && $char === ';') {
|
||||||
|
$statement = trim($buffer);
|
||||||
|
if ($statement !== '') {
|
||||||
|
$statements[] = $statement;
|
||||||
|
}
|
||||||
|
$buffer = '';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$buffer .= $char;
|
||||||
}
|
}
|
||||||
|
|
||||||
$vendorWarningsIgnored = moduleCliVendorWarningsIgnoredSince($warningsBefore);
|
$tail = trim($buffer);
|
||||||
fwrite(STDOUT, sprintf("module-migrate: vendor_warnings_ignored=%d\n", $vendorWarningsIgnored));
|
if ($tail !== '') {
|
||||||
|
$statements[] = $tail;
|
||||||
|
}
|
||||||
|
|
||||||
return $exitCode;
|
return $statements;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PHP_SAPI === 'cli' && realpath((string) ($_SERVER['SCRIPT_FILENAME'] ?? '')) === __FILE__) {
|
if (PHP_SAPI === 'cli' && realpath((string) ($_SERVER['SCRIPT_FILENAME'] ?? '')) === __FILE__) {
|
||||||
|
|||||||
@@ -21,33 +21,31 @@ require_once __DIR__ . '/module-cli-bootstrap.php';
|
|||||||
|
|
||||||
function modulePermissionsSyncRun(): int
|
function modulePermissionsSyncRun(): int
|
||||||
{
|
{
|
||||||
$warningsBefore = moduleCliVendorWarningsIgnoredTotal();
|
return moduleCliRunStep('module-permissions-sync', static function (): int {
|
||||||
moduleCliBootstrapApp();
|
$lockFile = __DIR__ . '/../storage/runtime/.module-permissions-sync.lock';
|
||||||
|
|
||||||
$exitCode = 0;
|
return moduleCliWithFileLock(
|
||||||
try {
|
$lockFile,
|
||||||
$synchronizer = new ModulePermissionSynchronizer(
|
'module-permissions-sync: another sync is in progress, skipping.',
|
||||||
app(ModuleRegistry::class),
|
static function (): int {
|
||||||
app(PermissionRepository::class)
|
$synchronizer = new ModulePermissionSynchronizer(
|
||||||
|
app(ModuleRegistry::class),
|
||||||
|
app(PermissionRepository::class)
|
||||||
|
);
|
||||||
|
$result = $synchronizer->sync();
|
||||||
|
|
||||||
|
fwrite(STDOUT, sprintf(
|
||||||
|
"module-permissions-sync: created=%d updated=%d unchanged=%d total=%d\n",
|
||||||
|
$result['created'],
|
||||||
|
$result['updated'],
|
||||||
|
$result['unchanged'],
|
||||||
|
$result['total']
|
||||||
|
));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
);
|
);
|
||||||
$result = $synchronizer->sync();
|
});
|
||||||
|
|
||||||
fwrite(STDOUT, sprintf(
|
|
||||||
"module-permissions-sync: created=%d updated=%d unchanged=%d total=%d\n",
|
|
||||||
$result['created'],
|
|
||||||
$result['updated'],
|
|
||||||
$result['unchanged'],
|
|
||||||
$result['total']
|
|
||||||
));
|
|
||||||
} catch (Throwable $exception) {
|
|
||||||
fwrite(STDERR, sprintf("module-permissions-sync: FAILED: %s\n", $exception->getMessage()));
|
|
||||||
$exitCode = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
$vendorWarningsIgnored = moduleCliVendorWarningsIgnoredSince($warningsBefore);
|
|
||||||
fwrite(STDOUT, sprintf("module-permissions-sync: vendor_warnings_ignored=%d\n", $vendorWarningsIgnored));
|
|
||||||
|
|
||||||
return $exitCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PHP_SAPI === 'cli' && realpath((string) ($_SERVER['SCRIPT_FILENAME'] ?? '')) === __FILE__) {
|
if (PHP_SAPI === 'cli' && realpath((string) ($_SERVER['SCRIPT_FILENAME'] ?? '')) === __FILE__) {
|
||||||
|
|||||||
@@ -12,54 +12,63 @@ require_once __DIR__ . '/module-assets-sync.php';
|
|||||||
|
|
||||||
function moduleRuntimeSyncRun(): int
|
function moduleRuntimeSyncRun(): int
|
||||||
{
|
{
|
||||||
moduleCliBootstrapApp();
|
return moduleCliRunStep('module-runtime-sync', static function (): int {
|
||||||
$warningsBeforeAll = moduleCliVendorWarningsIgnoredTotal();
|
$lockFile = __DIR__ . '/../storage/runtime/.module-runtime-sync.lock';
|
||||||
|
return moduleCliWithFileLock(
|
||||||
|
$lockFile,
|
||||||
|
'module-runtime-sync: another runtime sync is in progress, skipping.',
|
||||||
|
static function (): int {
|
||||||
|
moduleCliBootstrapApp();
|
||||||
|
$warningsBeforeAll = moduleCliVendorWarningsIgnoredTotal();
|
||||||
|
|
||||||
$steps = [
|
$steps = [
|
||||||
'migrate' => static fn (): int => moduleMigrateRun(),
|
'migrate' => static fn (): int => moduleMigrateRun(),
|
||||||
'permissions-sync' => static fn (): int => modulePermissionsSyncRun(),
|
'permissions-sync' => static fn (): int => modulePermissionsSyncRun(),
|
||||||
'build' => static fn (): int => moduleBuildRun(),
|
'build' => static fn (): int => moduleBuildRun(),
|
||||||
'assets-sync' => static fn (): int => moduleAssetsSyncRun(),
|
'assets-sync' => static fn (): int => moduleAssetsSyncRun(),
|
||||||
];
|
];
|
||||||
|
|
||||||
$statusByStep = [];
|
$statusByStep = [];
|
||||||
foreach (array_keys($steps) as $stepName) {
|
foreach (array_keys($steps) as $stepName) {
|
||||||
$statusByStep[$stepName] = 'pending';
|
$statusByStep[$stepName] = 'pending';
|
||||||
}
|
}
|
||||||
|
|
||||||
$finalExitCode = 0;
|
$finalExitCode = 0;
|
||||||
foreach ($steps as $stepName => $runner) {
|
foreach ($steps as $stepName => $runner) {
|
||||||
$stepWarningsBefore = moduleCliVendorWarningsIgnoredTotal();
|
$stepWarningsBefore = moduleCliVendorWarningsIgnoredTotal();
|
||||||
$stepExitCode = $runner();
|
$stepExitCode = $runner();
|
||||||
$stepWarnings = moduleCliVendorWarningsIgnoredSince($stepWarningsBefore);
|
$stepWarnings = moduleCliVendorWarningsIgnoredSince($stepWarningsBefore);
|
||||||
$stepStatus = $stepExitCode === 0 ? 'ok' : 'failed';
|
$stepStatus = $stepExitCode === 0 ? 'ok' : 'failed';
|
||||||
$statusByStep[$stepName] = $stepStatus;
|
$statusByStep[$stepName] = $stepStatus;
|
||||||
|
|
||||||
fwrite(STDOUT, sprintf(
|
fwrite(STDOUT, sprintf(
|
||||||
"module-runtime-sync: step=%s status=%s exit_code=%d vendor_warnings_ignored=%d\n",
|
"module-runtime-sync: step=%s status=%s exit_code=%d vendor_warnings_ignored=%d\n",
|
||||||
$stepName,
|
$stepName,
|
||||||
$stepStatus,
|
$stepStatus,
|
||||||
$stepExitCode,
|
$stepExitCode,
|
||||||
$stepWarnings
|
$stepWarnings
|
||||||
));
|
));
|
||||||
|
|
||||||
if ($stepExitCode !== 0) {
|
if ($stepExitCode !== 0) {
|
||||||
$finalExitCode = $stepExitCode;
|
$finalExitCode = $stepExitCode;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$totalVendorWarnings = moduleCliVendorWarningsIgnoredSince($warningsBeforeAll);
|
$totalVendorWarnings = moduleCliVendorWarningsIgnoredSince($warningsBeforeAll);
|
||||||
fwrite(STDOUT, sprintf(
|
fwrite(STDOUT, sprintf(
|
||||||
"module-runtime-sync: summary migrate=%s permissions-sync=%s build=%s assets-sync=%s vendor_warnings_ignored=%d\n",
|
"module-runtime-sync: summary migrate=%s permissions-sync=%s build=%s assets-sync=%s vendor_warnings_ignored=%d\n",
|
||||||
$statusByStep['migrate'],
|
$statusByStep['migrate'],
|
||||||
$statusByStep['permissions-sync'],
|
$statusByStep['permissions-sync'],
|
||||||
$statusByStep['build'],
|
$statusByStep['build'],
|
||||||
$statusByStep['assets-sync'],
|
$statusByStep['assets-sync'],
|
||||||
$totalVendorWarnings
|
$totalVendorWarnings
|
||||||
));
|
));
|
||||||
|
|
||||||
return $finalExitCode;
|
return $finalExitCode;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PHP_SAPI === 'cli' && realpath((string) ($_SERVER['SCRIPT_FILENAME'] ?? '')) === __FILE__) {
|
if (PHP_SAPI === 'cli' && realpath((string) ($_SERVER['SCRIPT_FILENAME'] ?? '')) === __FILE__) {
|
||||||
|
|||||||
@@ -3,27 +3,16 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
chdir(__DIR__ . '/..');
|
|
||||||
|
|
||||||
require 'vendor/autoload.php';
|
|
||||||
require 'config/config.php';
|
|
||||||
require 'lib/Support/helpers.php';
|
|
||||||
$container = require 'lib/App/registerContainer.php';
|
|
||||||
setAppContainer($container);
|
|
||||||
|
|
||||||
use MintyPHP\DB;
|
use MintyPHP\DB;
|
||||||
use MintyPHP\Http\RequestContext;
|
|
||||||
use MintyPHP\Service\Audit\SystemAuditService;
|
use MintyPHP\Service\Audit\SystemAuditService;
|
||||||
use MintyPHP\Service\Scheduler\SchedulerServicesFactory;
|
use MintyPHP\Service\Scheduler\SchedulerServicesFactory;
|
||||||
|
|
||||||
|
require_once __DIR__ . '/cli-bootstrap.php';
|
||||||
|
|
||||||
$exitCode = 0;
|
$exitCode = 0;
|
||||||
$startedAt = microtime(true);
|
$startedAt = microtime(true);
|
||||||
try {
|
try {
|
||||||
RequestContext::start([
|
cliBootstrapApp('scheduler', 'bin/scheduler-run.php');
|
||||||
'channel' => 'scheduler',
|
|
||||||
'method' => 'CLI',
|
|
||||||
'path' => 'bin/scheduler-run.php',
|
|
||||||
]);
|
|
||||||
$factory = app(SchedulerServicesFactory::class);
|
$factory = app(SchedulerServicesFactory::class);
|
||||||
$result = $factory->createSchedulerRunService()->runDueJobs();
|
$result = $factory->createSchedulerRunService()->runDueJobs();
|
||||||
if (!($result['ok'] ?? false)) {
|
if (!($result['ok'] ?? false)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user