330 lines
14 KiB
Org Mode
330 lines
14 KiB
Org Mode
#+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 <componente|paquete>=
|
|
|
|
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 <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]=
|
|
|
|
#+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, cada uno con su descripción.
|
|
- =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.
|
|
|
|
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:
|
|
|
|
1. *Paquetes/componentes* — la versión vive en =packages/<paquete>/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 <paquete> <patch|minor|major> [--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 <paquete>-v<última>..HEAD -- packages/<paquete>= (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 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).
|
|
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
|