#+TITLE: Duckbrain toolbox #+AUTHOR: KJ #+OPTIONS: toc:2 Instalador y catálogo de paquetes de [[https://git.kj2.me/kj/duckbrain][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: #+BEGIN_SRC sh duckbrain install mi-proyecto duckbrain add crypto duckbrain update #+END_SRC * 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 #+BEGIN_SRC text 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 #+END_SRC 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: #+BEGIN_SRC sh 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 #+END_SRC *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): #+BEGIN_SRC sh 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 #+END_SRC (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 = Instala componentes, resolviendo dependencias y copiando archivos. #+BEGIN_SRC sh 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) #+END_SRC ** =duckbrain remove = 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|] [--dry-run]= #+BEGIN_SRC sh 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 #+END_SRC ** =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= #+BEGIN_SRC 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"] } #+END_SRC - =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 prefijo =src/=); - 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. #+BEGIN_SRC sh # 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" #+END_SRC 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: #+BEGIN_SRC text core < requires avisa "requiere core >=X" requires ≤ core ≤ tested compatible core > tested avisa "no probado" sin datos avisa "sin datos" #+END_SRC 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: 1. *Paquetes/componentes* — la versión vive en =packages//duckbrain.json= y se versiona de forma independiente con tags prefijados (=http-v0.1.0=, =crypto-v0.1.0=…). La única forma de cambiarla es el asistente: #+BEGIN_SRC sh make release # interactivo: elige # paquete e incremento, # muestra cambios # pendientes y confirma scripts/release [--yes] # modo directo #+END_SRC Bumpea el manifiesto, commitea, crea el tag y lo publica. Al liberar =commands= sincroniza también =Config::VERSION= en 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 en =yanked.json=; las prereleases se omiten salvo =--pre=. Para detectar bumps olvidados: =git log -v<última>..HEAD -- packages/= (vacío = al día). 2. *Instalador/phar* — =Config::VERSION= (=packages/commands/src/Toolbox/Config.php=): es lo que imprime la CLI y la referencia con la que =self-update= compara las releases del servidor. No se edita a mano; la mantiene el release de =commands=. Se publica con =make 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) #+BEGIN_SRC sh 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 #+END_SRC 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 #+BEGIN_SRC sh php tests/run.php #+END_SRC * Contacto Puedes encontrarme en Telegram como [[https://telegram.me/keyjay][@keyjay]] o por correo: webmaster@outcontrol.net