diff --git a/CLAUDE.md b/CLAUDE.md index f9b4f7e..7a8b53c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -154,8 +154,8 @@ boot adminer — `api.php`, `Settings/Theme/screenshot.php` — each `require` (`Env::DataDir->get()`, `Latte::engine()`, `Debug\Tracy::enable()`), so there are no function files to special-case — PSR-4 covers everything. -**Build tooling in PHP goes to `app/cli/`, as `Desktop\Cli\` classes.** A static method with its -test in `tests/cli/`, added to `make qa`. It is dev-only autoload, and `package-app` removes the +**Build tooling in PHP goes to `app/cli/Command/`, as `Desktop\Cli\Command\` classes.** A static method listed in +`app/cli/run.php`, which the Makefile calls as `$(CLI) `, with its test in `tests/cli/`, added to `make qa`. It is dev-only autoload, and `package-app` removes the folder, so it never ships. `cli/` still holds the scripts not moved yet. ## The Adminer Desktop theme diff --git a/Makefile b/Makefile index 77b03d8..068d014 100644 --- a/Makefile +++ b/Makefile @@ -190,6 +190,8 @@ COMPOSER_VERSION = 2.10.2 # at startup, before any argument parsing, so it lands whatever the SAPI does with argv. WIN_INI = .cache/php-ini COMPOSER = $(if $(EXE),PHP_INI_SCAN_DIR=$(WIN_INI)) ./bin/frankenphp$(EXE) php-cli .cache/composer.phar +# The build tooling under app/cli/: `$(CLI) `, the commands listed in run.php. +CLI = ./bin/frankenphp$(EXE) php-cli app/cli/run.php # Both paths are relative to the directory make runs in, which is the repo root; composer's # --working-dir only chdirs later, long after PHP has loaded its extensions. @@ -335,6 +337,7 @@ qa: fetch app/vendor i18n mcp-bridge-data ## Run every static check (php, go, j ./bin/frankenphp$(EXE) php-cli tests/version/AppVersionTest.php ./bin/frankenphp$(EXE) php-cli tests/shortcuts/ShortcutsTest.php ./bin/frankenphp$(EXE) php-cli tests/cli/WindowsIcoTest.php + ./bin/frankenphp$(EXE) php-cli tests/cli/I18nTest.php @sh tests/cli/kill-orphans-test.sh @sh tests/cli/dresscode-test.sh @sh tests/cli/dresscode-hook-test.sh @@ -397,7 +400,7 @@ endif # i18n dependency. The outputs are gitignored and rebuilt here before every build; the language # files are the committed source. The PHP plugin UI localises itself, via AdminerDesktop::$translations. i18n: bin/frankenphp$(EXE) app/vendor ## Generate the native-shell translations from app/src/I18n/Locale/*.php - ./bin/frankenphp$(EXE) php-cli cli/i18n.php + $(CLI) i18n # The MCP bridge's copy of what PHP owns — the tool catalogue for both settings, the offline # sentences per locale — so the Go bridge describes tools from Server::catalogue() rather than a @@ -413,7 +416,7 @@ bench: mcp-bridge-data ## Benchmark the MCP bridge's own cost per call # Which strings each locale is still missing. Exits non-zero if any are, so CI can gate on it. i18n-check: bin/frankenphp$(EXE) app/vendor ## Report native-shell translation coverage (missing strings per locale) - @./bin/frankenphp$(EXE) php-cli cli/i18n.php check + @$(CLI) i18n-check # The exe's icon on Windows, from the same logo.png as the mac and linux ones. go build links any # .syso beside the sources, and windres comes with the gcc cgo already needs. @@ -422,9 +425,9 @@ WIN_ICON = $(if $(EXE),launcher/icon_windows_amd64.syso) launcher/icon_windows_amd64.syso: launcher/icon.rc build.noindex/logo.ico windres -O coff -i $< -o $@ -build.noindex/logo.ico: assets/logo.png app/cli/WindowsIco.php app/vendor $(if $(EXE),$(WIN_INI)/gd.ini) +build.noindex/logo.ico: assets/logo.png app/cli/Command/WindowsIco.php app/vendor $(if $(EXE),$(WIN_INI)/gd.ini) @mkdir -p $(dir $@) - $(if $(EXE),PHP_INI_SCAN_DIR=$(WIN_INI)) ./bin/frankenphp$(EXE) php-cli -r 'require "app/vendor/autoload.php"; Desktop\Cli\WindowsIco::run();' + $(if $(EXE),PHP_INI_SCAN_DIR=$(WIN_INI)) $(CLI) windows-ico build: fetch i18n mcp-bridge-data $(WIN_ICON) ## Build the launcher binary go build -ldflags "$(LDFLAGS)" -o build.noindex/adminer-desktop$(EXE) ./launcher diff --git a/app/cli/Command/I18n.php b/app/cli/Command/I18n.php new file mode 100644 index 0000000..640baaa --- /dev/null +++ b/app/cli/Command/I18n.php @@ -0,0 +1,39 @@ +generate(dirname(__DIR__, 3)); + echo "i18n: wrote " . count($catalog->all()) . " .strings + launcher/i18n_gen.h\n"; + } + + /** Exits non-zero on a gap, for CI. */ + static function check(): void { + $ok = true; + foreach (Domain::cases() as $domain) { + $ok = self::passes($domain->value, Catalog::load($domain)) && $ok; + } + exit($ok ? 0 : 1); + } + + /** Prints the domain's report, and says whether it is complete with every ID well-formed. */ + static function passes(string $label, Catalog $catalog): bool { + echo (new Generator($catalog))->report($label), "\n"; + $malformed = $catalog->malformedIds(); + if ($malformed !== []) { + echo "Malformed IDs in $label: '" . implode("', '", $malformed) . "'\n\n"; + } + return $catalog->complete() && $malformed === []; + } +} diff --git a/app/cli/WindowsIco.php b/app/cli/Command/WindowsIco.php similarity index 96% rename from app/cli/WindowsIco.php rename to app/cli/Command/WindowsIco.php index 81f23e1..0b7b622 100644 --- a/app/cli/WindowsIco.php +++ b/app/cli/Command/WindowsIco.php @@ -1,7 +1,7 @@ `. + +use Desktop\Cli\Command\I18n; +use Desktop\Cli\Command\WindowsIco; + +require dirname(__DIR__) . "/vendor/autoload.php"; + +$commands = [ + "i18n" => I18n::run(...), + "i18n-check" => I18n::check(...), + "windows-ico" => WindowsIco::run(...), +]; + +$command = $commands[$argv[1] ?? ""] ?? null; +if (!$command) { + fwrite(STDERR, "usage: run.php " . implode("|", array_keys($commands)) . "\n"); + exit(2); +} +$command(); diff --git a/app/src/I18n/Generator.php b/app/src/I18n/Generator.php index 15c6fd1..ac47de3 100644 --- a/app/src/I18n/Generator.php +++ b/app/src/I18n/Generator.php @@ -11,7 +11,7 @@ * for the C side. English is the base and the fallback, so it gets no C rows of its own. */ final class Generator { - private const string BANNER = 'Generated from app/src/I18n/native/*.php by cli/i18n.php. Do not edit — edit the language files and run `make i18n`.'; + private const string BANNER = 'Generated from app/src/I18n/native/*.php by app/cli/Command/I18n.php. Do not edit — edit the language files and run `make i18n`.'; public function __construct(private readonly Catalog $catalog) { } diff --git a/cli/i18n.php b/cli/i18n.php deleted file mode 100644 index ab62ef9..0000000 --- a/cli/i18n.php +++ /dev/null @@ -1,35 +0,0 @@ -report($domain->value), "\n"; - $malformed = $catalog->malformedIds(); - if ($malformed !== []) { - echo "Malformed IDs in {$domain->value}: '" . implode("', '", $malformed) . "'\n\n"; - } - $ok = $ok && $catalog->complete() && $malformed === []; - } - exit($ok ? 0 : 1); -} - -$catalog = Catalog::load(Domain::Native); -(new Generator($catalog))->generate($root); -echo "i18n: wrote " . count($catalog->all()) . " .strings + launcher/i18n_gen.h\n"; diff --git a/tests/cli/I18nTest.php b/tests/cli/I18nTest.php new file mode 100644 index 0000000..85408bd --- /dev/null +++ b/tests/cli/I18nTest.php @@ -0,0 +1,29 @@ + ['menu.open' => 'Open'], 'cs' => ['menu.open' => 'Otevřít']]); +Assert::true($passed); + +[$passed, $out] = $passes(['en' => ['menu.open' => 'Open'], 'cs' => []]); +Assert::false($passed); +Assert::contains('menu.open', $out); + +[$passed, $out] = $passes(['en' => ['Bad Id' => 'Open'], 'cs' => ['Bad Id' => 'Otevřít']]); +Assert::false($passed); +Assert::contains("Malformed IDs in test: 'Bad Id'", $out); diff --git a/tests/cli/WindowsIcoTest.php b/tests/cli/WindowsIcoTest.php index b7c1612..b6131db 100644 --- a/tests/cli/WindowsIcoTest.php +++ b/tests/cli/WindowsIcoTest.php @@ -5,7 +5,7 @@ * wrong offset still builds, and shows up only as a blank icon on a machine nobody tests on. */ -use Desktop\Cli\WindowsIco; +use Desktop\Cli\Command\WindowsIco; use Tester\Assert; require dirname(__DIR__) . "/bootstrap.php";