Files
pitiupi/scripts/pitiupi.sh
T
Gabriel Felipe Souza SantosandCursor 61753c421a feat(plugins): ping no catálogo, ausente não interrompe o run
A entrada fica no mesmo registro. Sem o build nesta máquina o comando segue só com os plugins que existem.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-04 00:32:23 -03:00

404 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
# Build, instala plugins em PITIUPI_HOME/plugins e executa o núcleo (catálogo: plugins.registry.json).
# Plugin do catálogo sem build.xml ou Makefile nesta máquina é ignorado.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CORE="${REPO_ROOT}/pitiupi"
PITIUPI_HOME="${PITIUPI_HOME:-${REPO_ROOT}/.pitiupi}"
PLUGINS_DIR="${PITIUPI_HOME}/plugins"
REGISTRY="${REPO_ROOT}/plugins.registry.json"
export PITIUPI_HOME
usage() {
cat <<'EOF'
Uso: scripts/pitiupi.sh <comando> [opções]
Comandos:
run Compila, atualiza plugins/ e abre a aplicação (padrão: --all)
build Compila e atualiza plugins/
link Só symlinks em PITIUPI_HOME/plugins (JARs já buildados)
list Lista plugins do catálogo (id, categoria, JAR presente)
discover Sugere entradas para o catálogo (pastas pitiupi.* com SPI)
check Ferramentas no PATH + valida plugins.registry.json
Opções (exclusivas; padrão em run/build/link: --all):
--all Todos os plugins do catálogo
--plugins ids IDs separados por vírgula (ex.: chat,screencast)
--category Nome Social | Jogos | Streaming
Exemplos:
./scripts/pitiupi.sh list
./scripts/pitiupi.sh run --all
./scripts/pitiupi.sh run --category Social
./scripts/pitiupi.sh run --plugins chat
Linux: no terminal, ./scripts/pitiupi.sh run --all (Java do Linux).
WSL: no terminal do Ubuntu, o mesmo comando (a janela abre no Java do Windows).
Windows: no cmd, scripts\pitiupi.cmd run --all (Java do Windows).
EOF
}
registry_python() {
python3 - "$REGISTRY" "$REPO_ROOT" "$@" <<'PY'
import json
import sys
from pathlib import Path
registry_path = Path(sys.argv[1])
repo = Path(sys.argv[2])
mode = sys.argv[3]
data = json.loads(registry_path.read_text(encoding="utf-8"))
plugins = list(data.get("plugins", []))
def by_id(pid):
for p in plugins:
if p["id"] == pid:
return p
return None
def jar_path(entry):
return repo / entry["dir"] / entry["jar"]
if mode == "validate":
if not plugins:
print("ERRO: nenhum plugin no catálogo", file=sys.stderr)
sys.exit(1)
for p in plugins:
for key in ("id", "dir", "build", "jar", "menuCategory"):
if key not in p:
print(f"ERRO: campo ausente {key} em {p}", file=sys.stderr)
sys.exit(1)
sys.exit(0)
if mode == "list":
for p in plugins:
jp = jar_path(p)
built = "sim" if jp.is_file() else "não"
print(f"{p['id']}\t{p['menuCategory']}\t{p['dir']}\tjar:{built}")
sys.exit(0)
if mode == "discover":
known_dirs = {p["dir"] for p in plugins}
candidates = []
plugins_root = repo / "plugins"
if plugins_root.is_dir():
candidates.extend(sorted(plugins_root.iterdir()))
for child in sorted(repo.glob("pitiupi.*")):
if child.is_dir():
candidates.append(child)
seen = set()
for child in candidates:
if not child.is_dir():
continue
key = child.relative_to(repo).as_posix()
if key in seen:
continue
seen.add(key)
spi = list(child.rglob("META-INF/services/pitiupi.models.Plugin"))
if not spi:
continue
rel = key
status = "registrado" if rel in known_dirs else "sugerido"
build = "make" if (child / "Makefile").is_file() else "ant"
print(f"{status}\t{rel}\tbuild:{build}")
sys.exit(0)
if mode == "resolve":
filter_mode = sys.argv[4]
arg = sys.argv[5] if len(sys.argv) > 5 else ""
selected = []
if filter_mode == "all":
selected = plugins
elif filter_mode == "category":
cat = arg.strip()
selected = [p for p in plugins if p["menuCategory"] == cat]
if not selected:
print(f"ERRO: categoria vazia ou inválida: {cat}", file=sys.stderr)
sys.exit(1)
elif filter_mode == "plugins":
ids = [x.strip() for x in arg.lower().split(",") if x.strip()]
for i in ids:
p = by_id(i)
if not p:
print(f"ERRO: id desconhecido no catálogo: {i}", file=sys.stderr)
sys.exit(1)
selected.append(p)
else:
print(f"ERRO: modo resolve {filter_mode}", file=sys.stderr)
sys.exit(1)
for p in selected:
print(p["id"])
sys.exit(0)
if mode == "field":
pid = sys.argv[4]
field = sys.argv[5]
p = by_id(pid)
if not p:
sys.exit(1)
print(p[field])
sys.exit(0)
print(f"ERRO: modo desconhecido {mode}", file=sys.stderr)
sys.exit(1)
PY
}
resolve_filter_mode() {
if [[ -n "${FILTER_PLUGINS:-}" ]]; then
echo "plugins"
elif [[ -n "${FILTER_CATEGORY:-}" ]]; then
echo "category"
else
echo "all"
fi
}
resolve_filter_arg() {
local mode="$1"
case "$mode" in
plugins) echo "${FILTER_PLUGINS}" ;;
category) echo "${FILTER_CATEGORY}" ;;
all) echo "" ;;
esac
}
selected_ids() {
local mode arg
mode="$(resolve_filter_mode)"
arg="$(resolve_filter_arg "$mode")"
registry_python resolve "$mode" "$arg"
}
build_core() {
(cd "${REPO_ROOT}/pitiupi" && ant jar)
}
build_one_plugin() {
local id="$1"
local dir build_cmd marker
dir="$(registry_python field "$id" dir)"
build_cmd="$(registry_python field "$id" build)"
case "$build_cmd" in
ant) marker="${REPO_ROOT}/${dir}/build.xml" ;;
make) marker="${REPO_ROOT}/${dir}/Makefile" ;;
*)
printf 'ERRO: build desconhecido para %s: %s\n' "$id" "$build_cmd" >&2
exit 1
;;
esac
if [[ ! -f "${marker}" ]]; then
printf 'Plugin %s ausente nesta máquina; não será carregado.\n' "$id"
return 0
fi
case "$build_cmd" in
ant) (cd "${REPO_ROOT}/${dir}" && ant jar) ;;
make) (cd "${REPO_ROOT}/${dir}" && make) ;;
esac
}
build_plugins_from_selection() {
local id
while IFS= read -r id; do
[[ -z "$id" ]] && continue
build_one_plugin "$id"
done < <(selected_ids)
}
running_on_wsl() {
[[ -r /proc/version ]] && grep -qi microsoft /proc/version
}
running_on_git_bash() {
[[ "${OSTYPE:-}" == msys* || "${OSTYPE:-}" == mingw* ]]
}
to_windows_path() {
if running_on_wsl && command -v wslpath >/dev/null 2>&1; then
wslpath -w "$1"
elif command -v cygpath >/dev/null 2>&1; then
cygpath -w "$1"
else
printf 'ERRO: não sei converter o caminho para o Windows: %s\n' "$1" >&2
exit 1
fi
}
link_plugins_from_selection() {
local mode="${1:-link}"
local id dir jar_rel link_name src_abs
mkdir -p "${PLUGINS_DIR}"
rm -f "${PLUGINS_DIR}/"*.jar
while IFS= read -r id; do
[[ -z "$id" ]] && continue
dir="$(registry_python field "$id" dir)"
jar_rel="$(registry_python field "$id" jar)"
link_name="${id}.jar"
src_abs="${REPO_ROOT}/${dir}/${jar_rel}"
if [[ ! -f "${src_abs}" ]]; then
local marker=""
local build_cmd
build_cmd="$(registry_python field "$id" build)"
case "$build_cmd" in
ant) marker="${REPO_ROOT}/${dir}/build.xml" ;;
make) marker="${REPO_ROOT}/${dir}/Makefile" ;;
esac
if [[ -z "${marker}" || ! -f "${marker}" ]]; then
printf 'Plugin %s ausente nesta máquina; não será carregado.\n' "$id"
continue
fi
printf 'ERRO: JAR ausente: %s\n' "${src_abs}" >&2
exit 1
fi
if [[ "${mode}" == "copy" ]]; then
cp -L "${src_abs}" "${PLUGINS_DIR}/${link_name}"
else
ln -sf "${src_abs}" "${PLUGINS_DIR}/${link_name}"
fi
done < <(selected_ids)
local count
count="$(find "${PLUGINS_DIR}" -maxdepth 1 -name '*.jar' 2>/dev/null | wc -l)"
if [[ "$count" -eq 0 ]]; then
printf 'AVISO: nenhum plugin em %s.\n' "${PLUGINS_DIR}" >&2
else
printf 'Plugins ativos em %s:\n' "${PLUGINS_DIR}"
ls -1 "${PLUGINS_DIR}"/*.jar
if [[ "${mode}" != "copy" ]]; then
local jar
for jar in "${PLUGINS_DIR}"/*.jar; do
[[ -L "$jar" ]] && printf ' -> %s\n' "$(readlink -f "$jar")"
done
fi
fi
}
launch_windows_java() {
local home_win jar_win ps1
if ! command -v powershell.exe >/dev/null 2>&1; then
printf 'ERRO: o PowerShell do Windows não está no PATH.\n' >&2
exit 1
fi
home_win="$(to_windows_path "${PITIUPI_HOME}")"
jar_win="$(to_windows_path "${CORE}/dist/pitiupi.jar")"
ps1="$(to_windows_path "${REPO_ROOT}/scripts/lib/launch-windows.ps1")"
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "${ps1}" -PitiupiHome "${home_win}" -Jar "${jar_win}"
}
check_tools() {
local ok=0
for cmd in java javac ant make python3; do
if command -v "${cmd}" >/dev/null 2>&1; then
printf 'OK %s\n' "${cmd}"
else
printf 'ERRO: %s não está no PATH\n' "${cmd}" >&2
ok=1
fi
done
if [[ ! -f "${REGISTRY}" ]]; then
printf 'ERRO: catálogo ausente: %s\n' "${REGISTRY}" >&2
ok=1
else
registry_python validate
printf 'OK plugins.registry.json\n'
fi
if command -v javac >/dev/null 2>&1; then
local javac_major
javac_major="$(javac -version 2>&1 | grep -oE '[0-9]+' | head -1)"
if [[ -n "${javac_major}" && "${javac_major}" -lt 17 ]]; then
printf 'ERRO: javac precisa ser Java 17 ou superior (atual: %s)\n' "$(javac -version 2>&1)" >&2
ok=1
fi
fi
return "${ok}"
}
CMD="run"
FILTER_PLUGINS=""
FILTER_CATEGORY=""
USE_ALL=1
while [[ $# -gt 0 ]]; do
case "$1" in
run | build | link | check | list | discover)
CMD="$1"
shift
;;
--all)
USE_ALL=1
FILTER_PLUGINS=""
FILTER_CATEGORY=""
shift
;;
--plugins)
USE_ALL=0
FILTER_PLUGINS="${2:-}"
FILTER_CATEGORY=""
shift 2
;;
--category)
USE_ALL=0
FILTER_CATEGORY="${2:-}"
FILTER_PLUGINS=""
shift 2
;;
-h | --help)
usage
exit 0
;;
*)
printf 'Argumento desconhecido: %s\n' "$1" >&2
usage >&2
exit 1
;;
esac
done
if [[ "$USE_ALL" -eq 1 && -z "$FILTER_PLUGINS" && -z "$FILTER_CATEGORY" ]]; then
: # default --all
elif [[ -n "$FILTER_PLUGINS" && -n "$FILTER_CATEGORY" ]]; then
printf 'ERRO: use apenas um de --plugins, --category ou --all\n' >&2
exit 1
fi
case "${CMD}" in
check)
check_tools
;;
list)
registry_python list
;;
discover)
registry_python discover
;;
link)
link_plugins_from_selection
printf '\nPara abrir o Pitiupi: %s run --all\n' "${REPO_ROOT}/scripts/pitiupi.sh"
;;
build)
check_tools || exit 1
build_core
build_plugins_from_selection
link_plugins_from_selection
;;
run)
check_tools || exit 1
build_core
build_plugins_from_selection
if running_on_wsl || running_on_git_bash; then
link_plugins_from_selection copy
launch_windows_java
printf 'Janela aberta no Windows. Cabo e Wi-Fi são as placas desta máquina.\n'
printf 'Plugins em %s\n' "${PLUGINS_DIR}"
else
link_plugins_from_selection link
exec java -jar "${CORE}/dist/pitiupi.jar"
fi
;;
esac