Etapa G: orquestador de tandas `import-batch.sh` (escalera nix→Alpine, yield por tier)
Faltaba el orquestador de "importar tandas a escala": las piezas per-paquete ya
existían (nix-import.sh, alpine-import.sh) pero sin un driver que aplicara la ESCALERA
DE FALLBACK en lote.
- import-batch.sh <pkg...> | -f <listfile>: por paquete intenta nix (Rust/Go/CLI,
~100% musl) y cae a Alpine main→community si el import falla (Alpine trae los parches
de musl). Mapeo opcional nixattr=alpinepkg. Escribe OUTDIR/<pkg>.toml.
- Mide el YIELD POR TIER (nix / alpine / fallaron) — el dato real al escalar, sin
especular. stdout limpio = nombres importados (encadenable con xargs/pin-recipes).
- Encadena con las piezas existentes: pin-recipes.sh (Fase 2 tag→SHA) + build-repo.sh
(Etapa F pack --build + repo firmado).
- tandas/cli-rust.txt: primera tanda curada (tier 1 Rust) para el modo -f.
VALIDADO REAL en vivo (nix 2.34.7): tanda {fd, hyperfine, jq} → yield 3/3, nix=2
(fd/hyperfine), alpine=1 (jq cae a main + baja test-portable.patch). pin en lote sobre
la tanda: fd v10.4.2 → SHA 7027d453, jq (tarball) salteado.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Semantic delta
No definition changed: this change is text, documentation or data.
2 files · +90 −0
- scripts/import-batch.sh +74 −0
- tandas/cli-rust.txt +16 −0
diff --git a/scripts/import-batch.sh b/scripts/import-batch.sh
new file mode 100755
index 00000000..d8a3c675
--- /dev/null
+++ b/scripts/import-batch.sh
@@ -0,0 +1,74 @@
+#!/bin/sh
+# import-batch.sh <pkg...> | import-batch.sh -f <listfile> — Etapa G: importa una TANDA a
+# escala aplicando la ESCALERA DE FALLBACK (memoria etapa-g): por cada paquete intenta primero nix
+# (Rust/Go/CLI moderno, ~100% en musl) y, si el import falla (src no-clasificable, eval error), cae
+# a Alpine (que TRAE los parches de musl) probando `main` y luego `community`. Escribe la receta a
+# OUTDIR/<pkg>.toml y, al final, MIDE EL YIELD POR TIER (nix / alpine / fallaron) — el dato real que
+# decide dónde está la fricción al escalar, sin especular.
+#
+# Es sólo el IMPORT (barato). El trabajo real es construir: encadená con
+# scripts/pin-recipes.sh "$OUTDIR"/*.toml (Fase 2: tag→SHA en lote)
+# scripts/build-repo.sh (Etapa F: pack --build + repo firmado)
+#
+# Ej: scripts/import-batch.sh ripgrep fd bat hyperfine
+# scripts/import-batch.sh -f tandas/cli-rust.txt
+# OUTDIR=recipes/incoming scripts/import-batch.sh sd dust
+# Env: OUTDIR (default recipes), NIX_STORE (p.ej. $HOME/.nixstore si /nix/store no es escribible),
+# ALPINE_BRANCH (default master), HAMMER (cmd del cli). Un nombre puede mapear nix→alpine
+# distintos con `nixattr=alpinepkg` (p.ej. `ripgrep=ripgrep`, `the_silver_searcher=the-silver-searcher`).
+set -eu
+
+here=$(dirname "$0")
+OUTDIR="${OUTDIR:-recipes}"
+export OUTDIR # alpine-import.sh baja sus .patch acá
+
+# Resolver la lista de paquetes: -f <fichero> (una entrada por línea, '#' comenta) o argumentos.
+if [ "${1:-}" = "-f" ]; then
+ [ -n "${2:-}" ] || { echo "uso: import-batch.sh -f <listfile>" >&2; exit 1; }
+ pkgs=$(sed -e 's/#.*//' -e '/^[[:space:]]*$/d' "$2")
+else
+ [ "$#" -gt 0 ] || { echo "uso: import-batch.sh <pkg...> | -f <listfile>" >&2; exit 1; }
+ pkgs=$(printf '%s\n' "$@")
+fi
+
+mkdir -p "$OUTDIR"
+nix_n=0; alpine_n=0; failed=""; got=""
+
+# IFS=newline para tolerar una entrada por línea; restaurar después no hace falta (sub-shell del for).
+OLDIFS=$IFS; IFS='
+'
+for entry in $pkgs; do
+ IFS=$OLDIFS
+ # Mapeo opcional nixattr=alpinepkg; si no hay '=', el mismo nombre sirve para ambas fuentes.
+ case $entry in
+ *=*) nixattr=${entry%%=*}; alpinepkg=${entry#*=} ;;
+ *) nixattr=$entry; alpinepkg=$entry ;;
+ esac
+ out="$OUTDIR/${nixattr}.toml"
+
+ printf '%-24s ' "$nixattr" >&2
+ if "$here/nix-import.sh" "$nixattr" > "$out.tmp" 2>/dev/null && [ -s "$out.tmp" ]; then
+ mv "$out.tmp" "$out"
+ nix_n=$((nix_n + 1)); got="$got $nixattr"
+ echo "✓ nix" >&2
+ elif "$here/alpine-import.sh" "$alpinepkg" main > "$out.tmp" 2>/dev/null && [ -s "$out.tmp" ]; then
+ mv "$out.tmp" "$out"
+ alpine_n=$((alpine_n + 1)); got="$got $nixattr"
+ echo "✓ alpine/main (parches musl)" >&2
+ elif "$here/alpine-import.sh" "$alpinepkg" community > "$out.tmp" 2>/dev/null && [ -s "$out.tmp" ]; then
+ mv "$out.tmp" "$out"
+ alpine_n=$((alpine_n + 1)); got="$got $nixattr"
+ echo "✓ alpine/community (parches musl)" >&2
+ else
+ rm -f "$out.tmp"
+ failed="$failed $nixattr"
+ echo "✗ ni nix ni Alpine" >&2
+ fi
+done
+
+total=$((nix_n + alpine_n))
+echo >&2
+echo "yield import: $total/$((total + $(printf '%s' "$failed" | wc -w))) — nix=$nix_n, alpine=$alpine_n.${failed:+ FALLARON:$failed}" >&2
+echo "recetas en $OUTDIR/.${got:+ Siguiente: pin + build —} ${got:+scripts/pin-recipes.sh}${got:+ $OUTDIR/*.toml}" >&2
+# Salida limpia en stdout: los nombres importados (para encadenar con xargs si se quiere).
+printf '%s\n' $got
diff --git a/tandas/cli-rust.txt b/tandas/cli-rust.txt
new file mode 100644
index 00000000..4f629933
--- /dev/null
+++ b/tandas/cli-rust.txt
@@ -0,0 +1,16 @@
+# Tanda CLI Rust moderno — Etapa G. Tier 1 de la escalera (Rust/musl-friendly → import-nix ~100%).
+# Uso: NIX_STORE=$HOME/.nixstore OUTDIR=recipes/incoming scripts/import-batch.sh -f tandas/cli-rust.txt
+# Luego: scripts/pin-recipes.sh recipes/incoming/*.toml && (pack --build para medir build-yield).
+# Sintaxis: un paquete por línea; '#' comenta; `nixattr=alpinepkg` si los nombres difieren.
+fd
+bat
+hyperfine
+ripgrep
+sd
+dust
+procs
+tokei
+hexyl
+xh
+zoxide
+bottom