feat(release): introduce interactive mode

This commit is contained in:
kj
2026-10-05 16:22:17 -03:00
parent df2e6e1dfe
commit 6e7b572935
4 changed files with 235 additions and 48 deletions
+86 -8
View File
@@ -41,7 +41,11 @@ repositorio.
#+BEGIN_SRC text
duckbrain-toolbox/
bin/duckbrain instalador global (bootstrap)
scripts/release escritor único de versión + tag
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
@@ -73,6 +77,25 @@ duckbrain add crypto htmx # copias reales, listas para commitear
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]=
@@ -212,18 +235,73 @@ desconocida y se fija por commit.
* Versionado y releases
Cada paquete se versiona de forma independiente con tags prefijados
(=http-v0.1.0=, =crypto-v0.1.0=…). 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.
Hay *dos* sistemas de versión y no hay que confundirlos:
El release lo hace un único script:
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 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
scripts/release <paquete> <patch|minor|major>
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
Bumpea la versión del manifiesto, commitea, crea el tag y publica. Las versiones
retiradas se listan en =yanked.json=; las prereleases se omiten salvo =--pre=.
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