Compare commits

...
3 Commits
6 changed files with 105 additions and 13 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "commands", "name": "commands",
"version": "0.2.7", "version": "0.2.8",
"description": "CLI y migraciones para Duckbrain", "description": "CLI y migraciones para Duckbrain",
"components": { "components": {
"cli": { "cli": {
@@ -89,7 +89,7 @@ final class AdoptCommand extends Command
} }
} }
$coreAdopted = $this->adoptCore($parsed['options'], $root, $lock, $dryRun); [$coreAdopted, $coreHasLocal] = $this->adoptCore($parsed['options'], $root, $lock, $dryRun);
if (!$dryRun) { if (!$dryRun) {
$lock->save($root); $lock->save($root);
@@ -100,7 +100,8 @@ final class AdoptCommand extends Command
$this->console->ok('adoptado ' . $id . $mark); $this->console->ok('adoptado ' . $id . $mark);
} }
if ($coreAdopted > 0) { if ($coreAdopted > 0) {
$this->console->ok("adoptado el core ({$coreAdopted} archivos)"); $mark = $coreHasLocal ? ' (con ediciones locales)' : '';
$this->console->ok("adoptado el core ({$coreAdopted} archivos){$mark}");
} }
if ($adopted === [] && $coreAdopted === 0) { if ($adopted === [] && $coreAdopted === 0) {
$this->console->out('No se encontró nada que adoptar.'); $this->console->out('No se encontró nada que adoptar.');
@@ -111,33 +112,42 @@ final class AdoptCommand extends Command
/** /**
* Registra el core existente: sólo los archivos que también existen en el * Registra el core existente: sólo los archivos que también existen en el
* core de referencia, con su hash actual. No toca archivos. * core de referencia, con su hash CANÓNICO. Si la copia local difiere,
* el archivo queda marcado como edición local y `update core` preguntará
* antes de pisarla (mismo contrato que `adoptComponent`).
* *
* @param array<string, string|bool> $options * @param array<string, string|bool> $options
* @return array{0:int,1:bool}
*/ */
private function adoptCore(array $options, string $root, $lock, bool $dryRun): int private function adoptCore(array $options, string $root, $lock, bool $dryRun): array
{ {
if ($lock->core() !== null) { if ($lock->core() !== null) {
return 0; return [0, false];
} }
try { try {
$provider = $this->coreProvider($options); $provider = $this->coreProvider($options);
} catch (ToolboxException $e) { } catch (ToolboxException $e) {
$this->console->warn('no se pudo resolver el core para adoptar: ' . $e->getMessage()); $this->console->warn('no se pudo resolver el core para adoptar: ' . $e->getMessage());
return 0; return [0, false];
} }
$files = []; $files = [];
$hasLocal = false;
foreach ($provider->files() as $relative) { foreach ($provider->files() as $relative) {
$absolute = $root . '/' . $relative; $absolute = $root . '/' . $relative;
if (is_file($absolute)) { if (!is_file($absolute)) {
$files[$relative] = Drift::hash($absolute); continue;
}
$canonical = $provider->read($relative);
$files[$relative] = 'sha256:' . hash('sha256', $canonical);
if (file_get_contents($absolute) !== $canonical) {
$hasLocal = true;
} }
} }
if ($files === []) { if ($files === []) {
return 0; return [0, false];
} }
if (!$dryRun) { if (!$dryRun) {
@@ -145,9 +155,10 @@ final class AdoptCommand extends Command
'version' => $provider->version(), 'version' => $provider->version(),
'commit' => $provider->commit(), 'commit' => $provider->commit(),
'files' => $files, 'files' => $files,
'origin' => $hasLocal ? 'local' : 'adopted',
]); ]);
} }
return count($files); return [count($files), $hasLocal];
} }
} }
+1 -1
View File
@@ -8,7 +8,7 @@ namespace Toolbox;
*/ */
final class Config final class Config
{ {
public const VERSION = '0.2.7'; public const VERSION = '0.2.8';
public static function toolboxUrl(): string public static function toolboxUrl(): string
{ {
@@ -284,6 +284,13 @@ final class Installer
} }
$absolute = $this->root . '/' . $relative; $absolute = $this->root . '/' . $relative;
if ($options['dryRun'] ?? false) {
$actions[$relative] = 'planned';
$installed[$relative] = 'sha256:' . hash('sha256', $provider->read($relative));
continue;
}
$action = $this->sync->write($absolute, $provider->read($relative), $expected[$relative] ?? null, $options); $action = $this->sync->write($absolute, $provider->read($relative), $expected[$relative] ?? null, $options);
$actions[$relative] = $action; $actions[$relative] = $action;
@@ -300,6 +307,10 @@ final class Installer
if (isset($installed[$relative]) || isset($protected[$relative]) || in_array($relative, $files, true)) { if (isset($installed[$relative]) || isset($protected[$relative]) || in_array($relative, $files, true)) {
continue; continue;
} }
if ($options['dryRun'] ?? false) {
$actions[$relative] = 'remove-planned';
continue;
}
$action = $this->sync->remove($this->root . '/' . $relative, $hash, $options); $action = $this->sync->remove($this->root . '/' . $relative, $hash, $options);
if ($action === FileSync::ABORTED) { if ($action === FileSync::ABORTED) {
$aborted[] = $relative; $aborted[] = $relative;
+42
View File
@@ -194,6 +194,48 @@ Harness::it('--dry-run no escribe archivos ni lock', function () {
rrmdir($tool); rrmdir($tool);
}); });
Harness::it('update core --dry-run no escribe archivos', function () {
$proj = tmpdir('proj');
$tool = g7_toolbox();
$core = g7_core('1.0.0', 'router-v1');
$core2 = g7_core('2.0.0', 'router-v2');
(new InstallCommand(new Console()))->run([$proj, '--toolbox=' . $tool, '--core-dir=' . $core, '--yes']);
$routerBefore = (string) file_get_contents($proj . '/src/Libs/Router.php');
(new UpdateCommand(new Console()))->run(['core', '--root=' . $proj, '--toolbox=' . $tool, '--core-dir=' . $core2, '--yes', '--dry-run']);
ok(str_contains($routerBefore, 'router-v1'), 'estado inicial correcto');
ok(str_contains((string) file_get_contents($proj . '/src/Libs/Router.php'), 'router-v1'), 'dry-run no escribe el fichero');
eq('1.0.0', Lock::load($proj)->core()['version'], 'dry-run no actualiza la version en el lock');
rrmdir($proj);
rrmdir($tool);
rrmdir($core);
rrmdir($core2);
});
Harness::it('update core --dry-run aborta por drift sin escribir', function () {
$proj = tmpdir('proj');
$tool = g7_toolbox();
$core = g7_core('1.0.0', 'router-v1');
$core2 = g7_core('2.0.0', 'router-v2');
(new InstallCommand(new Console()))->run([$proj, '--toolbox=' . $tool, '--core-dir=' . $core, '--yes']);
write_file($proj . '/config.php', "<?php\n// customizado\n");
(new UpdateCommand(new Console()))->run(['core', '--root=' . $proj, '--toolbox=' . $tool, '--core-dir=' . $core2, '--yes', '--dry-run']);
ok(!is_file($proj . '/config.php.bak'), 'dry-run no crea backups');
ok(str_contains((string) file_get_contents($proj . '/config.php'), 'customizado'), 'dry-run conserva la edicion local');
ok(str_contains((string) file_get_contents($proj . '/src/Libs/Router.php'), 'router-v1'), 'dry-run no toca ficheros sin drift');
rrmdir($proj);
rrmdir($tool);
rrmdir($core);
rrmdir($core2);
});
Harness::it('core sin tags: versión desconocida y commit pineado', function () { Harness::it('core sin tags: versión desconocida y commit pineado', function () {
$proj = tmpdir('proj'); $proj = tmpdir('proj');
$tool = g7_toolbox(); $tool = g7_toolbox();
+28
View File
@@ -121,3 +121,31 @@ Harness::it('adopt registra el core existente con sus hashes', function () {
rrmdir($proj); rrmdir($proj);
rrmdir($core); rrmdir($core);
}); });
Harness::it('adopt del core registra hash canónico y marca ediciones locales', function () {
$repo = $GLOBALS['g9_repo'];
$core = g7_core('0.1.0');
$proj = tmpdir('proj');
// Copia idéntica de Router.php + config.php customizado (proyecto heredado).
write_file($proj . '/src/Libs/Router.php', (string) file_get_contents($core . '/src/Libs/Router.php'));
write_file($proj . '/config.php', "<?php\n// credenciales del proyecto\n");
(new AdoptCommand(new Console()))->run(['--root=' . $proj, '--toolbox=' . $repo, '--core-dir=' . $core]);
$lock = Lock::load($proj);
$coreHashRouter = $lock->core()['files']['src/Libs/Router.php'];
$coreHashConfig = $lock->core()['files']['config.php'];
$canonicalRouter = (string) file_get_contents($core . '/src/Libs/Router.php');
$canonicalConfig = (string) file_get_contents($core . '/config.php');
eq('sha256:' . hash('sha256', $canonicalRouter), $coreHashRouter, 'hash de Router.php = canónico');
eq('sha256:' . hash('sha256', $canonicalConfig), $coreHashConfig, 'hash de config.php = canónico (aunque el local difiera)');
eq('local', $lock->core()['origin'] ?? null, 'origin = local porque hay ediciones locales');
ok(file_get_contents($proj . '/config.php') !== $canonicalConfig, 'la copia local customizada no se tocó');
rrmdir($proj);
rrmdir($core);
});