diff --git a/.github/workflows/action-test.yml b/.github/workflows/action-test.yml index 2494d85..3a2d61c 100644 --- a/.github/workflows/action-test.yml +++ b/.github/workflows/action-test.yml @@ -53,6 +53,15 @@ jobs: species: cactus out: out/cactus.svg + - name: Action con la especie calabaza (sin red) + id: pumpkin + uses: ./ + with: + user: "" + fixture: testdata/events.json + species: pumpkin + out: out/pumpkin.svg + - name: Action con la tarjeta compacta (sin red) id: compact uses: ./ @@ -145,6 +154,7 @@ jobs: FIXTURE_PATH: ${{ steps.fixture.outputs.path }} MUSHROOM_PATH: ${{ steps.mushroom.outputs.path }} CACTUS_PATH: ${{ steps.cactus.outputs.path }} + PUMPKIN_PATH: ${{ steps.pumpkin.outputs.path }} COMPACT_PATH: ${{ steps.compact.outputs.path }} BADSIZE_OUTCOME: ${{ steps.badsize.outcome }} REAL_PATH: ${{ steps.real.outputs.path }} @@ -165,7 +175,7 @@ jobs: test "$NOUSER_OUTCOME" = failure || { echo 'un usuario inexistente debería fallar'; exit 1; } cmp out/nouser.svg "$FIXTURE_PATH" || { echo 'el SVG previo del usuario inexistente cambió'; exit 1; } cmp out/keep.svg "$FIXTURE_PATH" || { echo 'el SVG previo cambió'; exit 1; } - for f in "$FIXTURE_PATH" "$MUSHROOM_PATH" "$CACTUS_PATH" "$COMPACT_PATH" "$REAL_PATH"; do + for f in "$FIXTURE_PATH" "$MUSHROOM_PATH" "$CACTUS_PATH" "$PUMPKIN_PATH" "$COMPACT_PATH" "$REAL_PATH"; do test -s "$f" head -c 120 "$f"; echo grep -q '' "$f" @@ -178,6 +188,8 @@ jobs: if grep -q 'Retoño' "$MUSHROOM_PATH"; then echo 'la especie hongo no se aplicó'; exit 1; fi grep -q 'Cactus' "$CACTUS_PATH" if grep -q 'Retoño' "$CACTUS_PATH"; then echo 'la especie cactus no se aplicó'; exit 1; fi + grep -q 'Calabacita' "$PUMPKIN_PATH" + if grep -q 'Retoño' "$PUMPKIN_PATH"; then echo 'la especie calabaza no se aplicó'; exit 1; fi grep -q "@$OWNER" "$REAL_PATH" grep -q '#2b2724' "$REAL_PATH" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b0cfa9..16f0268 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,7 +109,7 @@ jobs: go build -o "$RUNNER_TEMP/commitling" ./cmd/commitling mkdir -p out/wf "$RUNNER_TEMP/commitling" workflow > out/wf/base.yml 2>/dev/null - for sp in moss mushroom cactus; do for th in light dark both; do for sz in full compact; do + for sp in moss mushroom cactus pumpkin; do for th in light dark both; do for sz in full compact; do "$RUNNER_TEMP/commitling" workflow --user octocat --species "$sp" --theme "$th" --size "$sz" > "out/wf/$sp-$th-$sz.yml" 2>/dev/null done; done; done if grep -L 'uses: BertMarti/commitling@v1$' out/wf/*.yml | grep .; then echo "sin @v1"; exit 1; fi diff --git a/AGENTS.md b/AGENTS.md index 68bc33f..78bc040 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -80,6 +80,10 @@ Una mascota pixel-art **original** que vive en el README de tu perfil de GitHub. - Lo que solo es cierto tras publicar (quitar «el cactus aún no está en `@v1`», «desde la versión 1.0.0» de `go run …@latest`, ejemplos `@v1.0.0`) va en la rama `v1-tras-publicar`, que además borra `release/checksums.txt` (cambia la plantilla embebida). - Semver a partir de aquí: la interfaz de `action.yml` y el formato del SVG solo cambian de forma incompatible en una `v2`; mejoras compatibles → 1.x (menor), arreglos → parche. +## Versión 1.1.0 (31/10/2026) +- **Cuarta especie `pumpkin`** (`internal/creature/pumpkin.go`, «Calabaza»). La OG coloca dos especies por fila (`stagePos` en `internal/og/og.go`). +- Mismo ciclo que la 1.0.0: el último commit del PR lleva `release/checksums.txt`; lo que solo es cierto tras publicar (quitar «la calabaza llega con la 1.1.0», borrar `release/checksums.txt` de `main`) va en la rama `v1.1-tras-publicar`. + ## Reglas de contenido - El personaje es **diseño propio**. Prohibido imitar criaturas de videojuegos, anime o marcas. - Las reglas de fase y ánimo están documentadas en el README y son deterministas (mismos datos, mismo SVG). diff --git a/CHANGELOG.md b/CHANGELOG.md index f3fdb19..98662a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,21 @@ El formato sigue [Keep a Changelog](https://keepachangelog.com/es-ES/1.1.0/) y e ## [Sin publicar] +## [1.1.0] - 2026-10-31 + +Una especie nueva y nada más que cambie para quien ya usa commitling: **la interfaz de `action.yml` es la misma** (mismas entradas con los mismos valores por defecto, misma salida `path`, mismos archivos; solo cambia el texto de ayuda de `species` para nombrar la nueva) y las tarjetas del brote de musgo, el hongo y el cactus son byte a byte las de la v1.0.0. Con `@v1` no hay que hacer nada. + +### Añadido + +- **Cuarta especie: la calabaza** (`species: pumpkin`, también vale `calabaza`), de diseño propio: una calabaza de huerto con ojos que pasa por **Pepita** (una pepita con un brote), **Plántula** (dos hojas redondas con la cáscara de la pepita encima), **Calabacita** (verde y rayada), **Calabaza** (naranja, con gajos, hoja y zarcillo) y **Calabaza gigante**, con las mismas reglas, ánimos y accesorios. Está en la Action, en `--species` de la CLI (`render` y `workflow`), en la galería de la web (claro, oscuro y tarjeta compacta), en los dos generadores de la web y en la imagen de vista previa. + ### Cambiado -- Con la v1.0.0 publicada y `v1` apuntando a ella, el README, la guía de uso y la web dejan de avisar de que el cactus y `go run …@latest workflow` necesitan una versión aún sin publicar, y los ejemplos de versión exacta pasan a `@v1.0.0`. -- `release/checksums.txt` sale de `main`: describe los binarios de la v1.0.0 ya publicada (va en su etiqueta) y no los de `main`, que ha cambiado. La próxima versión lo vuelve a generar en su commit. +- La imagen de vista previa (`og.png`) coloca las fases de dos especies por fila para que quepan las cuatro. +- La web describía el cactus como «una semilla» al empezar; su primera fase es la **yema**. +- Con la v1.0.0 publicada y `v1` apuntando a ella, el README, la guía de uso y la web dejaron de avisar de que el cactus y `go run …@latest workflow` necesitaban una versión aún sin publicar, y los ejemplos de versión exacta pasaron a `@v1.0.0`. +- `release/checksums.txt` vuelve con los SHA-256 de los binarios de la 1.1.0 (tras la 1.0.0 había salido de `main`). +- Versión de la CLI: `1.1.0`. ## [1.0.0] - 2026-10-26 @@ -182,7 +193,8 @@ Primera versión: el MVP. - Las acciones de los workflows suben de versión para evitar Node 20 (`checkout@v7`, `setup-go@v7`, `configure-pages@v6`, `upload-pages-artifact@v5` y `deploy-pages@v5`) (PR #2). -[Sin publicar]: https://github.com/BertMarti/commitling/compare/v1.0.0...HEAD +[Sin publicar]: https://github.com/BertMarti/commitling/compare/v1.1.0...HEAD +[1.1.0]: https://github.com/BertMarti/commitling/compare/v1.0.0...v1.1.0 [1.0.0]: https://github.com/BertMarti/commitling/compare/v0.7.0...v1.0.0 [0.7.0]: https://github.com/BertMarti/commitling/compare/v0.6.0...v0.7.0 [0.6.0]: https://github.com/BertMarti/commitling/compare/v0.5.0...v0.6.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eb8794a..359ade8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ go run ./cmd/commitling render --fixture testdata/events.json --out out/commitli # Con tu usuario real (GITHUB_TOKEN es opcional, sube el límite de peticiones) go run ./cmd/commitling render --user tu-usuario --out out/commitling.svg -# Otra especie (moss por defecto, mushroom o cactus) +# Otra especie (moss por defecto, mushroom, cactus o pumpkin) go run ./cmd/commitling render --fixture testdata/events.json --species mushroom --out out/hongo.svg # Web con todas las fases, ánimos y accesorios (y og.png) @@ -87,26 +87,27 @@ Archivos exactos, en este orden: 1. `internal/creature/creature.go`: añade la constante de `Species` **después** de `Mushroom` (así el valor cero sigue siendo el brote de musgo) y una entrada, en el mismo orden, en cada uno de estos cuatro sitios: `AllSpecies`, `speciesNames` (nombre en español), `speciesSlugs` (ASCII: es lo que se escribe en `species:` y en `--species`) y `speciesStageNames` (los nombres en español de sus cinco fases). Si quieres alias, añádelos al `switch` de `SpeciesByName`. 2. Un archivo nuevo en `internal/creature/` (como `mushroom.go`) con su `[...]stageArt` de cinco mapas 16×16 (`body`, `face`, `hat`, `scarf`, `flower`), y engánchalo en `artFor` de `internal/creature/sprites.go` (un `switch`: añade un `case`). La cara (8×4) va sobre un relleno, la fila de la bufanda debe ser un tramo continuo y el gorro y la flor no pueden tapar la cara. Usa solo la paleta de 5 colores y no copies mapas de otra especie ni de ninguna obra ajena. -3. `cmd/commitling/main.go`: el mensaje «especie no válida … (usa moss, mushroom o cactus)» y la ayuda de `--species` (en el comentario del archivo y en `usage`) listan los valores; añade el nuevo. Igual el texto de la entrada `species` de `action.yml`. +3. `cmd/commitling/main.go`: el mensaje «especie no válida … (usa moss, mushroom, cactus o pumpkin)» y la ayuda de `--species` (en el comentario del archivo y en `usage`) listan los valores; añade el nuevo. Igual el texto de la entrada `species` de `action.yml`. 4. La galería (`internal/gallery`), la tarjeta SVG y la imagen Open Graph (`internal/og`) recorren `AllSpecies`, así que la nueva especie sale sin más cambios: genera la web y la imagen y revísalas en claro y oscuro. 5. Actualiza las tablas de especies del `README.md` y de `docs/USO.md` (valores de `species` y nombres de las fases), el `CHANGELOG.md` y `MEMORY.md`. Tests que deben pasar (y qué tocar en ellos): -- `internal/creature`: `TestSpeciesNamesAndSlugs` (fija `len(AllSpecies) != 3`: sube el número; comprueba nombres, slugs únicos y cinco nombres de fase), `TestSpeciesByName` (añade los alias), `TestAnchorsFitTheBody` (cara sobre relleno, bufanda continua, gorro y flor sin tapar la cara, para todas las especies y fases), `TestDrawUsesPalette` (solo colores de la paleta), `TestSpeciesDrawDifferently` (cada especie se dibuja distinta y de forma determinista, y cada fase distinta de las demás) y un test como `TestCactusMapsAreNotTheOthers` para tu especie (ningún mapa igual a los de las demás). +- `internal/creature`: `TestSpeciesNamesAndSlugs` (fija `len(AllSpecies) != 4`: sube el número; comprueba nombres, slugs únicos y cinco nombres de fase), `TestSpeciesByName` (añade los alias), `TestAnchorsFitTheBody` (cara sobre relleno, bufanda continua, gorro y flor sin tapar la cara, para todas las especies y fases), `TestDrawUsesPalette` (solo colores de la paleta), `TestSpeciesDrawDifferently` (cada especie se dibuja distinta y de forma determinista, y cada fase distinta de las demás) y un test como `TestCactusMapsAreNotTheOthers` para tu especie (ningún mapa igual a los de las demás). - `internal/render`: la tarjeta es XML válido, determinista y el texto cabe, para todas las especies (`TestMushroomCard` es el modelo). - `internal/gallery`: `TestBuildShowsBothSpecies`, `TestBuildIsDeterministic`, `TestGalleryAltsAreUnique` y `TestPageHasSkipLinkAndSpeciesNav`. - `internal/og`: las cinco fases de cada especie aparecen (`TestStageRowsShowAllStages`) y todo cabe en el marco. - `cmd/commitling`: la especie inválida sigue fallando antes de tocar la red y el mensaje lista los valores. - `internal/gallery/action_test.go`: `action.yml` y la tabla de entradas del README siguen coincidiendo. -- `.github/workflows/action-test.yml` prueba `species: mushroom` y `species: cactus`; añade un paso para la nueva. También el bucle `for sp in …` de los workflows generados en `ci.yml`, la lista `ALLOWED.species` de `internal/gallery/install.js` y la de `internal/gallery/testdata/install.test.js` (y el número de casos de `TestInstallJSMatchesGo`). -- `internal/og`: si la fila de fases ya no cabe sobre el nombre de la web, ajusta `stageY`, `stageRowH` y `siteY` (con tres especies, 380, 56 y 556). +- `.github/workflows/action-test.yml` prueba `species: mushroom`, `species: cactus` y `species: pumpkin`; el bucle `for sp in …` de `ci.yml`, la lista `ALLOWED.species` de `internal/gallery/install.js` y la de `internal/gallery/testdata/install.test.js` cubren las cuatro especies (96 casos en `TestInstallJSMatchesGo`). +- `internal/og`: la imagen OG coloca dos especies por fila (`stagePos`); con más de cuatro hay que añadir una fila y comprobar que cabe sobre `siteY`. - `internal/gallery`: el número de archivos de `TestBuild` y los `alt` únicos (`TestGalleryAltsAreUnique`): un nombre de fase no puede repetir el de otra especie con las mismas estadísticas (por eso la primera fase del cactus es «Yema» y no «Semilla»). +- `internal/gallery`: `TestCardsAreByteIdenticalToV100` (huellas de `testdata/golden/v1.0.0-sha256.txt`) y `TestFullCardsAreByteIdenticalToV050` deben seguir pasando sin tocar las listas: las especies que ya estaban no pueden cambiar. - Comprueba que las especies existentes no cambian ni un byte: genera la galería en `main` y en tu rama y compara con `diff -r` (solo deben aparecer archivos nuevos de tu especie, `index.html`, `og.png` y los scripts). ## Regla de diseño: personaje original -Las criaturas son **diseño propio**: un brote de musgo con ojos, una seta pequeña con ojos y un cactus con ojos en su maceta, dibujados píxel a píxel. Está prohibido imitar criaturas de videojuegos, anime o marcas, y no se aceptan sprites, nombres ni siluetas tomados de otras obras. +Las criaturas son **diseño propio**: un brote de musgo con ojos, una seta pequeña con ojos, un cactus con ojos en su maceta y una calabaza de huerto con ojos, dibujados píxel a píxel. Está prohibido imitar criaturas de videojuegos, anime o marcas, y no se aceptan sprites, nombres ni siluetas tomados de otras obras. Respeta también el estilo «papel y píxel» de [AGENTS.md](AGENTS.md): paleta de la criatura limitada a 5 colores (`#2b2724`, `#f3efe6`, `#7fb069`, `#e6aa68`, `#ca3c25`), píxeles nítidos, sin degradados ni sombras, animaciones suaves y respetando `prefers-reduced-motion`. diff --git a/MEMORY.md b/MEMORY.md index 7c6027d..57871ab 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -1,7 +1,9 @@ # MEMORY.md · commitling -Última actualización: 2026-10-26 por Claude Code (Opus 5.5, jefe de la fase 5, rama `v1-2026-10-26`) +Última actualización: 2026-10-31 por Claude Code (Opus 5.5, jefe de la fase 6, rama `v1.1-2026-10-31`) ## Estado actual +**Versión 1.1.0 (fase 6, 31/10/2026, rama `v1.1-2026-10-31`, PR contra `main`)**: v1.0.0 publicada y `v1` apuntando a ella. Cuarta especie **calabaza** (`pumpkin`; fases Pepita, Plántula, Calabacita, Calabaza y Calabaza gigante) en la Action, la CLI, la galería, los dos generadores y la OG (ahora dos especies por fila). Musgo, hongo y cactus byte a byte iguales (galería de `main` contra la rama: 180 SVG y favicon idénticos; 12 combinaciones de la CLI idénticas; huellas de v0.5.0). De `action.yml` solo cambia la `description` de `species`. El último commit lleva `release/checksums.txt` de la 1.1.0. Avisos «la calabaza llega con la 1.1.0» en README, `docs/USO.md` y el instalador de la web: los quita la rama `v1.1-tras-publicar` (que además borra `release/checksums.txt` de `main`), que se fusiona después de publicar y mover `v1`. Pasos exactos para el orquestador en el informe de la fase 6. + **Tras publicar la v1.0.0 (rama `v1-tras-publicar`, se fusiona después de la release y de mover `v1`)**: sin avisos de «aún no publicada» (README, `docs/USO.md`, web), ejemplos de versión exacta `@v1.0.0` y sin `release/checksums.txt` en `main` (va en la etiqueta v1.0.0; regenerarlo con la misma versión daría SHA-256 distintos de los subidos porque la plantilla embebida cambió). `@main` compila siempre con Go. **Versión 1.0.0 (fase 5, 26/10/2026, rama `v1-2026-10-26`, PR contra `main`)**: la 0.8.0 nunca se publicó y se salta: todo lo de las fases 1 a 4 sale como **v1.0.0** (CLI, Action, web y docs en 1.0.0; CHANGELOG `[1.0.0] - 2026-10-26`). El commit lleva `release/checksums.txt` de la 1.0.0 (generado tras el último cambio de código; la plantilla de la web va embebida en el binario), así que al publicar la release el workflow «Release» sube los binarios y la Action deja de compilar. Antes de publicarla, el paso `bin` recibe un 404 y compila con Go (probado en el job `binario`, que ahora prueba también la release real). El job `action` fuerza la ruta de `go run` (`COMMITLING_RELEASE_URL` a un puerto cerrado) para que el chequeo del Go parcheado no dependa de si la release existe. Lo que solo es cierto tras publicar está en la rama `v1-tras-publicar` (sin PR): quita los avisos del cactus y del `go run …@latest`, pone los ejemplos `@v1.0.0` y borra `release/checksums.txt`. `v1` sigue en v0.7.0 hasta que el orquestador la mueva. @@ -173,7 +175,13 @@ Pulido de v0.4.0 (rama `agent/builder/v0.4-pulido`, un PR contra `main`, tras la - **Cactus**: primera fase «Yema» (no «Semilla»: repetiría el `alt` del musgo con las mismas estadísticas, `TestGalleryAltsAreUnique`). Los mapas de Luna no valían (una fila de 15 píxeles y «con brazos» sin brazos): los redibujó Claude. La OG se reajustó (`stageY` 380, `stageRowH` 56, `siteY` 556) para tres filas. - **Acciones por SHA** también en `action.yml` (el `setup-go` que corre en los perfiles); los workflows que genera la web para las personas siguen con `@v7` (legibles y suyos). +### Fase 6 (31/10/2026) +- **Calabaza** como cuarta especie (receta de `CONTRIBUTING.md`): encaja con las otras (planta de huerto), usa la paleta tal cual (miel = calabaza, musgo = tallo y hojas, papel = pepita, acento = gajos). Primera fase «Pepita» (no «Semilla», por `TestGalleryAltsAreUnique`). Los gajos son píxeles de acento y se curvan alrededor de la cara; la pepita lleva borde miel para que se lea en oscuro (el contorno pasa a papel). Mapas de Claude; Luna hizo el cableado, los tests y las docs. +- **OG con dos especies por fila** (`stagePos`): con cuatro filas de una especie no cabía sobre el nombre de la web; con una quinta especie habría que añadir una fila. +- **`release/checksums.txt` en el último commit del PR** (como en la 1.0.0): antes de publicar, el paso `bin` recibe 404 y compila; tras publicar, usa el binario. La rama `v1.1-tras-publicar` lo borra de `main` junto con los avisos. + ## Siguiente paso +-7. v1.1.0: fusionar el PR de `v1.1-2026-10-31`; publicar la release `v1.1.0` sobre el SHA del merge; esperar a que «Release» suba los 6 binarios y `checksums.txt`; mover `v1`; probar el perfil; PR de `v1.1-tras-publicar`. Ver `PLAN.md`. -6. v1.0.0: fusionar el PR de `v1-2026-10-26`; publicar la release `v1.0.0` desde ese commit y mover `v1` (órdenes en el README); comprobar que «Release» sube 6 binarios y `checksums.txt`; abrir y fusionar el PR de `v1-tras-publicar`. Ver `PLAN.md`. -5. Fase 3: fusionar el PR de `fase3-2026-10-25`; tras el despliegue, abrir la web con la consola (Chromium) y comprobar 0 violaciones de CSP/Trusted Types al usar la demo, el generador y el selector de tema; después, la release `v0.8.0` (notas en `CHANGELOG.md`) y mover `v1`. Opcional: activar el binario precompilado (`docs/DESARROLLO.md`). -4. v0.8.0: fusionar el PR de `mejoras-2026-10-24`; tras el despliegue, abrir la web con la consola y comprobar que la CSP no bloquea nada (demo, generador, descarga); crear la release `v0.8.0` y mover `v1`; activar «Private vulnerability reporting». Ver `PLAN.md`. @@ -234,3 +242,4 @@ Pulido de v0.4.0 (rama `agent/builder/v0.4-pulido`, un PR contra `main`, tras la - 2026-10-24 (tarde) Claude Code · Opus 5.5 (jefe) con OpenCode (GPT-5.6 Luna) (fase2-2026-10-24): generador del workflow en la web (`install.js`, paridad con `generate.Install`), `commitling workflow`, errores de la API accionables y anotación `::error`, push con aviso de permisos, `::add-mask::` del token, actionlint en el CI, CSS en `style.css` (CSP `style-src 'self'`). - 2026-10-26 Claude Code · Opus 5.5 (jefe) con OpenCode (GPT-5.6 Luna) (fase3-2026-10-25): especie cactus (mapas rehechos por Claude), selector de tema (Luna, revisado y corregido por Claude), Trusted Types, acciones por SHA, binario precompilado preparado (paso `bin`, `build-release.sh`, `release.yml`, job `binario`), OG para tres especies, docs. - 2026-10-26 Claude Code · Opus 5.5 (jefe) con OpenCode (Gemini 3 Flash) (v1-2026-10-26): versión 1.0.0 (CLI, web, docs, CHANGELOG), `release/checksums.txt` de la 1.0.0, jobs `action` y `binario` independientes del estado de la release, docs sin promesas falsas (Marketplace, «`v1` aún no existe»), rama `v1-tras-publicar`; notas de la release redactadas por Gemini y revisadas por Claude. +- 2026-10-31 Claude Code · Opus 5.5 (jefe) con OpenCode (GPT-5.6 Luna) (v1.1-2026-10-31): cuarta especie calabaza (mapas de Claude; cableado, tests y docs de Luna, revisados), OG en dos columnas, versión 1.1.0, `release/checksums.txt` de la 1.1.0 y rama `v1.1-tras-publicar`. diff --git a/PLAN.md b/PLAN.md index 92c69b6..d028844 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1,6 +1,6 @@ # PLAN.md · commitling -Hoja de ruta priorizada. Fechada el 24/10/2026 y actualizada el 26/10/2026 (versión 1.0.0). Las etiquetas, releases y mover `v1` los hace Alberto (o el orquestador en su nombre). +Hoja de ruta priorizada. Fechada el 24/10/2026 y actualizada el 31/10/2026 (versión 1.1.0). Las etiquetas, releases y mover `v1` los hace Alberto (o el orquestador en su nombre). ## Hecho: versión 1.0.0 (26/10/2026, de v0.7.0 a v1.0.0; la 0.8.0 nunca se publicó) - **Fase 1 (24/10, «Blindada»)**: CSP estricta en la web y sin scripts en línea, CI con job «seguridad» (govulncheck, staticcheck, gitleaks), permisos mínimos, dependabot, `SECURITY.md`, `go 1.27.3` (la Action compilaba con Go 1.27.0, con 11 vulnerabilidades), escritura atómica del SVG, README para personas usuarias y `docs/DESARROLLO.md`. @@ -9,7 +9,12 @@ Hoja de ruta priorizada. Fechada el 24/10/2026 y actualizada el 26/10/2026 (vers - **Fase 4 (26/10)**: README, `docs/USO.md`, CHANGELOG y web sin contradicciones. - **Fase 5 (26/10, rama `v1-2026-10-26`)**: versión 1.0.0 en la CLI, la web y la documentación; CHANGELOG `[1.0.0]`; `release/checksums.txt` de la 1.0.0 (binario precompilado activo al publicar); el job `action` del CI fuerza la ruta de `go run` y el job `binario` prueba también la release real; fuera la falsa promesa del Marketplace y el aviso obsoleto de «`v1` aún no existe» en `docs/USO.md`. +## Hecho: versión 1.1.0 (31/10/2026, rama `v1.1-2026-10-31`) +- **Cuarta especie: la calabaza** (`species: pumpkin`), de la lista «Después de la 1.0»: Pepita, Plántula, Calabacita, Calabaza y Calabaza gigante, en la Action, la CLI, la galería, los generadores de la web y la imagen OG (dos especies por fila). Musgo, hongo y cactus byte a byte iguales. +- Mantenimiento: Go 1.27.3 sigue siendo el último parche (sin cambios en `go.mod`), govulncheck limpio, 0 alertas y 0 PRs de Dependabot. + ## Siguiente (por orden) +0. **Orquestador/Alberto (1.1.0)**: fusionar el PR de `v1.1-2026-10-31`, publicar `v1.1.0` sobre el SHA del merge, esperar a que «Release» suba los binarios, mover `v1` y fusionar el PR de `v1.1-tras-publicar` (pasos exactos en el informe de la fase 6). 1. **Orquestador/Alberto**: fusionar el PR de `v1-2026-10-26`, publicar la release `v1.0.0` desde ese commit (título y notas en el informe de la fase 5) y mover `v1` a ella (orden exacta en el README, «Lo que tienes que hacer tú»). Comprobar que el workflow «Release» sube los 6 binarios y `checksums.txt`. 2. ~~**Orquestador**: PR de la rama `v1-tras-publicar`~~ Hecho: avisos de «aún no publicada» fuera, ejemplos `@v1.0.0` y `release/checksums.txt` borrado de `main`. 3. Tras el despliegue: abrir https://bertmarti.github.io/commitling/ con la consola y comprobar 0 violaciones de CSP/Trusted Types (demo, generador con un usuario real, selector de tema). La primera ejecución del perfil de BertMarti tras mover `v1` no debe tener el paso de `setup-go` (usa el binario). @@ -17,6 +22,6 @@ Hoja de ruta priorizada. Fechada el 24/10/2026 y actualizada el 26/10/2026 (vers ## Después de la 1.0 - **Alberto**: publicar la Action en el GitHub Marketplace (hay que aceptar el acuerdo de desarrollador en la web al editar la release: casilla «Publish this Action to the GitHub Marketplace»). `action.yml` ya cumple los requisitos (`TestActionMetadataIsMarketplaceReady`). -- Una cuarta especie (receta en `CONTRIBUTING.md`, al día con lo aprendido en el cactus). +- ~~Una cuarta especie~~ Hecho en la 1.1.0 (calabaza). - Modo «año en píxeles» con la actividad de todo el año (requiere otra fuente de datos: la API de eventos solo da 90 días; probablemente GraphQL con token). - Atestaciones de procedencia de los binarios (`actions/attest-build-provenance`) como capa extra para quien quiera verificarlos a mano; la Action seguiría confiando solo en `release/checksums.txt`. diff --git a/README.md b/README.md index 1bfcbbe..4e3d1b4 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ commitling es una GitHub Action que lee tu actividad pública, calcula tu experi - Crece con tus commits, pull requests, issues y demás actividad pública. - Cambia de ánimo según tu actividad reciente y tu racha. - Desbloquea gorro, bufanda y flor. -- Tiene tema claro u oscuro y tres especies: brote de musgo, hongo o cactus. +- Tiene tema claro u oscuro y cuatro especies: brote de musgo, hongo, cactus o calabaza. ## 🎮 Pruébalo sin instalar nada @@ -103,7 +103,7 @@ Puedes añadir estas entradas dentro de `with:`: | `user` | `${{ github.repository_owner }}` | Usuario cuya actividad pública se dibuja | | `out` | `commitling.svg` | Archivo donde se guarda el SVG | | `theme` | `light` | Usa `light` (papel) o `dark` (tinta) | -| `species` | `moss` | Elige `moss` (brote de musgo), `mushroom` (hongo) o `cactus` | +| `species` | `moss` | Elige `moss` (brote de musgo), `mushroom` (hongo), `cactus` o `pumpkin` (calabaza) | | `size` | `full` | Usa la tarjeta completa o la insignia `compact` de 200x60 | | `token` | `${{ github.token }}` | Token de la API; normalmente basta el del workflow | | `fixture` | vacío | Archivo de eventos para probar sin red | @@ -113,25 +113,28 @@ La salida `path` contiene la ruta absoluta del SVG generado. La Action crea el a ## Especies -Las tres especies son originales y tienen las mismas reglas, fases, ánimos y accesorios. +Las cuatro especies son originales y tienen las mismas reglas, fases, ánimos y accesorios. -| Fase | Desde | Brote de musgo (`moss`) | Hongo (`mushroom`) | Cactus (`cactus`) | -|---|---|---|---|---| -| 1 | 0 XP | Semilla | Espora | Yema | -| 2 | 100 XP | Brote | Botón | Bolita | -| 3 | 400 XP | Retoño | Seta | Cactus | -| 4 | 1.000 XP | Arbusto | Seta grande | Cactus con brazos | -| 5 | 2.500 XP | Árbol ancestral | Corro de setas | Cactus centenario | +| Fase | Desde | Brote de musgo (`moss`) | Hongo (`mushroom`) | Cactus (`cactus`) | Calabaza (`pumpkin`) | +|---|---|---|---|---|---| +| 1 | 0 XP | Semilla | Espora | Yema | Pepita | +| 2 | 100 XP | Brote | Botón | Bolita | Plántula | +| 3 | 400 XP | Retoño | Seta | Cactus | Calabacita | +| 4 | 1.000 XP | Arbusto | Seta grande | Cactus con brazos | Calabaza | +| 5 | 2.500 XP | Árbol ancestral | Corro de setas | Cactus centenario | Calabaza gigante | +
Árbol ancestral radiante (brote de musgo)
moss: árbol ancestral, radiante
Corro de setas radiante (hongo)
mushroom: corro de setas, radiante
Cactus centenario radiante (cactus)
cactus: cactus centenario, radiante
Calabaza gigante radiante (calabaza)
pumpkin: calabaza gigante, radiante
-`species` también acepta `musgo`, `hongo` y `seta`, sin distinguir mayúsculas. +La calabaza llega con la versión 1.1.0. Si al usarla el paso falla con «especie no válida», es que `@v1` aún no apunta a la 1.1.0: vuelve a intentarlo cuando se publique. + +`species` también acepta `musgo`, `hongo`, `seta` y `calabaza`, sin distinguir mayúsculas. ### Tarjeta compacta @@ -151,6 +154,7 @@ Si la añades como un paso más, suma `commitling-compact.svg` al `git add` del Arbusto contento, tarjeta compacta (brote de musgo) Seta grande contenta, tarjeta compacta (hongo) Cactus con brazos contento, tarjeta compacta (cactus) + Calabaza contenta, tarjeta compacta (calabaza) @@ -250,7 +254,7 @@ Sí. Solo cuentan los últimos 90 días, así que la XP y los accesorios pueden
¿Cómo cambio a la seta o al tema oscuro? -Añade `species: mushroom` (o `species: cactus`) o `theme: dark` en `with:`. Para el tema oscuro, genera las dos imágenes y usa el bloque `` de esta página. +Añade `species: mushroom` (o `species: cactus` o `species: pumpkin`) o `theme: dark` en `with:`. Para el tema oscuro, genera las dos imágenes y usa el bloque `` de esta página.
¿La criatura es de algún videojuego? diff --git a/action.yml b/action.yml index 3e42e03..8710b5e 100644 --- a/action.yml +++ b/action.yml @@ -23,7 +23,7 @@ inputs: required: false default: ${{ github.token }} species: - description: Especie de la criatura, moss (brote de musgo, la predeterminada), mushroom (hongo) o cactus. + description: Especie de la criatura, moss (brote de musgo, la predeterminada), mushroom (hongo), cactus o pumpkin (calabaza). required: false default: moss size: diff --git a/cmd/commitling/main.go b/cmd/commitling/main.go index 0fd0117..2af2c98 100644 --- a/cmd/commitling/main.go +++ b/cmd/commitling/main.go @@ -1,9 +1,9 @@ // Command commitling draws a pixel-art creature that grows with your public // GitHub activity. // -// commitling render --user [--theme light|dark] [--species moss|mushroom|cactus] [--size full|compact] [--keep-on-error] [--now RFC3339] --out commitling.svg +// commitling render --user [--theme light|dark] [--species moss|mushroom|cactus|pumpkin] [--size full|compact] [--keep-on-error] [--now RFC3339] --out commitling.svg // commitling render --fixture testdata/events.json --out commitling.svg -// commitling workflow --user [--species moss|mushroom|cactus] [--theme light|dark|both] [--size full|compact] +// commitling workflow --user [--species moss|mushroom|cactus|pumpkin] [--theme light|dark|both] [--size full|compact] // commitling gallery --out site/ // commitling og --out og.png // commitling version @@ -28,14 +28,14 @@ import ( ) // version is the CLI version; it can be overridden with -ldflags "-X main.version=...". -var version = "1.0.0" +var version = "1.1.0" const usage = `commitling: una mascota pixel-art que crece con tus commits. Uso: - commitling render --user [--theme light|dark] [--species moss|mushroom|cactus] [--size full|compact] [--keep-on-error] [--now RFC3339] --out - commitling render --fixture [--user ] [--theme light|dark] [--species moss|mushroom|cactus] [--size full|compact] [--now RFC3339] --out - commitling workflow [--user ] [--species moss|mushroom|cactus] [--theme light|dark|both] [--size full|compact] + commitling render --user [--theme light|dark] [--species moss|mushroom|cactus|pumpkin] [--size full|compact] [--keep-on-error] [--now RFC3339] --out + commitling render --fixture [--user ] [--theme light|dark] [--species moss|mushroom|cactus|pumpkin] [--size full|compact] [--now RFC3339] --out + commitling workflow [--user ] [--species moss|mushroom|cactus|pumpkin] [--theme light|dark|both] [--size full|compact] commitling gallery --out commitling og --out commitling version @@ -112,7 +112,7 @@ func runRender(args []string, stdout, stderr io.Writer) error { user := fs.String("user", "", "usuario de GitHub") fixture := fs.String("fixture", "", "archivo JSON de eventos para trabajar sin red") theme := fs.String("theme", "light", "tema: light o dark") - speciesFlag := fs.String("species", "moss", "especie: moss (brote de musgo), mushroom (hongo) o cactus") + speciesFlag := fs.String("species", "moss", "especie: moss (brote de musgo), mushroom (hongo), cactus o pumpkin (calabaza)") sizeFlag := fs.String("size", "full", "tamaño: full (480x200) o compact (insignia de 200x60)") nowFlag := fs.String("now", "", "fecha de referencia en RFC3339 (por defecto, ahora; con --fixture, el último evento)") out := fs.String("out", "commitling.svg", "archivo SVG de salida (- para la salida estándar)") @@ -255,7 +255,7 @@ func runWorkflow(args []string, stdout, stderr io.Writer) error { fs := flag.NewFlagSet("workflow", flag.ContinueOnError) fs.SetOutput(stderr) user := fs.String("user", "", "usuario de GitHub (por defecto, el dueño del repositorio donde corre el workflow)") - speciesFlag := fs.String("species", "moss", "especie: moss (brote de musgo), mushroom (hongo) o cactus") + speciesFlag := fs.String("species", "moss", "especie: moss (brote de musgo), mushroom (hongo), cactus o pumpkin (calabaza)") theme := fs.String("theme", "light", "tema: light, dark o both (claro y oscuro, según quien mire tu perfil)") sizeFlag := fs.String("size", "full", "tamaño: full (480x200) o compact (insignia de 200x60)") if err := fs.Parse(args); err != nil { diff --git a/cmd/commitling/main_test.go b/cmd/commitling/main_test.go index 3546db7..ee8c80f 100644 --- a/cmd/commitling/main_test.go +++ b/cmd/commitling/main_test.go @@ -94,7 +94,7 @@ func TestMissingFixture(t *testing.T) { func TestVersionAndGallery(t *testing.T) { var stdout, stderr bytes.Buffer - if err := run([]string{"version"}, &stdout, &stderr); err != nil || stdout.String() != "commitling 1.0.0\n" { + if err := run([]string{"version"}, &stdout, &stderr); err != nil || stdout.String() != "commitling 1.1.0\n" { t.Fatalf("version: %v %q", err, stdout.String()) } dir := t.TempDir() @@ -191,7 +191,7 @@ func TestRenderInvalidSpeciesFailsEarlyAndClearly(t *testing.T) { t.Errorf("%q: err = %v, want a usage error", bad, err) continue } - for _, want := range []string{"especie no válida", bad, "moss", "mushroom", "cactus"} { + for _, want := range []string{"especie no válida", bad, "moss", "mushroom", "cactus", "pumpkin"} { if !strings.Contains(err.Error(), want) { t.Errorf("%q: message %q should contain %q", bad, err, want) } @@ -409,7 +409,7 @@ func TestRenderMatchesGenerateRender(t *testing.T) { t.Fatal(err) } now := time.Date(2026, 9, 29, 12, 0, 0, 0, time.UTC) - for _, sp := range []string{"moss", "mushroom", "cactus"} { + for _, sp := range []string{"moss", "mushroom", "cactus", "pumpkin"} { for _, th := range []string{"light", "dark"} { var stdout, stderr bytes.Buffer args := []string{"render", "--fixture", fixture, "--user", "octoexample", "--species", sp, "--theme", th, diff --git a/docs/DESARROLLO.md b/docs/DESARROLLO.md index 60ad627..2f70979 100644 --- a/docs/DESARROLLO.md +++ b/docs/DESARROLLO.md @@ -16,7 +16,7 @@ go run ./cmd/commitling render --fixture testdata/events.json --out out/commitli # Tema oscuro y fecha de referencia fija, a la salida estándar go run ./cmd/commitling render --fixture testdata/events.json --theme dark --now 2026-10-01T12:00:00Z --out - -# Otra especie (moss por defecto, mushroom o cactus) +# Otra especie (moss por defecto, mushroom, cactus o pumpkin) go run ./cmd/commitling render --fixture testdata/events.json --species mushroom --out out/hongo.svg # Si la API tiene una caída transitoria y out/commitling.svg ya existe, lo conserva (aviso, código de salida 0) @@ -31,7 +31,7 @@ go run ./cmd/commitling og --out out/og.png go run ./cmd/commitling version ``` -Con `--fixture` y sin `--now`, se usa la fecha del último evento del fixture. `--species` acepta `moss`, `mushroom` o `cactus` y `--size` acepta `full` o `compact`. +Con `--fixture` y sin `--now`, se usa la fecha del último evento del fixture. `--species` acepta `moss`, `mushroom`, `cactus` o `pumpkin` y `--size` acepta `full` o `compact`. ## Stack diff --git a/docs/USO.md b/docs/USO.md index 937a079..b71c110 100644 --- a/docs/USO.md +++ b/docs/USO.md @@ -28,14 +28,14 @@ Antes de instalar nada puedes ver todas las fases y ánimos en la web del proyec https://bertmarti.github.io/commitling/ -Arriba a la izquierda puedes elegir el **tema de la web** (Sistema, Claro u Oscuro); se recuerda en tu navegador con la clave `commitling:theme` y «Sistema» la borra. Ahí verás las tres especies, cada una con sus 5 fases y los 4 ánimos de cada una (20 tarjetas por especie, en versión clara y oscura), los accesorios, las reglas y las instrucciones de instalación con botones para copiar. También hay una criatura «en vivo» del autor del proyecto, que se actualiza cada día. +Arriba a la izquierda puedes elegir el **tema de la web** (Sistema, Claro u Oscuro); se recuerda en tu navegador con la clave `commitling:theme` y «Sistema» la borra. Ahí verás las cuatro especies, cada una con sus 5 fases y los 4 ánimos de cada una (20 tarjetas por especie, en versión clara y oscura), los accesorios, las reglas y las instrucciones de instalación con botones para copiar. También hay una criatura «en vivo» del autor del proyecto, que se actualiza cada día. ### Generar la tuya en la web (sin instalar nada) Arriba de todo, en la sección **Pruébalo con tu usuario**, puedes ver tu propia criatura al instante: 1. Escribe tu usuario de GitHub (vale con o sin `@`). -2. Elige la especie (brote de musgo, hongo o cactus) y el tema (claro u oscuro). +2. Elige la especie (brote de musgo, hongo, cactus o calabaza) y el tema (claro u oscuro). 3. Pulsa **Dibujar** (o Intro). En unos segundos aparece tu criatura. Si cambias la especie o el tema, se redibuja sola, sin volver a preguntar a GitHub. 4. Con **Descargar SVG** te llevas la imagen (`commitling-.svg`), y debajo tienes el **workflow ya relleno** con tu usuario, la especie y el tema que has elegido, con botón de copiar. Pégalo en `.github/workflows/commitling.yml` de tu repositorio de perfil y sigue los pasos de la sección 3. @@ -151,7 +151,7 @@ Dentro de `with:` puedes ajustar: | `user` | el dueño del repositorio | Usuario de GitHub cuya actividad se dibuja | | `out` | `commitling.svg` | Nombre del archivo de imagen | | `theme` | `light` | `light` (papel) o `dark` (tinta) | -| `species` | `moss` | Especie de la criatura: `moss` (brote de musgo) o `mushroom` (hongo) | +| `species` | `moss` | Especie de la criatura: `moss` (brote de musgo), `mushroom` (hongo), `cactus` o `pumpkin` (calabaza) | | `size` | `full` | Tamaño de la tarjeta: `full` (480x200, la de siempre) o `compact` (insignia de 200x60 para firmas y barras laterales) | | `token` | el token del propio workflow | Suele bastar; no hace falta que lo cambies | | `fixture` | vacío | Archivo de eventos de ejemplo para probar sin conexión (solo para desarrollo) | @@ -159,7 +159,7 @@ Dentro de `with:` puedes ajustar: ### Elegir la especie -Hay tres especies, todas de diseño propio y con las mismas reglas (XP, ánimos y accesorios). Por defecto tu criatura es un **brote de musgo** (`moss`). Si prefieres el **hongo** (`mushroom`), una seta pequeña de sombrero miel, añade `species: mushroom` en `with:` (o `species: cactus` para el **cactus**, un cactus con ojos en su maceta de barro): +Hay cuatro especies, todas de diseño propio y con las mismas reglas (XP, ánimos y accesorios). Por defecto tu criatura es un **brote de musgo** (`moss`). Si prefieres el **hongo** (`mushroom`), una seta pequeña de sombrero miel, añade `species: mushroom` en `with:` (o `species: cactus` para el **cactus**, o `species: pumpkin` para la **calabaza**): ```yaml - uses: BertMarti/commitling@v1 @@ -170,13 +170,15 @@ Hay tres especies, todas de diseño propio y con las mismas reglas (XP, ánimos Solo cambian el dibujo y el nombre de las fases: -| Fase | Desde | Brote de musgo (`moss`) | Hongo (`mushroom`) | Cactus (`cactus`) | -|---|---|---|---|---| -| 1 | 0 XP | Semilla | Espora | Yema | -| 2 | 100 XP | Brote | Botón | Bolita | -| 3 | 400 XP | Retoño | Seta | Cactus | -| 4 | 1.000 XP | Arbusto | Seta grande | Cactus con brazos | -| 5 | 2.500 XP | Árbol ancestral | Corro de setas | Cactus centenario | +| Fase | Desde | Brote de musgo (`moss`) | Hongo (`mushroom`) | Cactus (`cactus`) | Calabaza (`pumpkin`) | +|---|---|---|---|---|---| +| 1 | 0 XP | Semilla | Espora | Yema | Pepita | +| 2 | 100 XP | Brote | Botón | Bolita | Plántula | +| 3 | 400 XP | Retoño | Seta | Cactus | Calabacita | +| 4 | 1.000 XP | Arbusto | Seta grande | Cactus con brazos | Calabaza | +| 5 | 2.500 XP | Árbol ancestral | Corro de setas | Cactus centenario | Calabaza gigante | + +La calabaza llega con la versión 1.1.0. Si al usarla el paso falla con «especie no válida», es que `@v1` aún no apunta a la 1.1.0: vuelve a intentarlo cuando se publique. Valores que acepta `species` (sin distinguir mayúsculas ni espacios alrededor): @@ -185,8 +187,9 @@ Valores que acepta `species` (sin distinguir mayúsculas ni espacios alrededor): | Brote de musgo | `moss`, `musgo`, `brote de musgo` (y vacío, que es la predeterminada) | | Hongo | `mushroom`, `hongo`, `seta`, `setas` | | Cactus | `cactus` | +| Calabaza | `pumpkin`, `calabaza` | -Cualquier otro valor (por ejemplo `dragon`) hace fallar el paso a propósito, con el mensaje `especie no válida "dragon" (usa moss, mushroom o cactus)`, para que un error tipográfico no pase desapercibido, y no escribe ningún archivo. Puedes ver todas las fases de las tres especies en la [galería](https://bertmarti.github.io/commitling/#galeria). +Cualquier otro valor (por ejemplo `dragon`) hace fallar el paso a propósito, con el mensaje `especie no válida "dragon" (usa moss, mushroom, cactus o pumpkin)`, para que un error tipográfico no pase desapercibido, y no escribe ningún archivo. Puedes ver todas las fases de las cuatro especies en la [galería](https://bertmarti.github.io/commitling/#galeria). ¿Varias a la vez? Sí: usa la Action dos veces con `out` distinto (por ejemplo `commitling.svg` y `commitling-hongo.svg`, cada uno con su `species`), suma el segundo archivo al `git add` y enseña la imagen que quieras en el README. @@ -201,7 +204,7 @@ Por defecto la tarjeta mide 480x200 (`size: full`). Si la quieres más pequeña, size: compact ``` -Verás la compacta en las cinco fases de las tres especies en la [galería](https://bertmarti.github.io/commitling/#compacta), y en el generador de la web (sección «Pruébalo con tu usuario») el selector **Tamaño** te la enseña con tu usuario y rellena el workflow con `size: compact`. +Verás la compacta en las cinco fases de las cuatro especies en la [galería](https://bertmarti.github.io/commitling/#compacta), y en el generador de la web (sección «Pruébalo con tu usuario») el selector **Tamaño** te la enseña con tu usuario y rellena el workflow con `size: compact`. Sin `size`, o con `size: full`, el dibujo es exactamente el de siempre, byte a byte. Cualquier otro valor (por ejemplo `grande`) hace fallar el paso a propósito, con el mensaje `tamaño no válido "grande" (usa full o compact)`. Puedes usar la Action dos veces (una con cada tamaño) con `out` distinto y suma los dos archivos al `git add`. @@ -284,7 +287,7 @@ Ejemplo: en los últimos 90 días has hecho 20 commits (200 XP), abierto 2 pull | Arbusto | 1.000 XP | | Árbol ancestral | 2.500 XP | -En el hongo, las cinco fases se llaman Espora, Botón, Seta, Seta grande y Corro de setas, y en el cactus Yema, Bolita, Cactus, Cactus con brazos y Cactus centenario, con las mismas XP. Con 285 XP estarías en **Brote** (fase 2 de 5) y te faltarían 115 XP para ser Retoño. La tarjeta muestra la XP total, la meta siguiente y una barra de progreso (24 casillas) hasta la próxima fase; en el Árbol ancestral la barra va siempre llena. +En el hongo, las cinco fases se llaman Espora, Botón, Seta, Seta grande y Corro de setas; en el cactus, Yema, Bolita, Cactus, Cactus con brazos y Cactus centenario; y en la calabaza, Pepita, Plántula, Calabacita, Calabaza y Calabaza gigante, con las mismas XP. Con 285 XP estarías en **Brote** (fase 2 de 5) y te faltarían 115 XP para ser Retoño. La tarjeta muestra la XP total, la meta siguiente y una barra de progreso (24 casillas) hasta la próxima fase; en el Árbol ancestral la barra va siempre llena. ### Ánimos @@ -378,7 +381,7 @@ go run ./cmd/commitling version **Conservar el SVG ante una caída de la API (`--keep-on-error`).** Con esta opción, si la API de GitHub sigue fallando tras los reintentos por una causa **transitoria** (error de red, 5xx o límite de peticiones: 429, o 403 con cabeceras de límite) y el archivo de `--out` ya existe y es un SVG completo, commitling lo deja intacto, escribe un aviso («la API de GitHub no responde… se conserva … sin cambios») y termina con código 0. Los fallos **permanentes** siguen siendo un error aunque haya archivo previo: 401 (token inválido o caducado), 404 (usuario inexistente), 422 y 403 sin cabeceras de límite (permisos). Sin archivo previo, con un archivo vacío o cortado, o con `--out -`, el error también se mantiene, y no afecta a otros errores (un `--fixture` que no existe, una especie no válida…). En la Action equivale a la entrada `keep-on-error`, que está activada por defecto. -**La imagen de vista previa (`og`).** Es el PNG de 1200×630 píxeles que muestran Slack, X, LinkedIn o Telegram cuando alguien pega el enlace de la web del proyecto (formato «Open Graph»). Enseña una criatura grande y las cinco fases de las tres especies, en la paleta de la criatura, sin suavizado y en unos 3 KB. `commitling og --out og.png` la dibuja donde le digas (con `--out -` va a la salida estándar); no necesita red ni usuario, y sale siempre igual. Solo te hace falta si mantienes tu propia web: `commitling gallery` ya la genera como `og.png` dentro de la carpeta de salida y las etiquetas `og:image` de la página apuntan a ella. Ten en cuenta que las redes sociales guardan las vistas previas en caché, así que un cambio puede tardar en verse. +**La imagen de vista previa (`og`).** Es el PNG de 1200×630 píxeles que muestran Slack, X, LinkedIn o Telegram cuando alguien pega el enlace de la web del proyecto (formato «Open Graph»). Enseña una criatura grande y las cinco fases de las cuatro especies, en la paleta de la criatura, sin suavizado y en unos 3 KB. `commitling og --out og.png` la dibuja donde le digas (con `--out -` va a la salida estándar); no necesita red ni usuario, y sale siempre igual. Solo te hace falta si mantienes tu propia web: `commitling gallery` ya la genera como `og.png` dentro de la carpeta de salida y las etiquetas `og:image` de la página apuntan a ella. Ten en cuenta que las redes sociales guardan las vistas previas en caché, así que un cambio puede tardar en verse. ## 8. Preguntas frecuentes diff --git a/internal/creature/creature.go b/internal/creature/creature.go index 1c5784b..d894a09 100644 --- a/internal/creature/creature.go +++ b/internal/creature/creature.go @@ -2,10 +2,11 @@ // growth stage, its mood and the accessories it has unlocked, plus the pixel // maps used to draw it. // -// All three species are original designs. The default one is a small moss +// All four species are original designs. The default one is a small moss // sprout with eyes that grows from a honey-coloured seed into an ancient tree; -// the others are a little mushroom that grows from a spore into a fairy ring -// and a cactus with eyes in a clay-coloured pot. +// the others are a little mushroom that grows from a spore into a fairy ring, +// a cactus with eyes in a clay-coloured pot and a garden pumpkin that grows +// from a seed into a giant pumpkin. package creature import ( @@ -23,20 +24,22 @@ const ( MossSprout Species = iota Mushroom Cactus + Pumpkin ) // AllSpecies lists every species, default first. -var AllSpecies = []Species{MossSprout, Mushroom, Cactus} +var AllSpecies = []Species{MossSprout, Mushroom, Cactus, Pumpkin} -var speciesNames = [...]string{"Brote de musgo", "Hongo", "Cactus"} +var speciesNames = [...]string{"Brote de musgo", "Hongo", "Cactus", "Calabaza"} -var speciesSlugs = [...]string{"moss", "mushroom", "cactus"} +var speciesSlugs = [...]string{"moss", "mushroom", "cactus", "pumpkin"} // Names of the stages of each species, in growth order. var speciesStageNames = [...][5]string{ MossSprout: {"Semilla", "Brote", "Retoño", "Arbusto", "Árbol ancestral"}, Mushroom: {"Espora", "Botón", "Seta", "Seta grande", "Corro de setas"}, Cactus: {"Yema", "Bolita", "Cactus", "Cactus con brazos", "Cactus centenario"}, + Pumpkin: {"Pepita", "Plántula", "Calabacita", "Calabaza", "Calabaza gigante"}, } // Name is the Spanish name of the species. diff --git a/internal/creature/creature_test.go b/internal/creature/creature_test.go index 57b0073..b4775e2 100644 --- a/internal/creature/creature_test.go +++ b/internal/creature/creature_test.go @@ -216,7 +216,7 @@ func TestDrawChangesWithMoodAndAccessories(t *testing.T) { } func TestSpeciesNamesAndSlugs(t *testing.T) { - if len(AllSpecies) != 3 || AllSpecies[0] != MossSprout { + if len(AllSpecies) != 4 || AllSpecies[0] != MossSprout { t.Fatalf("AllSpecies = %v, want the default first", AllSpecies) } if (Creature{}).Species != MossSprout { @@ -246,6 +246,9 @@ func TestSpeciesNamesAndSlugs(t *testing.T) { if Cactus.StageName(Seed) != "Yema" || Cactus.StageName(Sprout) != "Bolita" || Cactus.StageName(Sapling) != "Cactus" || Cactus.StageName(Shrub) != "Cactus con brazos" || Cactus.StageName(Ancient) != "Cactus centenario" { t.Error("unexpected cactus stage names") } + if Pumpkin.StageName(Seed) != "Pepita" || Pumpkin.StageName(Sprout) != "Plántula" || Pumpkin.StageName(Sapling) != "Calabacita" || Pumpkin.StageName(Shrub) != "Calabaza" || Pumpkin.StageName(Ancient) != "Calabaza gigante" { + t.Error("unexpected pumpkin stage names") + } } func TestSpeciesByName(t *testing.T) { @@ -253,6 +256,7 @@ func TestSpeciesByName(t *testing.T) { "": MossSprout, " ": MossSprout, "moss": MossSprout, "MOSS": MossSprout, "musgo": MossSprout, "Brote de musgo": MossSprout, "mushroom": Mushroom, " Mushroom ": Mushroom, "hongo": Mushroom, "seta": Mushroom, "cactus": Cactus, " CACTUS ": Cactus, + "pumpkin": Pumpkin, " PUMPKIN ": Pumpkin, "calabaza": Pumpkin, "Calabaza": Pumpkin, } for in, want := range ok { got, found := SpeciesByName(in) @@ -336,3 +340,14 @@ func TestCactusMapsAreNotTheOthers(t *testing.T) { } } } + +// The pumpkin is its own design: it must not reuse another species' maps. +func TestPumpkinMapsAreNotTheOthers(t *testing.T) { + for _, st := range Stages { + for name, other := range map[string]stageArt{"moss": mossArt[st], "mushroom": mushroomArt[st], "cactus": cactusArt[st]} { + if pumpkinArt[st].body == other.body { + t.Errorf("stage %d: pumpkin body equals the %s body", st, name) + } + } + } +} diff --git a/internal/creature/pumpkin.go b/internal/creature/pumpkin.go new file mode 100644 index 0000000..9200cdd --- /dev/null +++ b/internal/creature/pumpkin.go @@ -0,0 +1,137 @@ +package creature + +// Pixel maps of the pumpkin. K ink, P paper, G moss, H honey, R accent, +// . transparent. An original design: a garden pumpkin with eyes. It grows +// from a cream seed with a curl of a shoot (the face is on the seed) into a +// seedling still wearing its seed coat between two round leaves, a small +// striped green pumpkin, a ribbed orange pumpkin with a leaf and a tendril, +// and a giant pumpkin that fills the whole map. The ribs are accent pixels and +// curve around the face, which always sits on a fill; the hat sits on the +// stem, the flower on a leaf or a tendril and the scarf wraps the lower part. +var pumpkinArt = [...]stageArt{ + Seed: { + body: [Size]string{ + "................", + "................", + "................", + "................", + ".........KKK....", + "........KGGGK...", + "........KGKK....", + "......KKKGK.....", + ".....KHHHHK.....", + "....KHPPPPHK....", + "...KHPPPPPPHK...", + "...KHPPPPPPHK...", + "...KHPPPPPPHK...", + "...KHPPPPPPHK...", + "....KHHHHHHK....", + ".....KKKKKK.....", + }, + face: Point{4, 10}, + hat: Point{5, 4}, + scarfRow: 14, + scarfTail: Point{9, 15}, + flower: Point{11, 6}, + }, + Sprout: { + body: [Size]string{ + "................", + "................", + "................", + "......KKKK......", + "..KKKKHPPHKKKK..", + ".KGGGGKHHKGGGGK.", + "KGGGGGGKKGGGGGGK", + "KGGGGGGGGGGGGGGK", + "KGGGGGGGGGGGGGGK", + "KGGGGGGGGGGGGGGK", + ".KGGGGGGGGGGGGK.", + "..KKKKKGGKKKKK..", + "......KGGK......", + ".....KKGGKK.....", + "...KKHHHHHHKK...", + "..KKKKKKKKKKKK..", + }, + face: Point{4, 7}, + hat: Point{5, 0}, + scarfRow: 11, + scarfTail: Point{9, 12}, + flower: Point{12, 3}, + }, + Sapling: { + body: [Size]string{ + "................", + "................", + "................", + "................", + ".......KK.......", + "......KGGKKKK...", + "......KGGKGGGK..", + "...KKKKGGKKKK...", + "..KPGGGKKGGGPK..", + "..KPGGGGGGGGPK..", + "..KPGGGGGGGGPK..", + "..KPGGGGGGGGPK..", + "..KPGGGGGGGGPK..", + "...KPGGGGGGPK...", + "....KKKKKKKK....", + "................", + }, + face: Point{4, 9}, + hat: Point{5, 3}, + scarfRow: 13, + scarfTail: Point{9, 14}, + flower: Point{11, 4}, + }, + Shrub: { + body: [Size]string{ + "................", + "................", + ".......KK.......", + "..GG..KGGK.KKK..", + ".G..G.KGGKKGGGK.", + ".G.G..KGGKGGGK..", + "..GKKKKGGKKKK...", + "..KHRHHKKHHRHK..", + ".KHRHHHHHHHHRHK.", + ".KHRHHHHHHHHRHK.", + ".KHRHHHHHHHHRHK.", + ".KHRHHHHHHHHRHK.", + ".KHRHHHHHHHHRHK.", + ".KHRHHHHHHHHRHK.", + "..KHRHHHHHHRHK..", + "...KKKKKKKKKK...", + }, + face: Point{4, 9}, + hat: Point{5, 2}, + scarfRow: 13, + scarfTail: Point{9, 14}, + flower: Point{11, 3}, + }, + Ancient: { + body: [Size]string{ + "......KK........", + "..KKKKGGK...GG..", + ".KGGGKGGK..G..G.", + "KGGGGKGGK..G.G..", + ".KKKKKGGKKKKG...", + ".KHHHRHKKHRHHHK.", + "KHPHRHHHHHHRHHHK", + "KHPRHHHHHHHHRHHK", + "KHHRHHHHHHHHRHHK", + "KHHRHHHHHHHHRHHK", + "KHHRHHHHHHHHRHHK", + "KHHRHHHHHHHHRHHK", + "KHHRHHHHHHHHRHHK", + "KHHHRHHHHHHRHHHK", + ".KHHHRHHHHRHHHK.", + "..KKKKKKKKKKKK..", + }, + face: Point{4, 8}, + hat: Point{5, 0}, + scarfRow: 13, + scarfTail: Point{9, 14}, + flower: Point{12, 1}, + }, +} diff --git a/internal/creature/sprites.go b/internal/creature/sprites.go index 43b520b..ed8efe4 100644 --- a/internal/creature/sprites.go +++ b/internal/creature/sprites.go @@ -98,6 +98,8 @@ func artFor(sp Species, st Stage) stageArt { return mushroomArt[st] case Cactus: return cactusArt[st] + case Pumpkin: + return pumpkinArt[st] default: return mossArt[st] } diff --git a/internal/gallery/gallery.go b/internal/gallery/gallery.go index 4cc0190..971f772 100644 --- a/internal/gallery/gallery.go +++ b/internal/gallery/gallery.go @@ -205,7 +205,8 @@ func Build(dir, version string) (int, error) { intros := map[creature.Species]string{ creature.MossSprout: "La especie predeterminada: un brote de musgo con ojos que acaba siendo un árbol ancestral.", creature.Mushroom: "Una seta pequeña con ojos que empieza siendo una espora y acaba en un corro de setas. Se elige con species: mushroom.", - creature.Cactus: "Un cactus pequeño en su maceta que empieza siendo una semilla y acaba en un cactus centenario. Se elige con species: cactus.", + creature.Cactus: "Un cactus pequeño en su maceta que empieza siendo una yema y acaba en un cactus centenario. Se elige con species: cactus.", + creature.Pumpkin: "Una calabaza de huerto con ojos que empieza siendo una pepita y acaba en una calabaza gigante. Se elige con species: pumpkin.", } for _, sp := range creature.AllSpecies { // The default species keeps the file names of the first release. diff --git a/internal/gallery/gallery_test.go b/internal/gallery/gallery_test.go index 9ac58f6..083ad0b 100644 --- a/internal/gallery/gallery_test.go +++ b/internal/gallery/gallery_test.go @@ -40,7 +40,7 @@ func TestBuild(t *testing.T) { // dark, plus favicon, og.png, generator.js, page.js, theme.js, install.js, style.css and index (the hero of the header is // one of the accessory cards), plus the compact badges: per species, one // per stage, light and dark. - if want := 3*(20+4)*2 + 8 + 3*5*2; n != want { + if want := 4*(20+4)*2 + 8 + 4*5*2; n != want { t.Fatalf("Build wrote %d files, want %d", n, want) } @@ -518,6 +518,18 @@ func TestPageDoesNotOverflowOnPhones(t *testing.T) { if got := rules.propIn(t, phone, "td:first-child", "white-space"); got != "normal" { t.Errorf("on a phone the first column of a table must be able to wrap, white-space = %q", got) } + // The stages table lists the names of every species in one cell + // («Semilla / Espora / Yema / Pepita»): with nowrap it pushed the page + // wider between ~720 and ~1350 px. The names wrap; «desde 2.500 XP» does not. + if got := rules.prop(".fases td:first-child", "white-space"); got != "normal" { + t.Errorf("the names of the stages table must wrap at every width, white-space = %q", got) + } + if got := rules.prop(".fases td:last-child", "white-space"); got != "nowrap" { + t.Errorf("the XP of the stages table must stay on one line, white-space = %q", got) + } + if !strings.Contains(readRepoFile(t, "internal/gallery/index.html.tmpl"), ``+"\n ") { + t.Error(`the stages table must have class="fases"`) + } for _, r := range rules { last := r.sel[strings.LastIndex(r.sel, " ")+1:] @@ -585,6 +597,43 @@ func TestFullCardsAreByteIdenticalToV050(t *testing.T) { } } +// A new species must not change a single byte of what v1.0.0 drew for the +// species it already had: every card (full and compact, light and dark) of +// the moss sprout, the mushroom and the cactus, and favicon.svg. +// testdata/golden/v1.0.0-sha256.txt holds their SHA-256, taken from the +// v1.0.0 gallery. +func TestCardsAreByteIdenticalToV100(t *testing.T) { + list, err := os.ReadFile("../../testdata/golden/v1.0.0-sha256.txt") + if err != nil { + t.Fatal(err) + } + dir := t.TempDir() + if _, err := Build(dir, "test"); err != nil { + t.Fatal(err) + } + n := 0 + for _, line := range strings.Split(strings.TrimSpace(strings.ReplaceAll(string(list), "\r\n", "\n")), "\n") { + sum, name, ok := strings.Cut(line, " ") + if !ok { + t.Fatalf("bad line in the golden list: %q", line) + } + data, err := os.ReadFile(filepath.Join(dir, filepath.FromSlash(name))) + if err != nil { + t.Errorf("%s: %v", name, err) + continue + } + if got := fmt.Sprintf("%x", sha256.Sum256(data)); got != sum { + t.Errorf("%s changed: sha256 %s, v1.0.0 had %s", name, got, sum) + } + n++ + } + // Three species × (20 stage cards + 4 accessory cards + 5 compact + // badges) × light and dark, plus favicon.svg. + if want := 3*(20+4+5)*2 + 1; n != want { + t.Errorf("the golden list has %d files, want %d", n, want) + } +} + // The gallery shows the compact badge for every stage of both species, light // and dark, at its real size, with its own text alternative. func TestGalleryShowsTheCompactBadge(t *testing.T) { diff --git a/internal/gallery/index.html.tmpl b/internal/gallery/index.html.tmpl index af7e6cb..4ccfcb4 100644 --- a/internal/gallery/index.html.tmpl +++ b/internal/gallery/index.html.tmpl @@ -17,7 +17,7 @@ - + @@ -204,7 +204,7 @@ {{range .XP}}{{end}}
Fases Experiencia
{{.Name}}{{.When}}
- +
{{range .Stages}}{{end}}
Fases
{{.Name}}{{.When}}
@@ -235,6 +235,7 @@
{{range $i, $c := .GenSpecies}}
{{end}}
+

La calabaza llega con la versión 1.1.0: si tu workflow falla con «especie no válida», es que @v1 aún no la incluye; vuelve a ejecutarlo cuando se publique.

Tema diff --git a/internal/gallery/install.js b/internal/gallery/install.js index ba3c4f7..a75787a 100644 --- a/internal/gallery/install.js +++ b/internal/gallery/install.js @@ -59,7 +59,7 @@ // Only the canonical values of the radios are accepted (as Go checks them // with SpeciesByName, ThemeByName and SizeByName); anything else is the // default, so nothing unexpected can reach the YAML. - var ALLOWED = { species: ['moss', 'mushroom', 'cactus'], theme: ['light', 'dark', 'both'], size: ['full', 'compact'] }; + var ALLOWED = { species: ['moss', 'mushroom', 'cactus', 'pumpkin'], theme: ['light', 'dark', 'both'], size: ['full', 'compact'] }; function option(kind, value) { var v = typeof value === 'string' ? value.trim().toLowerCase() : ''; return ALLOWED[kind].indexOf(v) >= 0 ? v : ALLOWED[kind][0]; diff --git a/internal/gallery/install_test.go b/internal/gallery/install_test.go index ca76b09..2381169 100644 --- a/internal/gallery/install_test.go +++ b/internal/gallery/install_test.go @@ -59,8 +59,8 @@ func TestInstallJSMatchesGo(t *testing.T) { if err := json.Unmarshal(out, &cases); err != nil { t.Fatalf("--dump is not JSON: %v\n%.300s", err, out) } - if len(cases) != 4*3*3*2 { - t.Fatalf("%d cases, want 72", len(cases)) + if len(cases) != 4*4*3*2 { + t.Fatalf("%d cases, want 96", len(cases)) } for _, c := range cases { want, err := generate.Install(generate.Options{User: c.User, Species: c.Species, Theme: c.Theme, Size: c.Size}) diff --git a/internal/gallery/style.css b/internal/gallery/style.css index 61e449a..1a3eefa 100644 --- a/internal/gallery/style.css +++ b/internal/gallery/style.css @@ -102,6 +102,8 @@ td:first-child{padding-right:16px;white-space:nowrap} td:last-child{color:var(--muted)} .xp td:first-child{white-space:normal} .xp td:last-child{white-space:nowrap;text-align:right} +.fases td:first-child{white-space:normal} +.fases td:last-child{white-space:nowrap} .code{position:relative;margin:10px 0 22px} pre{margin:0;padding:18px 20px;background:var(--code);overflow-x:auto;font-size:13px;line-height:1.6;border-left:4px solid var(--moss)} code{font-family:inherit} diff --git a/internal/gallery/testdata/install.test.js b/internal/gallery/testdata/install.test.js index 4b0c89d..aba60d3 100644 --- a/internal/gallery/testdata/install.test.js +++ b/internal/gallery/testdata/install.test.js @@ -18,7 +18,7 @@ const api = sandbox.window.commitlingInstall; assert.ok(api); const users = ['', 'octocat', '007', 'a-b-9']; -const species = ['moss', 'mushroom', 'cactus']; +const species = ['moss', 'mushroom', 'cactus', 'pumpkin']; const themes = ['light', 'dark', 'both']; const sizes = ['full', 'compact']; diff --git a/internal/generate/generate.go b/internal/generate/generate.go index 2f5dd5d..f55f8a7 100644 --- a/internal/generate/generate.go +++ b/internal/generate/generate.go @@ -21,7 +21,7 @@ import ( // events, the moss sprout, the light theme, the full card and "now". type Options struct { User string // GitHub login; empty takes the most common actor of the events - Species string // moss, mushroom or cactus (or an alias); empty is moss + Species string // moss, mushroom, cactus or pumpkin (or an alias); empty is moss Theme string // light or dark; empty is light Size string // full or compact; empty is full Now time.Time // reference date; zero is the current time @@ -41,7 +41,7 @@ func (o Options) Check() error { return errors.New("tema no válido " + strconv.Quote(o.Theme) + " (usa light o dark)") } if _, ok := creature.SpeciesByName(o.speciesName()); !ok { - return errors.New("especie no válida " + strconv.Quote(o.Species) + " (usa moss, mushroom o cactus)") + return errors.New("especie no válida " + strconv.Quote(o.Species) + " (usa moss, mushroom, cactus o pumpkin)") } if _, ok := render.SizeByName(o.Size); !ok { return errors.New("tamaño no válido " + strconv.Quote(o.Size) + " (usa full o compact)") diff --git a/internal/generate/generate_test.go b/internal/generate/generate_test.go index 67cc775..2146ab1 100644 --- a/internal/generate/generate_test.go +++ b/internal/generate/generate_test.go @@ -186,7 +186,7 @@ func TestCheckMessagesAreTheOnesOfTheCLI(t *testing.T) { if err := (Options{Theme: "sepia"}).Check(); err == nil || err.Error() != `tema no válido "sepia" (usa light o dark)` { t.Errorf("theme: %v", err) } - if err := (Options{Species: "dragon"}).Check(); err == nil || err.Error() != `especie no válida "dragon" (usa moss, mushroom o cactus)` { + if err := (Options{Species: "dragon"}).Check(); err == nil || err.Error() != `especie no válida "dragon" (usa moss, mushroom, cactus o pumpkin)` { t.Errorf("species: %v", err) } if err := (Options{User: "a b"}).Check(); err == nil || err.Error() != `nombre de usuario no válido: "a b" (solo letras, números y guiones, sin espacios ni guion al principio)` { diff --git a/internal/og/og.go b/internal/og/og.go index c712ff0..be3e496 100644 --- a/internal/og/og.go +++ b/internal/og/og.go @@ -37,15 +37,23 @@ const ( heroX = 96 heroY = 150 textX = 496 // left edge of the text column - stageY = 380 // top of the first row of small stages (three species fit) + stageY = 392 // top of the first row of small stages stageScale = 3 - stageGap = 20 - stageRowH = creature.Size*stageScale + 8 // distance between rows of stages - siteY = 556 + stageGap = 8 // between the stages of a species + stageW = 5*creature.Size*stageScale + 4*stageGap // the five stages of a species + speciesGap = 32 // between the two species of a row + stageRowH = creature.Size*stageScale + 12 // distance between rows of species + siteY = 530 ) -// stageRowY is the top of the row of stages of the i-th species. -func stageRowY(i int) int { return stageY + i*stageRowH } +// stagePos is the top-left corner of the small stage st of the i-th species. +// The species are laid out two per row (four species fit in two rows), each +// one with its five stages from the youngest to the oldest. +func stagePos(i int, st creature.Stage) (x, y int) { + x = textX + (i%2)*(stageW+speciesGap) + int(st)*(creature.Size*stageScale+stageGap) + y = stageY + (i/2)*stageRowH + return x, y +} // Palette indexes. const ( @@ -153,13 +161,11 @@ func Image(hero creature.Creature) *image.Paletted { c.text(textX, 282, Tagline1, 4, iInk) c.text(textX, 326, Tagline2, 4, iInk) - // The five stages of each species, one row per species, from the - // youngest to the oldest. + // The five stages of each species, from the youngest to the oldest. for i, sp := range creature.AllSpecies { - x := textX for _, st := range creature.Stages { - c.sprite(x, stageRowY(i), creature.Creature{Species: sp, Stage: st, Mood: creature.Happy}, stageScale) - x += creature.Size*stageScale + stageGap + x, y := stagePos(i, st) + c.sprite(x, y, creature.Creature{Species: sp, Stage: st, Mood: creature.Happy}, stageScale) } } diff --git a/internal/og/og_test.go b/internal/og/og_test.go index 9749892..19bd110 100644 --- a/internal/og/og_test.go +++ b/internal/og/og_test.go @@ -112,18 +112,18 @@ func TestSpriteIsScaledInWholeBlocks(t *testing.T) { } } -// Every stage of every species appears, small, in the rows under the tagline. +// Every stage of every species appears, small, under the tagline. func TestStageRowsShowAllStages(t *testing.T) { img := Image(Hero) for i, sp := range creature.AllSpecies { - x := textX for _, st := range creature.Stages { + x, y := stagePos(i, st) sprite := creature.Draw(creature.Creature{Species: sp, Stage: st, Mood: creature.Happy}) ink := 0 for gy := 0; gy < creature.Size; gy++ { for gx := 0; gx < creature.Size; gx++ { if sprite.Outline[gy][gx] == creature.PInk { - if img.ColorIndexAt(x+gx*stageScale, stageRowY(i)+gy*stageScale) != iInk { + if img.ColorIndexAt(x+gx*stageScale, y+gy*stageScale) != iInk { t.Fatalf("%s/%s: outline pixel (%d,%d) is not ink", sp.Name(), sp.StageName(st), gx, gy) } ink++ @@ -133,20 +133,36 @@ func TestStageRowsShowAllStages(t *testing.T) { if ink == 0 { t.Fatalf("%s/%s has no outline?", sp.Name(), sp.StageName(st)) } - x += creature.Size*stageScale + stageGap } } - // The rows fit above the site name and inside the frame. - last := stageRowY(len(creature.AllSpecies)-1) + creature.Size*stageScale - if last >= siteY { - t.Errorf("stage rows end at y=%d, overlapping the site name at y=%d", last, siteY) + // The stages fit under the tagline, above the site name and inside the + // frame, and no two of them overlap. + type box struct{ x, y int } + seen := map[box]bool{} + const side = creature.Size * stageScale + for i := range creature.AllSpecies { + for _, st := range creature.Stages { + x, y := stagePos(i, st) + if y < 326+glyphH*4 { + t.Errorf("species %d stage %d starts at y=%d, over the tagline", i, st, y) + } + if y+side >= siteY { + t.Errorf("species %d stage %d ends at y=%d, overlapping the site name at y=%d", i, st, y+side, siteY) + } + if x+side > Width-24-6-16 { + t.Errorf("species %d stage %d reaches x=%d, too close to the frame", i, st, x+side) + } + for b := range seen { + if x < b.x+side && b.x < x+side && y < b.y+side && b.y < y+side { + t.Errorf("species %d stage %d at (%d,%d) overlaps another stage at (%d,%d)", i, st, x, y, b.x, b.y) + } + } + seen[box{x, y}] = true + } } if siteY+glyphH*3 > Height-24-6 { t.Error("the site name touches the frame") } - if end := textX + 5*(creature.Size*stageScale+stageGap) - stageGap; end > Width-24-6-16 { - t.Errorf("stage rows reach x=%d, too close to the frame", end) - } } func TestTextIsDrawnInsideTheCard(t *testing.T) { diff --git a/release/checksums.txt b/release/checksums.txt new file mode 100644 index 0000000..83704bd --- /dev/null +++ b/release/checksums.txt @@ -0,0 +1,6 @@ +fe32071498096b3ee29fbc3fab6446124211743ad93559a0fca84b838565734e commitling_1.1.0_darwin_amd64 +6e7dc532f56114ec83c2a88966926fd5aaf0cf665b42f77e590796ee2d26f477 commitling_1.1.0_darwin_arm64 +79f1abc64abdc9ecefbc5df8586e2cee45e2c072e6af9730dd62c93a6d265b94 commitling_1.1.0_linux_amd64 +4c7b3fc684cfe5cc2104e93f66e0addc6a5515811400c60546b0bce6f922c473 commitling_1.1.0_linux_arm64 +60c42532e6e339aa11f7b433f1894747e5de7340aeceb690f5245e7cb352cf5f commitling_1.1.0_windows_amd64.exe +8583660aebfcd449bb11dace7951ff21c61a50f4c47a3bd71905652bd41c9358 commitling_1.1.0_windows_arm64.exe diff --git a/testdata/golden/v1.0.0-sha256.txt b/testdata/golden/v1.0.0-sha256.txt new file mode 100644 index 0000000..7282218 --- /dev/null +++ b/testdata/golden/v1.0.0-sha256.txt @@ -0,0 +1,175 @@ +d4a4e4dfad7463349c89becac6c03bf787bea0624a2939b89039d70fd621c5f0 favicon.svg +7443d63835d5a3bace97beb797aeecbb4b81ce0fb725726e30aa539e567b5205 svg/acc-all-dark.svg +a681343527f70b747a70cec75a8b247f7232519e6f09a5267529cc680f3b5dca svg/acc-all.svg +d700bf4951b77848ece97ee5361647a551c51a69cf4553f9b9048268dc5f2e41 svg/acc-flower-dark.svg +73b1636bd2bebfdd5599ed49b8248413cb8e2f000239a8390864e1c5b9de8b55 svg/acc-flower.svg +96cdc3b825324b08d0a65e78392effb65e4206127e315838311eaba244c1b568 svg/acc-hat-dark.svg +a8b157ead319cac139fe2e7faa4b9789c1636a92e5112457446f9f766db3f7c5 svg/acc-hat.svg +f885fbf90dcfb8f991ffd89898e4530528b0d95984e36226e8b5c37ddb2d5ba2 svg/acc-scarf-dark.svg +042f6d31987d86ebaccce026265397ad5751c22dd76d31cde3b3b6b50dbbf9eb svg/acc-scarf.svg +d4d36f96cdd691e31b7004c19559dfcadb15d30a443ec28bf021caf6bd3be5bd svg/ancient-bored-dark.svg +07637d7944018ef1d4234d5dff01c1964d6cb78aa203cfb5bcfb41ae3744292c svg/ancient-bored.svg +cafe1ba0f8bee6eac2be9cd8e52897d87bfd0d54dc22c4bcfead18ffa61adaaa svg/ancient-happy-dark.svg +dc268195b05d9342fc575fe5d3514b8167f3e0fa29640c381d3074f6368eb924 svg/ancient-happy.svg +0b199c2329fd7b2f0e9e08b6c0e49afb52e94c2dc41dd78b8accca3302a40c26 svg/ancient-radiant-dark.svg +05967f712f3234949ced0663e843f6fe5b124e07be49e5a05d7698c317b48795 svg/ancient-radiant.svg +86612785d8fd2b7711aec65a8b8ec756d01aa9655149e47d934c966078561f18 svg/ancient-sleeping-dark.svg +a348a1b509cdb6bc76e59f39ee5157e0220bc2561601960cc5b67787657a7233 svg/ancient-sleeping.svg +045a54e711ad47248e2de2c1cbca3f1b53170b83daac552c51321ab55beaa71b svg/cactus-acc-all-dark.svg +068d69da9c6d86f4192e1585e06216e50b2506626e948dc3f868ec95bde0f838 svg/cactus-acc-all.svg +0b895a5ac4689f948a20774310f7190886f93b90de2667ed0da3b12496ec9efd svg/cactus-acc-flower-dark.svg +966c68a67f33ac299a3cf9df2cb23786d96b8c148c8687af8f7df5b1c816175d svg/cactus-acc-flower.svg +dfd6aadcfd1cfc5fec3877af7e899d8381af01dfaed5983130b53890ff055043 svg/cactus-acc-hat-dark.svg +050fa2d716a14a98cdaaf87eec11aade4c4246b7d3cf7318a44a7ff18b1b1ff9 svg/cactus-acc-hat.svg +3a7cc6cbf6f16060677924b133afca60497addb4170b4abc96276232dd1f0fee svg/cactus-acc-scarf-dark.svg +11a9ae275dd48462d309b5c189c63ce47589cf38555ecfd4598946af68903fd6 svg/cactus-acc-scarf.svg +63f76d5ef0b04ed240a1db8e0a565cf9023c91aed92e9ce421c45099c2370614 svg/cactus-ancient-bored-dark.svg +a8a6f3d305658b7500bf410a7f72b1e88898a5aaea61dfa3214c0b55e731c5df svg/cactus-ancient-bored.svg +3baaaf32ea21e01df7d6016083fe7a81014c73e6f25fc0a7a9a3c7f2b9530e88 svg/cactus-ancient-happy-dark.svg +690ae74a947bf479ee0a340d94b24c8b05ca6559307e1aa73d5456968d940e0d svg/cactus-ancient-happy.svg +56abdb91cfe6bca6b2cf78fb9b7f0b39c45fb81a634fc2a1554906d19d0805d8 svg/cactus-ancient-radiant-dark.svg +c20cd579aa4f35eb7971d3ad53a4b717d7720ffde6e47f9aadd3bf637b6b2cd3 svg/cactus-ancient-radiant.svg +ed3aa114040ef78d86a920d50181c1344a93b1820bdaa6d4a6c95ff3afeed312 svg/cactus-ancient-sleeping-dark.svg +a4e8437eedf3e5550670751d7de8b3826e0c57f112655f87cbcebad7768d0856 svg/cactus-ancient-sleeping.svg +ce340705e6044675bc894e9a4ffcd44e503f14d9b53664e13e0bdc30d51953d7 svg/cactus-sapling-bored-dark.svg +5adb2a257dc0d2141aa915e4ce41d27fbf0b4b97b7adef24fff1069cd311d092 svg/cactus-sapling-bored.svg +e1f257c5d5f520387ecf384f2a5a94a4a306db77f9c2946e2701fcc070a48e16 svg/cactus-sapling-happy-dark.svg +84abab823ad174f4154f719596fa2998453d43c87ad02cc48379ba8a4091eb7e svg/cactus-sapling-happy.svg +e934263cdea16d3fad8f9e4d0bfaf9751e23b7f7abac900290ae2de592bbe423 svg/cactus-sapling-radiant-dark.svg +5dd2f09e8ff2b26f247db1b76f791f48779b73fd5e644ad6d10f9f6d49bdd084 svg/cactus-sapling-radiant.svg +80965c66efe73d2d21a9e9c0b8f52b7ae83a82b0e0b05dadc367e50c4039f6ee svg/cactus-sapling-sleeping-dark.svg +19897c13f52db492adf3d335e28986fc394d6b6773341e8cd2195349b007d9f6 svg/cactus-sapling-sleeping.svg +bf298edf96b5fd6ecf8f6c10fff09c40485af446eb9171aadc2dee09f5b67160 svg/cactus-seed-bored-dark.svg +c3c3af0be2a01b1cd93dc8bcc7b3ad80ed456d61e6f267777aa5835bce5d856b svg/cactus-seed-bored.svg +bfdb7e75aad17c70d7050fa79f080efb505276030b53d3109ae2e942e79a6cb9 svg/cactus-seed-happy-dark.svg +eda12471289bf93063c471402205774a95c9a171c710abf1aa7890b632c8c6b5 svg/cactus-seed-happy.svg +c60039b5d8ea86633f49dcc26cbe4a51ee00ca94d170bbd1fec920305345fde9 svg/cactus-seed-radiant-dark.svg +8ca8820da7a00ed032331329cf0b2276a69321c2cede8451c33c9b9c0d65cef3 svg/cactus-seed-radiant.svg +cc0069ed3e90a9cadc69d6a692d9b780f0c45df263c3b79378d67d8d687fb04e svg/cactus-seed-sleeping-dark.svg +14a8c542cc5d3f7d6bb9ae898cdfdaf4d5415fc6e552dc178d6820872c938741 svg/cactus-seed-sleeping.svg +4f6014e0824e014bab7d891341cfbf946e32e69687b0ca9aecde44c251777808 svg/cactus-shrub-bored-dark.svg +111ee8da99ebb4a7d2ec9cdc8f05d6ef36c7e9ef95e813bd863786e7222c4eac svg/cactus-shrub-bored.svg +31d85d2eedbaf35647a69ea0fbd9f7118471d5f8f9ab42496e59f938ad25fb7c svg/cactus-shrub-happy-dark.svg +8872207d6f0bcfa365365112d18bcf6da246dcc1b741aead6e5f9e38287c2f17 svg/cactus-shrub-happy.svg +975fd3fefb03aed37e307c7133cb716bca58a142ab9e0f9751dfc559db4a6ec1 svg/cactus-shrub-radiant-dark.svg +ad8dedf477f502127e9b0dfaae3f0c9635bda04457c80e19c59d3b30e31d593e svg/cactus-shrub-radiant.svg +19980de1b599708e6b8e839b90ff32515ecc78eb12e377468a2cfc189ec72d9e svg/cactus-shrub-sleeping-dark.svg +bbf902c9ddb6a9d7b21711d00cf1d0784ef865a98b2dab8f8848864dcbf79958 svg/cactus-shrub-sleeping.svg +aa0fecf08fe20f054ce102a00f3d69e5ffbef02ca7363ce76da8058644546352 svg/cactus-sprout-bored-dark.svg +67d1804f162e03c1fc42c42e763a2f1763540299bd4e41fc38028492d01810a4 svg/cactus-sprout-bored.svg +b6cdf7574d6926bbd378372c355be59279fbe3cc77f483775768790cef1cb98a svg/cactus-sprout-happy-dark.svg +1ec5360fecc9075e4d1332d62a43644ff9263265d8206774bbfca8c0c5256c16 svg/cactus-sprout-happy.svg +0def1b42783c3673592480f86b41422a77c046b634c4b166c46f85321d864e5f svg/cactus-sprout-radiant-dark.svg +77489cdafde92b33df8d4b4773600d91e30f8b4b8061b06d7478183c49882bc2 svg/cactus-sprout-radiant.svg +860a5bd27953970522874a9d2bc31fef3bb25f74d28788991b531b554db607f9 svg/cactus-sprout-sleeping-dark.svg +5c4a8519326b4462c62eecd6cb499cbc60c6df395c9c989a8de17044d4dc0bef svg/cactus-sprout-sleeping.svg +dc8eae62f88ef2b9bd2445592095287705266e67a8ad91682573610eddb9d1bf svg/compact-ancient-dark.svg +7dc8fe4d08672259a1e1ea53feeba6eb1ffcb1eb25afd587403b43e5d73dff64 svg/compact-ancient.svg +e91dea7fe473a14dd0177bcd3598be68eeb47b3d6a8cdc4e29c6932b13ea8b0e svg/compact-cactus-ancient-dark.svg +056c4fcefc22812ea56f68aacffb8b750ff0dcc57c19f7882fab7029f59aa348 svg/compact-cactus-ancient.svg +296527ddbda062f06712e4336404cde368699e0deaffa4b38c55bc6b8ffa4820 svg/compact-cactus-sapling-dark.svg +65ad7bc8dd2a06a28a2ed92bba1dc7a25ef3ef4a56fe133c22bc90205cd5a898 svg/compact-cactus-sapling.svg +77c92da49e9004d8be48a0b5268714d82c3890ad63d2168aa1218f1d0c04f731 svg/compact-cactus-seed-dark.svg +1eb81747174286aa51510785b3d7e5497ccb1721403c3add48dc30506c35235a svg/compact-cactus-seed.svg +bdcc3ba1bd8958fd16c44ccbd7e32cd66e6a9e3730bfed448ad7dc42b4d5b68d svg/compact-cactus-shrub-dark.svg +628065bac787aac0e7bf6bfbe28dc4fc5047f636d1fb7839d76177f62513ecc2 svg/compact-cactus-shrub.svg +bc36468900b1ace2e85c0c0da62647ce44255590f602d0a54bd01b29025b96f5 svg/compact-cactus-sprout-dark.svg +22ac44c561cc99e06a2cb45f3cb9031043e60100112b0f0c50ad8d4a28cf7e38 svg/compact-cactus-sprout.svg +b2d58a77c8dde635d69641ca9ca17d172d49a780fb7bd36a09e2b9606b580004 svg/compact-mushroom-ancient-dark.svg +b7d475811d1ce4372cf8cd953d484cc39d45b3ddc4f3f046461a7d9ccca5a0f0 svg/compact-mushroom-ancient.svg +6ee11812d8d3111f8beeaee9507648174367a24dad40de4637f2b3a4387b04c2 svg/compact-mushroom-sapling-dark.svg +267fcb3ef6694f36ce95397d7088e5852924846cd98c96402fe9bd3cbde744e1 svg/compact-mushroom-sapling.svg +e82a71ad45a8c02ad3619f9e9b693a6ae631b286831627dee2fc57d42c2c8165 svg/compact-mushroom-seed-dark.svg +a9bf3ffb83a4f28345cc4e8ab6e2f8cbbc6706003e9a8d3e2f411f2f79bdea29 svg/compact-mushroom-seed.svg +6cc547cba636cf7267e5882f3c936bb1b5fe6914877e1b83d28cedee7158b173 svg/compact-mushroom-shrub-dark.svg +fde0239591d767a60af47052fcd0d51677cfa03c7a76bf9f2b67de5faff2048f svg/compact-mushroom-shrub.svg +d487792f39d231a42010fdd8679abd6724cbbe0b5b3c6bd954547e732f75513b svg/compact-mushroom-sprout-dark.svg +036b0955be3b1c669c398306afdebed53a42e6befac7e133267788108acf9767 svg/compact-mushroom-sprout.svg +aa7df5cb6d9581bcfde2099fc460087ef1295f0beed10679ef28fee0ed00ee19 svg/compact-sapling-dark.svg +d1758387e8eeae9297064850834eee81a8a6c26aa730f07de5f96a6b04507158 svg/compact-sapling.svg +3af49fec08688d9e82a0a3430614f20191b53d12ace34f4d8c3d93b00c3c7197 svg/compact-seed-dark.svg +1f33302ae6c24fe8175f8e007a2cdae9a4253b26a8aee06ed9d172090043ae37 svg/compact-seed.svg +d8962793b2deafff4bf10e4b90ce2b1c0dc10a0b5baefa3ca05084d4946d9b9d svg/compact-shrub-dark.svg +707cdae0227520c60c6ae9567f002f2b2da9a229e4c514716492d391d2d6bf1c svg/compact-shrub.svg +3b60c4c98c6b0437ddac35c8d1941ac51f56dc2458770d1aa105cc0f9d30f96c svg/compact-sprout-dark.svg +c1132660761111d6f308b42bfb35a3fe782754b918c8f557e2b02991f493fa5c svg/compact-sprout.svg +91de20fab9af403da37976b86d3f68d4243daee9c5f802761c41b7bdcd111367 svg/mushroom-acc-all-dark.svg +ab04ccded699a0f0eac6f66cf0e8f9faf0b16447fcabd58d271d137da858f0eb svg/mushroom-acc-all.svg +8f51a90adbf06e9de0faa69da53ffb683f079f77a980ae23dc127e3aa59e8e30 svg/mushroom-acc-flower-dark.svg +82698a15a83d895eb216bb489bd275ead8e7ca95698f0a840999ceec791795f0 svg/mushroom-acc-flower.svg +aa6cc3cb605c4f7847e6d5783c0b993ba8f9ae266c616a237f56614c87caa7e7 svg/mushroom-acc-hat-dark.svg +c570bfeba784d888cbfdace2215cdb372d112e53eaf9ce37dc7c1885ad69a6f6 svg/mushroom-acc-hat.svg +79ffeebf99e53be7e43f33882e7dd46e75e094fd8e94ffa274fcc6a65aa2990f svg/mushroom-acc-scarf-dark.svg +0df4701df0dfad1cd4c944710dc7b6c535a7f7240167c81dc4c753dc8db4b883 svg/mushroom-acc-scarf.svg +a07ce133305289319634eb317cae3a163f389c771db31a362304db4fb3e4db89 svg/mushroom-ancient-bored-dark.svg +a247d8e90dec843406ac6536e0ade1832fde6f05fdc493764de3ca611ea5c12b svg/mushroom-ancient-bored.svg +7aab8c3b0092ffd66632b521fc8bfb31e6911c40b02de2b872809c70f98365d3 svg/mushroom-ancient-happy-dark.svg +37e110b2c11721c37a033af429f6b5b82e29412bdbaa6794ef04c258cdfbcdac svg/mushroom-ancient-happy.svg +f1a9b23e35f0233c41b054e6ac9af2cf1bba763a9cc14556b694aefb54e4f792 svg/mushroom-ancient-radiant-dark.svg +a563b34a032fbefea8af3c1e601d5285675619d32f3634752c7550341501ec11 svg/mushroom-ancient-radiant.svg +2ac6a51129f6460cce68498843376dc1d0d4d78e775fff75373af104930c1587 svg/mushroom-ancient-sleeping-dark.svg +3963a740cdbbe97d6a4294df0b45948899e81232256f15aff5ff841040b653d8 svg/mushroom-ancient-sleeping.svg +bfacfd9d94971f2215b05ac8e32d44a0921d8ce84c7d8b2d1d475625950ad70f svg/mushroom-sapling-bored-dark.svg +e17f3a4c067975ec7efab8565d1c97eb00d95ec1ce8f38e6c5adf19fe508378f svg/mushroom-sapling-bored.svg +8673c92f1aff9ca91e303d4ba6a88bc32e70825e19b1e5aaaaa9198301d4157d svg/mushroom-sapling-happy-dark.svg +3e115d75b5e1e6731ee5f5b4e7131550dabe8bc492bb36916cd9b7cf5249300a svg/mushroom-sapling-happy.svg +45c619f13594d4fb24fc47abfb9e174917aebf9043d9fd2603f52e0a670c1a55 svg/mushroom-sapling-radiant-dark.svg +425b4c23f8114380a43bad3f4e77820ee64e1156b2c9337b015a2a96ce47df3e svg/mushroom-sapling-radiant.svg +aa123a5763beb70972ab30d205cddbd14beda4877c84d970949ad7e3bd7979b3 svg/mushroom-sapling-sleeping-dark.svg +83e5ff805666b7fe7cd9841a6b17e7cf41e49346a90ca485b44aa26ca2f8f62e svg/mushroom-sapling-sleeping.svg +3e6155015a5d282358739ff3dede36e0b89940336728af1a149510893e241763 svg/mushroom-seed-bored-dark.svg +96b644cb061399a46ed300e9ad2e912669b6de7d19f92b80c007b1aaae77dca8 svg/mushroom-seed-bored.svg +4871ca5c75f094ff28c1c01f0579fcf57e285261f3b0305dcd65ddd229d2ff52 svg/mushroom-seed-happy-dark.svg +4ce5c8a871f5df91720df69d137f0438c488b99b2d70510b3b296a16f468bd3b svg/mushroom-seed-happy.svg +33ff2d5521ba3a0f3b40373935c86e738813ee4635ac1108fc46b27b06846e06 svg/mushroom-seed-radiant-dark.svg +edf82cae426a80dfcd8e4472d328520eb99bc222ee2aa448894da8c537ef40e3 svg/mushroom-seed-radiant.svg +b18417126f192a0d4c03920841382be0bfa11dca5f7bde3acf7e9024484b0029 svg/mushroom-seed-sleeping-dark.svg +22f9386980ee2eea2a71d6e8c60812aa89ac5d88d026bbe7665fe03674f82d72 svg/mushroom-seed-sleeping.svg +a3bbff2212927c4f6bca131697b90569b16201650ebef4e40d0ef2d8a5882754 svg/mushroom-shrub-bored-dark.svg +4e0423ae266798cae93c11eafdbddd2d292be0ed715cbcade48dcd35c69e9bf1 svg/mushroom-shrub-bored.svg +cfdc312121c74672814b38dd0fa501fa835588a97b6b97eb587e97702b134bb3 svg/mushroom-shrub-happy-dark.svg +c1af88b4efd61fc2c7974ec716ef26bb37a6f3bb10b8cfc3fd63a141032075fe svg/mushroom-shrub-happy.svg +dd3d40f6d4f2d95fe16165c2d943c29c9a9f5adc97e37bf8b74038ad3bad6979 svg/mushroom-shrub-radiant-dark.svg +58bdd37562a12c3248c9d86275a97b1ad285f877c49f99e4ef892bfba54e27b3 svg/mushroom-shrub-radiant.svg +ad954eaf104509f52fa91efa9858cc19746b6097fc911766605130e03bd41ffd svg/mushroom-shrub-sleeping-dark.svg +b600a5436d22b153b143a75a26f814c5c38547a3f236b0186b08db05d0ac35e7 svg/mushroom-shrub-sleeping.svg +0a4bafae0f872e97e8051917769040789b0acb69267992bc21ec257afa199c52 svg/mushroom-sprout-bored-dark.svg +98a1982420e69fcc24e662fbd45b08f68c43f2c676ff83093f72e787688f42fa svg/mushroom-sprout-bored.svg +54ef837ca907f30a04c55557973639640922ed21ccace19d60c555ae69e78c70 svg/mushroom-sprout-happy-dark.svg +505c86cfa64284345e10289ec76017315fb167fe09606471b802463fd1b7028a svg/mushroom-sprout-happy.svg +acb002ec4d9c6d8721844f6acf6e01efd70ca981b20de59dc1671cd7585bf850 svg/mushroom-sprout-radiant-dark.svg +53797bfdf58994022dda25e3e72148adb3d71fc80723459a0037dde392b71f6d svg/mushroom-sprout-radiant.svg +6d197c8a1dfeffae89a7d1c7e47630b7bfe1cdfbe77e1ef8c2b3a694953951ed svg/mushroom-sprout-sleeping-dark.svg +31ecaa5fdbef1f364287c62d87a553b914c3325c5a4b92de176b101fc59f83df svg/mushroom-sprout-sleeping.svg +d4a85ae4ea31d9e36960ebfa7879b900e144599a462636189b693b0ac3130212 svg/sapling-bored-dark.svg +afd8737ad3678d8df0d22289a2093d52c306762ca95e6d560b32533d9d4be248 svg/sapling-bored.svg +1dbcffaca791fdaa4fb50c61fc033e0a75b2fcbf0a248eb330a4c3b2e0628ac8 svg/sapling-happy-dark.svg +f436e446f87c038db898be19ef71dc759db2fd2c41dd8bc310f33464e9a09cd8 svg/sapling-happy.svg +7900866cf0b87db5c922f96938d6d91d81df4fe3dacfdffcc1047b59e71d10f2 svg/sapling-radiant-dark.svg +18ffc97f7f0f399853faa532c4fef025b40ae84285e2037d070a9e1008029de5 svg/sapling-radiant.svg +acf73c2c63af83b17678dcad8f6708c4a2bb37bfea666cc52eb3a04fc017d0d5 svg/sapling-sleeping-dark.svg +8c7a81d94d4d4906716b8163516b4f1dd0444731221c379af0902743746724fb svg/sapling-sleeping.svg +7cb19763720e6ea91e038d46daefb5a3c2f288e27c75adf1b1ad029581d78a77 svg/seed-bored-dark.svg +5fc123e38ea9cceb027c6b62b5bd02dac609555b8dd8ab68908578fe8e4bd556 svg/seed-bored.svg +43b8a563e045d47a31bd2387412b3004da268a60daae6093c57838fd10e70654 svg/seed-happy-dark.svg +9b63eddd543f538aaa5986303ea103316a6652e30026e005263fb24bf9d86fb0 svg/seed-happy.svg +a707b1d9dbcbecff2f6ec85dfd924127f8da14d43622d11ea458a98ddf36644c svg/seed-radiant-dark.svg +8e90684e2dc1b4d49464b27c49cd53ab7ff3dde80f00eee30cf0836299126af5 svg/seed-radiant.svg +28aa73a6933b51c14b340ad37727b618571eb9617d59bfa1b59eb853019e9dd2 svg/seed-sleeping-dark.svg +1cceb6512ad1de81de5157d1d5ad5c35c72733a6cecc593a52edca2308b71711 svg/seed-sleeping.svg +2222d4f462e46736bc8bbfdfaf14c7a56ee2a188b628b221057ee3148dd3fdd6 svg/shrub-bored-dark.svg +0d5e04c4f65bf5036820b73aad9837ae0fd40e15fdeaea0d972bfeeb1f57162b svg/shrub-bored.svg +383ff7efb9ae913ec6ce5a4469643b213047a19d9e35543bbdcf6976702da32d svg/shrub-happy-dark.svg +177cd0ece4b69041bb328c5eeb19c47b5483f9489a7b192518224773f945a639 svg/shrub-happy.svg +6400b43b3e7f636ef2fd6dabff7513901b55d61738519de3646a0e0fe844b1b4 svg/shrub-radiant-dark.svg +67e2d23047362b75a0f706d0627f5087a892f143c66c50078ee66f026471e647 svg/shrub-radiant.svg +126b321331c1abc548439ce569c837bae2ea038014a7edc02b751b714a570c96 svg/shrub-sleeping-dark.svg +906d7e86ac45b9a418247c4628e786fe3d27878008c5c825aa9a5c7d1a1dab58 svg/shrub-sleeping.svg +0a7c352b7c8349911eb38ff3327aefa4a36f8872206e9310d5e6731bd64fcf73 svg/sprout-bored-dark.svg +4417e30ed830fa227c021471332419dc6fed8f9cb51913e06dce8a49509474bf svg/sprout-bored.svg +13840964e82003343c5f6549e04aaedb968d16f76cd81674be2f38aba1865339 svg/sprout-happy-dark.svg +b76fd2106975430309c56c0857161e0d77523c22fe0bbdb4cf4c384d1c4065ff svg/sprout-happy.svg +0c8e82cb86279cdb158c6f60d65c711eda23675dd12a914b9f066824f9ef7947 svg/sprout-radiant-dark.svg +605692c66a7eb2e343bd0fd25a6a22e7da028e255496a807207846aa4dcb0701 svg/sprout-radiant.svg +22cea3072a65b7d2b461f412ea5c9a9fc584d0fd277185799f55ed7750c7f06d svg/sprout-sleeping-dark.svg +013ac5fe027977ed6c7606d669b0eefd961eaf62fced9b4d4056c71f68b06bd6 svg/sprout-sleeping.svg