feat: externalize apps/analysis to Gitea repos, add analysis table
- Migration 007: repo_url on apps table + analysis table with FTS5 - Analysis struct, parser, CRUD, validation, hash computation - Selective purge: remote-only apps/analysis preserved across fn index - CLI: fn app list/clone/pull, fn analysis list/clone/pull - search/show/list now include analysis results - Apps removed from git tracking (content lives in Gitea repos) - .gitkeep for apps/ and analysis/ dirs - Bash functions: jupyter analysis pipeline, shell utilities - Browser domain: CDP functions moved from infra to browser Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
#!/usr/bin/env bash
|
||||
# run_steps — ejecuta pasos de un YAML generico (action=command)
|
||||
|
||||
# run_steps <yaml_file> [--strict]
|
||||
#
|
||||
# Lee un YAML con la estructura:
|
||||
# name: <run_name>
|
||||
# steps:
|
||||
# - name: <step_name>
|
||||
# action: command
|
||||
# command: "<shell_command>"
|
||||
# continue_on_error: true|false # opcional, default false
|
||||
# timeout_ms: 30000 # opcional, default 30000
|
||||
#
|
||||
# Para cada paso de action=command:
|
||||
# - Ejecuta el command con timeout (timeout_ms ms)
|
||||
# - Captura exit code, stdout+stderr y elapsed time
|
||||
# - Si falla y continue_on_error=false → aborta
|
||||
# - Acumula resultados en TSV temporal
|
||||
#
|
||||
# Al final:
|
||||
# - Llama a report_execution_json para generar JSON a stdout
|
||||
# - Llama a exit_with_status para determinar el exit code
|
||||
#
|
||||
# --strict: mapea partial (2) a failure (1) en status y exit code
|
||||
#
|
||||
# Requiere: yq, jq (jq opcional si report_execution_json tiene fallback printf)
|
||||
run_steps() {
|
||||
local yaml_file="$1"
|
||||
local strict=0
|
||||
|
||||
if [[ "${2:-}" == "--strict" ]]; then
|
||||
strict=1
|
||||
fi
|
||||
|
||||
# --- validaciones previas ---
|
||||
if [[ -z "$yaml_file" ]]; then
|
||||
echo "run_steps: yaml_file requerido" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$yaml_file" ]]; then
|
||||
echo "run_steps: archivo '$yaml_file' no existe" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! command -v yq &>/dev/null; then
|
||||
echo "run_steps: yq no encontrado en PATH" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# --- cargar funciones del estandar ---
|
||||
local script_dir
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=bash/functions/shell/exit_with_status.sh
|
||||
source "$script_dir/exit_with_status.sh"
|
||||
# shellcheck source=bash/functions/shell/report_execution_json.sh
|
||||
source "$script_dir/report_execution_json.sh"
|
||||
|
||||
# --- leer nombre del run ---
|
||||
local run_name
|
||||
run_name=$(yq e '.name // "unnamed"' "$yaml_file")
|
||||
|
||||
# --- contar pasos ---
|
||||
local step_count
|
||||
step_count=$(yq e '.steps | length' "$yaml_file")
|
||||
|
||||
if [[ -z "$step_count" || "$step_count" -eq 0 ]]; then
|
||||
echo "run_steps: el YAML no tiene steps" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# --- archivo TSV temporal para acumular resultados ---
|
||||
# columnas: name<TAB>action<TAB>status<TAB>elapsed_ms<TAB>output<TAB>error
|
||||
local tsv_file
|
||||
tsv_file=$(mktemp /tmp/run_steps_XXXXXX.tsv)
|
||||
# shellcheck disable=SC2064
|
||||
trap "rm -f '$tsv_file'" EXIT
|
||||
|
||||
local ok_steps=0
|
||||
local failed_steps=0
|
||||
local started_at
|
||||
started_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
local t_run_start
|
||||
t_run_start=$(date +%s%3N)
|
||||
|
||||
# --- iterar sobre cada paso ---
|
||||
local i
|
||||
for (( i=0; i<step_count; i++ )); do
|
||||
local step_name step_action step_command step_continue step_timeout_ms
|
||||
|
||||
step_name=$(yq e ".steps[$i].name // \"step_$i\"" "$yaml_file")
|
||||
step_action=$(yq e ".steps[$i].action // \"command\"" "$yaml_file")
|
||||
step_command=$(yq e ".steps[$i].command // \"\"" "$yaml_file")
|
||||
step_continue=$(yq e ".steps[$i].continue_on_error // false" "$yaml_file")
|
||||
step_timeout_ms=$(yq e ".steps[$i].timeout_ms // 30000" "$yaml_file")
|
||||
|
||||
# convertir timeout de ms a segundos enteros (timeout(1) usa segundos)
|
||||
local timeout_sec
|
||||
timeout_sec=$(( (step_timeout_ms + 999) / 1000 ))
|
||||
|
||||
# solo soportamos action=command
|
||||
if [[ "$step_action" != "command" ]]; then
|
||||
local err_msg="action '$step_action' no soportada (solo command)"
|
||||
printf '%s\t%s\t%s\t%s\t%s\t%s\n' \
|
||||
"$step_name" "$step_action" "error" "0" "" "$err_msg" >> "$tsv_file"
|
||||
failed_steps=$((failed_steps + 1))
|
||||
|
||||
if [[ "$step_continue" != "true" ]]; then
|
||||
echo "run_steps: paso '$step_name' — $err_msg — abortando" >&2
|
||||
break
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ -z "$step_command" ]]; then
|
||||
local err_msg="campo 'command' vacio en paso '$step_name'"
|
||||
printf '%s\t%s\t%s\t%s\t%s\t%s\n' \
|
||||
"$step_name" "command" "error" "0" "" "$err_msg" >> "$tsv_file"
|
||||
failed_steps=$((failed_steps + 1))
|
||||
|
||||
if [[ "$step_continue" != "true" ]]; then
|
||||
echo "run_steps: $err_msg — abortando" >&2
|
||||
break
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
# ejecutar con timeout y capturar output + tiempos
|
||||
local t_start t_end elapsed_ms step_output step_exit
|
||||
|
||||
t_start=$(date +%s%3N)
|
||||
step_output=$(timeout "$timeout_sec" bash -c "$step_command" 2>&1)
|
||||
step_exit=$?
|
||||
t_end=$(date +%s%3N)
|
||||
elapsed_ms=$(( t_end - t_start ))
|
||||
|
||||
# timeout(1) sale con 124 cuando agota el tiempo
|
||||
local step_error=""
|
||||
if [[ "$step_exit" -eq 124 ]]; then
|
||||
step_error="timeout: comando excedio ${step_timeout_ms}ms"
|
||||
fi
|
||||
|
||||
# escapar tabuladores y saltos de linea en el output para el TSV
|
||||
local safe_output
|
||||
safe_output=$(printf '%s' "$step_output" | tr '\t' ' ' | tr '\n' ' ')
|
||||
|
||||
if [[ "$step_exit" -eq 0 ]]; then
|
||||
printf '%s\t%s\t%s\t%s\t%s\t%s\n' \
|
||||
"$step_name" "command" "ok" "$elapsed_ms" "$safe_output" "" >> "$tsv_file"
|
||||
ok_steps=$((ok_steps + 1))
|
||||
else
|
||||
printf '%s\t%s\t%s\t%s\t%s\t%s\n' \
|
||||
"$step_name" "command" "error" "$elapsed_ms" "$safe_output" "$step_error" >> "$tsv_file"
|
||||
failed_steps=$((failed_steps + 1))
|
||||
|
||||
if [[ "$step_continue" != "true" ]]; then
|
||||
echo "run_steps: paso '$step_name' fallo (exit $step_exit) — abortando" >&2
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
local t_run_end
|
||||
t_run_end=$(date +%s%3N)
|
||||
local total_duration_ms=$(( t_run_end - t_run_start ))
|
||||
local ended_at
|
||||
ended_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
local total_steps=$(( ok_steps + failed_steps ))
|
||||
|
||||
# --- calcular status y exit code ---
|
||||
local status_code
|
||||
status_code=$(exit_with_status "$total_steps" "$ok_steps" "$failed_steps")
|
||||
|
||||
local run_status
|
||||
case "$status_code" in
|
||||
0) run_status="success" ;;
|
||||
1) run_status="failure" ;;
|
||||
2)
|
||||
if [[ "$strict" -eq 1 ]]; then
|
||||
run_status="failure"
|
||||
status_code=1
|
||||
else
|
||||
run_status="partial"
|
||||
fi
|
||||
;;
|
||||
*) run_status="failure"; status_code=1 ;;
|
||||
esac
|
||||
|
||||
# --- generar JSON a stdout ---
|
||||
report_execution_json \
|
||||
"$run_name" \
|
||||
"$run_status" \
|
||||
"$status_code" \
|
||||
"$started_at" \
|
||||
"$ended_at" \
|
||||
"$total_duration_ms" \
|
||||
"$tsv_file"
|
||||
|
||||
return "$status_code"
|
||||
}
|
||||
Reference in New Issue
Block a user