The harness failed under a real phpunit run because nothing mapped Tests\ -> tests/ and Commands\Migration needs migrations/ to exist. Declare commands:migration as the actual dependency.
Duckbrain toolbox
- Qué resuelve
- Conceptos
- Estructura del repositorio
- Instalación
- Comandos
- Manifiesto
duckbrain.json - Destino de los archivos
- Estado del proyecto
- Migrar un proyecto existente
- Compatibilidad
- Versionado y releases
- Makefile
- self-update (el phar)
- Pruebas
- Contacto
Instalador y catálogo de paquetes de Duckbrain. Trae el núcleo a un proyecto y gestiona sus addons con un solo comando, manteniendo la filosofía del núcleo: sin Composer obligatorio, código legible y copias reales desplegables (hosting compartido, FTP, etc.).
Qué resuelve
El núcleo Duckbrain mantiene su código simple y sin dependencias, y todo lo
accesorio vive en addons. Antes, instalar un addon era copiar archivos a mano
dentro de src/: cada proyecto quedaba con una versión distinta, las
correcciones no se propagaban y se acumulaba código muerto.
El toolbox convierte eso en:
duckbrain install mi-proyecto
duckbrain add crypto
duckbrain update
Conceptos
| Concepto | Qué es |
|---|---|
| Repositorio | Dónde vive el código (canal de distribución) |
| Paquete | Una familia de componentes (http, crypto, =text=…) |
| Componente | La unidad instalable (una clase o una familia) |
| Manifiesto | duckbrain.json: qué archivos aporta cada componente |
| Catálogo | Los paquetes disponibles en el toolbox |
| Lock | .duckbrain/lock.json: qué quedó instalado y con qué hash |
La unidad que se pide con duckbrain add es el componente, no el paquete ni el
repositorio.
Estructura del repositorio
duckbrain-toolbox/
bin/duckbrain instalador global (bootstrap)
src/autoload.php mapea Toolbox\ -> packages/commands/src/Toolbox/
scripts/release version + tag + push (interactivo o directo)
scripts/build-phar construye dist/duckbrain.phar autocontenido
scripts/publish-phar publica el phar como release en Forgejo
Makefile test / build / release / publish / check / clean
packages/
commands/ cli, migration, toolbox
htmx/ htmlComponent, htmx
http/ curlRequest, aria2, proxy, compreFace
crypto/ crypto, nonce, twoFactor
text/ filter, replacement
cache/ apcache
id/ ulid
test/ harness
Cada paquete tiene su duckbrain.json. Sólo se copian los archivos declarados
por el componente que pides; los metadatos del paquete no se instalan.
Instalación
El instalador global arranca un proyecto desde cero y no necesita nada previo:
git clone git@git.kj2.me:kj/duckbrain-toolbox.git
ln -s "$PWD/duckbrain-toolbox/bin/duckbrain" ~/.local/bin/duckbrain
duckbrain install mi-proyecto # descarga el núcleo Duckbrain
cd mi-proyecto
duckbrain add crypto htmx # copias reales, listas para commitear
Uso opcional dentro del proyecto: si quieres que el proyecto gestione sus
propios addons sin el instalador global, instala el componente commands:toolbox
(es un add más, no un requisito). A partir de ahí ./duckbrain funciona solo.
Alternativa sin clone del repo: descargar el phar autocontenido publicado como
release y ejecutarlo directamente (los enlaces simbólicos no valen:
self-update reemplaza el binario con rename() sobre su propia ruta):
VERSION=$(curl -s https://git.kj2.me/api/v1/repos/kj/duckbrain-toolbox/releases \
| jq -r '.[].tag_name' | grep -E '^v[0-9]' | sort -V | tail -1)
curl -fsSL "https://git.kj2.me/kj/duckbrain-toolbox/releases/download/$VERSION/duckbrain.phar" \
-o ~/.local/bin/duckbrain
chmod +x ~/.local/bin/duckbrain
duckbrain --version # ya es el instalador global; se actualiza solo:
duckbrain self-update
(Para probar el tuyo local sin release, usa cp dist/duckbrain.phar ~/.local/bin/duckbrain
en lugar del curl; make build + ln -s solo sirve mientras no llames a
self-update.)
Comandos
duckbrain install [dir]
Descarga el núcleo Duckbrain en dir. Es sólo del instalador global.
duckbrain add <componente|paquete>
Instala componentes, resolviendo dependencias y copiando archivos.
duckbrain add crypto # la familia completa
duckbrain add http:aria2 # un solo componente
duckbrain add http@^0.1 # fijando restricción de versión
duckbrain add htmx --link # enlaza en lugar de copiar (desarrollo)
duckbrain remove <componente>
Borra sólo los archivos de ese componente; si alguno fue editado localmente,
se bloquea y pide --force, --backup o --keep-local.
duckbrain update [core|<componente>] [--dry-run]
duckbrain update core # sólo el núcleo
duckbrain update http # un paquete
duckbrain update # todo: catálogo -> core -> componentes
duckbrain update --dry-run # muestra el plan sin escribir
duckbrain list / duckbrain doctor / duckbrain catalog / duckbrain adopt
list: core y componentes instalados con versión y modo.doctor: matriz de compatibilidad, sin efectos secundarios.catalog: paquetes y componentes disponibles.adopt: registra copias hechas a mano en proyectos antiguos.
Manifiesto duckbrain.json
{
"name": "http",
"version": "0.1.0",
"description": "Utilidades HTTP",
"core": { "requires": ">=0.1", "tested": "0.1" },
"php": ">=8.1",
"components": {
"curlRequest": { "files": ["src/Libs/CurlRequest.php"] },
"aria2": { "files": ["src/Libs/Aria2.php"] },
"compreFace": {
"files": ["src/Libs/CompreFace.php"],
"require": { "http:curlRequest": ">=0.1" }
}
},
"default": ["curlRequest", "aria2", "compreFace"]
}
files: archivos (o directorios) que aporta el componente.require: dependencias entre componentes, con rangos semver.core.requires/core.tested: compatibilidad con el núcleo.post-install: comandos de configuración (se muestran y confirman).
Destino de los archivos
- un archivo bajo
src/→ROOT_CORE/(quitando el prefijosrc/); - cualquier otro archivo →
ROOT_DIR/.
Así crypto/src/Libs/Crypto.php → src/Libs/Crypto.php y
commands/duckbrain → duckbrain.
Estado del proyecto
.duckbrain/manifest.json: lo que quieres (restricciones de versión)..duckbrain/lock.json: lo que tienes (core y componentes, con versión, commit del toolbox y hash por archivo).
Los hashes permiten detectar ediciones locales. La actualización respeta lo que editaste y te pregunta antes de pisarlo.
Migrar un proyecto existente
Un proyecto que ya tenía Duckbrain y addons copiados a mano entra al toolbox sin
perder nada. Clave: adopt no modifica archivos; sólo registra en
.duckbrain/lock.json lo que encuentra. Los cambios de contenido los hace
update, y sólo cuando tú lo pides.
# 0. Red de seguridad
git add -A && git commit -m "antes de migrar a duckbrain-toolbox"
# 1. Registrar el core y las copias existentes (NO toca archivos)
duckbrain adopt
# 2. Traer lo canónico (y respaldar antes de pisar)
duckbrain update core --backup
duckbrain add http crypto text --backup
# 3. Estado y compatibilidad
duckbrain list
duckbrain doctor
# 4. Commitear la migración
git add -A && git commit -m "migrado a duckbrain-toolbox"
Cómo trata adopt cada archivo:
| Caso del archivo | Qué hace adopt |
¿Actualiza? |
|---|---|---|
| Coincide con el catálogo | Lo registra como adopted |
No |
| Existe pero difiere | Lo registra con origin: local |
No |
| Del core (existe en el core) | Registra el hash actual en lock.core |
No |
| Ajeno al core/addons | No lo toca | No |
Las copias divergentes quedan marcadas como edición local: un update
posterior te avisa y no las pisa salvo --force o --backup. Así puedes revisar
el git diff con calma (por ejemplo, el arreglo de Replacement en
multipaste-vip) y decidir cuándo reconciliar. Como un proyecto heredado no
tiene hashes previos, --backup respalda también la primera escritura.
Compatibilidad
Cada componente declara hasta qué versión del núcleo fue probado:
core < requires avisa "requiere core >=X"
requires ≤ core ≤ tested compatible
core > tested avisa "no probado"
sin datos avisa "sin datos"
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.
Versionado y releases
Hay dos sistemas de versión y no hay que confundirlos:
-
Paquetes/componentes — la versión vive en
packages/<paquete>/duckbrain.jsony se versiona de forma independiente con tags prefijados (http-v0.1.0, =crypto-v0.1.0=…). La única forma de cambiarla es el asistente:make release # interactivo: elige # paquete e incremento, # muestra cambios # pendientes y confirma scripts/release <paquete> <patch|minor|major> [--yes] # modo directoBumpea el manifiesto, commitea, crea el tag y lo publica. Al liberar
commandssincroniza tambiénConfig::VERSIONen el mismo commit. El tag es el descubrimiento; el commit es la identidad, así que el lock pinea commits y no se rompe si un tag se mueve. Las versiones retiradas se listan enyanked.json; las prereleases se omiten salvo--pre. Para detectar bumps olvidados:git log <paquete>-v<última>..HEAD -- packages/<paquete>(vacío = al día). - Instalador/phar —
Config::VERSION(packages/commands/src/Toolbox/Config.php): es lo que imprime la CLI y la referencia con la queself-updatecompara las releases del servidor. No se edita a mano; la mantiene el release decommands. Se publica conmake publish(ver Makefile).
Makefile
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/. |
El token para publicar es un token personal de la API de Forgejo
(Ajustes → Aplicaciones, scope write:repository, con caducidad y revocable).
Los PAT de Forgejo no pueden limitarse a un solo repositorio: para aislar el
riesgo, crea un usuario bot con acceso Write solo en duckbrain-toolbox y usa
su token. dist/ está en gitignore: nunca se commitea.
self-update (el phar)
duckbrain self-update # se auto-reemplaza con la última release
duckbrain self-update --check # actual vs disponible (exit 1 si hay nueva)
duckbrain self-update --force # reemplaza aunque esté al día
Lee la lista de releases
(https://git.kj2.me/api/v1/repos/kj/duckbrain-toolbox/releases, sobrescribible
con DUCKBRAIN_UPDATE_URL) y elige el mayor tag semver con la misma clase que
el core usa con sus tags; tags que no son semver y drafts se ignoran. No
existe la ruta /releases/latest/download/ en Forgejo, así que no se depende de
ella: basta con subir make publish la versión real vX.Y.Z. La descarga se
verifica contra el .sha256 publicado y el reemplazo es atómico (temp + rename)
sobre Phar::running(false). Si Config::VERSION > remota= no se descarga nada.
Para tocar el instalador con el phar en mente: build-phar ya ejecuta con
php -d phar.readonly=0 (crear phars está bloqueado por defecto), el código que
corre dentro del phar no debe usar glob() (no soporta el stream phar://;
usa DirectoryIterator / file_get_contents()), y la raíz del toolbox se
resuelve con Config::toolboxDir(), que ya antepone phar:// cuando toca.
Pruebas
php tests/run.php
Contacto
Puedes encontrarme en Telegram como @keyjay o por correo: webmaster@outcontrol.net