From 6925b814e67140a62e8689f3c02e0fdad436a26d Mon Sep 17 00:00:00 2001 From: kj Date: Mon, 5 Oct 2026 17:36:08 -0300 Subject: [PATCH] fix(commands): surface core fetch errors, run post-install in the project, exit 1 on invalid project command; document compat flow --- README.org | 30 +++++++--- packages/commands/duckbrain | 6 +- packages/commands/duckbrain.json | 60 ++++++++++--------- .../commands/src/Toolbox/CoreProvider.php | 7 ++- packages/commands/src/Toolbox/Installer.php | 3 +- packages/commands/src/Toolbox/PostInstall.php | 46 +++++++++----- 6 files changed, 97 insertions(+), 55 deletions(-) diff --git a/README.org b/README.org index dae58f1..f6231f7 100644 --- a/README.org +++ b/README.org @@ -233,6 +233,19 @@ Nunca bloquea: se confirma con =--yes=. Al core se le sigue la pista por sus tags semver (=vX.Y.Z=, empezando por =v0.1.0=); si no hay tags, la versión es desconocida y se fija por commit. +Los valores =tested= de los manifiestos no son decorativos: los escribe sólo +quien corrió el *barrido de compatibilidad* (=make compat=), que monta un +proyecto temporal por paquete contra el core real publicado y lo ejercita por +HTTP (migraciones sobre sqlite, runner phpunit real; los clientes con servicio +externo — =aria2=, =proxy=, =compreFace= — sólo a nivel de carga/instantiación, +consignado así en =tests/compat/report.json=). Cualquier +=Deprecated=/=Warning=/=Notice= durante el barrido cuenta como fallo. +Un test de la suite (=group11=) exige que cada =tested= declarado tenga su =ok= +en ese reporte y que no se estampen componentes deliberadamente sin datos +(=commands:cli=/=commands:toolbox=, que son el instalador y no consumen el +core). Al aparecer un tag nuevo del core, el ritual es: =make compat= → arreglar +lo que falle → estampar → =make release=. + * Versionado y releases Hay *dos* sistemas de versión y no hay que confundirlos: @@ -265,14 +278,15 @@ Hay *dos* sistemas de versión y no hay que confundirlos: =make help= lista los objetivos: -| Objetivo | Qué hace | -|---------------+------------------------------------------------------------| -| =make test= | Corre la suite (=php tests/run.php=). En verde antes de commitear. | -| =make build= | Genera =dist/duckbrain.phar= + =dist/duckbrain.phar.sha256=. | -| =make release= | Asistente interactivo de releases de paquetes (=ARGS="pkg minor --yes"= para modo directo). | -| =make publish= | =build= + publica la release =v$(Config::VERSION)= con los assets. Requiere =FORGEJO_TOKEN=. | -| =make check= | =self-update --check= contra el servidor: =actual/disponible=, exit 1 si hay nuevo. | -| =make clean= | Borra =dist/=. | +| Objetivo | Qué hace | +|--------------+-------------------------------------------------------------------------------------------| +| =make test= | Corre la suite (=php tests/run.php=). En verde antes de commitear. | +| =make build= | Genera =dist/duckbrain.phar= + =dist/duckbrain.phar.sha256=. | +| =make compat= | Barrido de compatibilidad contra el core real (=scripts/compat-sweep=); requiere red. =ARGS="--only pkg"= para un paquete. | +| =make release= | Asistente interactivo de releases de paquetes (=ARGS="pkg minor --yes"= para modo directo). | +| =make publish= | =build= + publica la release =v$(Config::VERSION)= con los assets. Requiere =FORGEJO_TOKEN=. | +| =make check= | =self-update --check= contra el servidor: =actual/disponible=, exit 1 si hay nuevo. | +| =make clean= | Borra =dist/=. | El token para publicar es un token personal de la API de Forgejo (*Ajustes → Aplicaciones*, scope =write:repository=, con caducidad y revocable). diff --git a/packages/commands/duckbrain b/packages/commands/duckbrain index d62a506..e69f00d 100644 --- a/packages/commands/duckbrain +++ b/packages/commands/duckbrain @@ -28,7 +28,8 @@ if ($command === null || in_array($command, $toolboxCommands, true)) { $coreCommand = 'Commands\\' . ucfirst($command); if (!class_exists($coreCommand) && !file_exists(ROOT_CORE . '/Commands/' . ucfirst($command) . '.php')) { - exit("Invalid command.\n"); + fwrite(STDERR, "Invalid command.\n"); + exit(1); } $cli = new $coreCommand(); @@ -36,5 +37,6 @@ $cli = new $coreCommand(); if ($cli instanceof Interfaces\CommandInterface) { call_user_func_array([$cli, 'run'], array_slice($argv, 2)); } else { - exit("Invalid command.\n"); + fwrite(STDERR, "La clase $coreCommand no implementa CommandInterface.\n"); + exit(1); } diff --git a/packages/commands/duckbrain.json b/packages/commands/duckbrain.json index df2e588..4ac860f 100644 --- a/packages/commands/duckbrain.json +++ b/packages/commands/duckbrain.json @@ -1,31 +1,35 @@ { - "name": "commands", - "version": "0.2.0", - "description": "CLI y migraciones para Duckbrain", - "components": { - "cli": { - "files": [ - "duckbrain", - "src/Interfaces/CommandInterface.php" - ] - }, - "migration": { - "files": [ - "src/Commands/Migration.php", - "src/Libs/Migration.php" - ], - "require": { - "commands:cli": "*" - } - }, - "toolbox": { - "files": [ - "src/Toolbox" - ] - } + "name": "commands", + "version": "0.2.0", + "description": "CLI y migraciones para Duckbrain", + "components": { + "cli": { + "files": [ + "duckbrain", + "src/Interfaces/CommandInterface.php" + ] }, - "default": [ - "cli", - "migration" - ] + "migration": { + "files": [ + "src/Commands/Migration.php", + "src/Libs/Migration.php" + ], + "require": { + "commands:cli": "*" + }, + "core": { + "requires": ">=0.1", + "tested": "0.1" + } + }, + "toolbox": { + "files": [ + "src/Toolbox" + ] + } + }, + "default": [ + "cli", + "migration" + ] } diff --git a/packages/commands/src/Toolbox/CoreProvider.php b/packages/commands/src/Toolbox/CoreProvider.php index 4a011a0..582aab6 100644 --- a/packages/commands/src/Toolbox/CoreProvider.php +++ b/packages/commands/src/Toolbox/CoreProvider.php @@ -36,7 +36,12 @@ final class CoreProvider throw new ToolboxException('No se pudo clonar el core: ' . implode("\n", $out)); } } else { - exec('git -C ' . escapeshellarg($dir) . ' fetch -q --tags 2>&1'); + exec('git -C ' . escapeshellarg($dir) . ' remote set-url origin ' . escapeshellarg($url) . ' 2>&1'); + $out = []; + exec('git -C ' . escapeshellarg($dir) . ' fetch -q --tags 2>&1', $out, $code); + if ($code !== 0) { + throw new ToolboxException('No se pudo actualizar el core: ' . implode("\n", $out)); + } } $provider = new self($dir); diff --git a/packages/commands/src/Toolbox/Installer.php b/packages/commands/src/Toolbox/Installer.php index 2fa00b5..042c256 100644 --- a/packages/commands/src/Toolbox/Installer.php +++ b/packages/commands/src/Toolbox/Installer.php @@ -191,7 +191,8 @@ final class Installer $manifest->postInstall, $this->console, (bool) ($options['yes'] ?? false), - $options['postRunner'] ?? null + $options['postRunner'] ?? null, + $this->root, ); } } diff --git a/packages/commands/src/Toolbox/PostInstall.php b/packages/commands/src/Toolbox/PostInstall.php index 9e2ebe3..8de085e 100644 --- a/packages/commands/src/Toolbox/PostInstall.php +++ b/packages/commands/src/Toolbox/PostInstall.php @@ -11,35 +11,51 @@ final class PostInstall /** * @param list $commands * @param callable(string):int|null $runner + * @param string|null $cwd Directorio de trabajo del proyecto: los + * comandos se ejecutan alli, no donde arranco + * el instalador. * @return list comandos ejecutados */ public static function run( array $commands, Console $console, bool $assumeYes = false, - ?callable $runner = null + ?callable $runner = null, + ?string $cwd = null ): array { $executed = []; + $previous = null; - foreach ($commands as $command) { - $console->out(' post-install: ' . $command); + if ($cwd !== null && is_dir($cwd)) { + $previous = getcwd(); + chdir($cwd); + } - if (!$console->confirm(' ¿Ejecutar?', $assumeYes)) { - $console->warn('post-install omitido: ' . $command); + try { + foreach ($commands as $command) { + $console->out(' post-install: ' . $command); - continue; - } + if (!$console->confirm(' ¿Ejecutar?', $assumeYes)) { + $console->warn('post-install omitido: ' . $command); - if ($runner !== null) { - $runner($command); - } else { - passthru($command, $code); - if (($code ?? 0) !== 0) { - throw new ToolboxException("post-install falló: {$command}"); + continue; } - } - $executed[] = $command; + if ($runner !== null) { + $runner($command); + } else { + passthru($command, $code); + if (($code ?? 0) !== 0) { + throw new ToolboxException("post-install falló: {$command}"); + } + } + + $executed[] = $command; + } + } finally { + if ($previous !== null) { + chdir($previous); + } } return $executed;