#!/usr/bin/env bash # licencias-detectar.sh — deriva la licencia de las recetas cuya fuente vive en GitHub, # preguntándole a GitHub por el fichero LICENSE REAL del repo. Complemento de `licencias.sh`. # # ── POR QUÉ ESTO NO VIOLA LA REGLA DE «NO SE ADIVINA» ─────────────────────────────────────────── # `licencias.sh` prohíbe rellenar a ojo, y con razón: declarar mal una licencia convierte un hueco # visible en una afirmación falsa. Esto NO es adivinar. La API `/repos/{o}/{r}/license` devuelve el # resultado de **detectar el fichero LICENSE que el repo tiene de verdad** (GitHub usa licensee), no # una etiqueta que alguien escribió a mano en una web. Es la misma evidencia que miraríamos nosotros # abriendo el tarball, obtenida sin descargar 686 tarballs por un enlace de 8 Mbps. # # ── ⚠ LA LIMITACIÓN, QUE SE REGISTRA EN VEZ DE ESCONDERSE ─────────────────────────────────────── # GitHub detecta sobre la **rama por defecto**, no sobre el tag/commit que la receta pinea. En la # enorme mayoría de proyectos es lo mismo, pero NO es una garantía: un proyecto pudo relicenciarse # después de la versión que empaquetamos. Por eso: # · la salida va a un fichero APARTE (`docs/licencias-detectadas.tsv`) con procedencia y fecha, # no directo a las recetas — se puede auditar y rehacer; # · sólo se aceptan SPDX **definidos**: se descartan `NOASSERTION`, `null` y `other`, que es # justamente GitHub diciendo «no estoy seguro». Un «no sé» de la fuente se propaga como hueco, # no se redondea a una licencia plausible. # Si algún día hace falta exactitud a nivel de tag, el cierre correcto es el estructural: capturar # el COPYING/LICENSE en la fase de fetch, que ya descarga y extrae el tarball pineado. # # Uso: scripts/licencias-detectar.sh (escribe docs/licencias-detectadas.tsv) # luego: TABLA=docs/licencias-detectadas.tsv scripts/licencias.sh --sembrar set -uo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT" SALIDA="${SALIDA:-docs/licencias-detectadas.tsv}" JOBS="${JOBS:-8}" command -v gh >/dev/null || { echo "!! falta el CLI 'gh'"; exit 1; } gh auth status >/dev/null 2>&1 || { echo "!! 'gh' no está autenticado"; exit 1; } TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT # 1. Las que NO tienen licencia y cuya fuente es github.com → «nombreowner/repo» ./scripts/licencias.sh --faltan 2>/dev/null | while IFS= read -r n; do f=$(ls "recipes/$n.toml" 2>/dev/null | head -1) [ -z "$f" ] && f=$(ls recipes/incoming-*/"$n.toml" 2>/dev/null | head -1) [ -z "$f" ] && continue u=$(grep -m1 -E '^[[:space:]]*(tarball|repo)[[:space:]]*=' "$f" 2>/dev/null) case "$u" in *github.com*) ;; *) continue ;; esac # Sirve para https://github.com/o/r/... y para git@github.com:o/r.git slug=$(printf '%s\n' "$u" | sed -E 's#.*github\.com[:/]+([^/"]+)/([^/"]+).*#\1/\2#; s/\.git$//') case "$slug" in */*) printf '%s\t%s\n' "$n" "$slug" ;; esac done | sort -u > "$TMP/objetivos.tsv" TOT=$(wc -l < "$TMP/objetivos.tsv") echo "==> a consultar: $TOT recetas con fuente en GitHub" # 2. Preguntar a GitHub. Sólo SPDX definidos. export TMP xargs -P "$JOBS" -I{} sh -c ' n=$(printf "%s" "{}" | cut -f1); slug=$(printf "%s" "{}" | cut -f2) lic=$(gh api "repos/$slug/license" --jq ".license.spdx_id" 2>/dev/null) # ⚠ VALIDAR LA FORMA, NO SÓLO LOS «NO SÉ» CONOCIDOS. Cuando el repo da 404 (renombrado, borrado, # privado), `gh api` escribe el CUERPO DEL ERROR JSON en stdout y el filtro --jq falla ⇒ `lic` # queda valiendo `{"message":"Not Found",...}`. Filtrando sólo ""/null/NOASSERTION/other, ese JSON # pasaba como si fuera una licencia y se escribía en la receta: comillas incluidas, lo que además # ROMPE el TOML y hace que el ArtifactHash deje de calcularse. Pasó de verdad en 5 recetas el # 2026-08-07 y lo cazó la verificación de hashes, no la vista. # ⇒ Regla: aceptar sólo lo que TIENE FORMA de expresión SPDX (letras, dígitos, . - + y los # operadores OR/AND/WITH). Todo lo demás es un fallo disfrazado de dato. case "$lic" in ""|null|NOASSERTION|other) printf "%s\t%s\n" "$n" "$slug" >> "$TMP/sin.tsv" ;; *[!A-Za-z0-9.+\ -]*) printf "%s\t%s\n" "$n" "$slug" >> "$TMP/sin.tsv" ;; *) printf "%s\t%s\t%s\n" "$n" "$lic" "$slug" >> "$TMP/con.tsv" ;; esac ' < "$TMP/objetivos.tsv" CON=$(wc -l < "$TMP/con.tsv" 2>/dev/null || echo 0) SIN=$(wc -l < "$TMP/sin.tsv" 2>/dev/null || echo 0) { echo "# licencias-detectadas.tsv — GENERADO por scripts/licencias-detectar.sh. No editar a mano." echo "#" echo "# Procedencia: API de GitHub /repos/{owner}/{repo}/license, que detecta el fichero LICENSE" echo "# REAL del repositorio (licensee). Es evidencia, no una etiqueta escrita a mano." echo "#" echo "# ⚠ LIMITACIÓN REGISTRADA: la detección es sobre la RAMA POR DEFECTO, no sobre el tag que la" echo "# receta pinea. Casi siempre coinciden, pero un proyecto pudo relicenciarse después de la" echo "# versión que empaquetamos. Los SPDX indefinidos (NOASSERTION/other/vacío) se DESCARTAN: un" echo "# «no sé» de la fuente se propaga como hueco, no se redondea a una licencia plausible." echo "#" echo "# Formato: " echo "# Consultadas $TOT · con SPDX definido $CON · sin SPDX utilizable $SIN" sort "$TMP/con.tsv" 2>/dev/null } > "$SALIDA" echo "==> con SPDX definido: $CON · descartadas por indefinido: $SIN" echo "==> escrito: $SALIDA" echo "==> sembrar con: TABLA=$SALIDA ./scripts/licencias.sh --sembrar"