feat: corrigida a resolução do Node.js ao executar planos Python p

- corrigida a resolução do Node.js ao executar planos Python pelo painel CEP

Resumo:
- 2 arquivos alterados
- 1 novos
- 1 modificados
- 0 removidos

 1 file changed, 38 insertions(+), 1 deletion(-)

Arquivos:
  - code/engine/aplicar_plano_de_edicao.py
  - code/engine/testes/test_aplicar_plano_de_edicao.py
This commit is contained in:
João Henrique
2026-09-09 12:04:28 -04:00
parent 73bfb05f52
commit fd3440bcc4
2 changed files with 77 additions and 1 deletions
+38 -1
View File
@@ -18,6 +18,8 @@ troca de sequência.
from __future__ import annotations
import json
import os
import shutil
import sys
from pathlib import Path
@@ -41,6 +43,40 @@ from engine.integracoes.premiere.sessao_mcp import SessaoMCP
CAMINHO_DO_SERVIDOR_MCP = CAMINHO_DO_CODIGO / "dist" / "index.js"
def resolver_caminho_do_node() -> str:
"""Resolve o executável Node mesmo quando o painel CEP possui um PATH reduzido.
A variável ``PREMIERE_NODE_BIN`` permite que o painel ou o sistema informe
um caminho explícito. Em seguida, a resolução tenta o ``PATH`` e os
caminhos comuns de instalação local. Levanta :class:`FileNotFoundError`
com orientação acionável quando o Node não está disponível.
"""
caminho_configurado = os.environ.get("PREMIERE_NODE_BIN", "").strip()
if caminho_configurado:
if os.path.isfile(caminho_configurado) and os.access(caminho_configurado, os.X_OK):
return caminho_configurado
raise FileNotFoundError(
f"Node.js configurado em PREMIERE_NODE_BIN não é executável: {caminho_configurado}"
)
encontrado_no_path = shutil.which("node.exe" if os.name == "nt" else "node")
if encontrado_no_path:
return encontrado_no_path
caminhos_conhecidos = (
("C:/Program Files/nodejs/node.exe", "C:/Program Files/nodejs/nodejs.exe")
if os.name == "nt"
else ("/opt/homebrew/bin/node", "/usr/local/bin/node", "/usr/bin/node")
)
for caminho in caminhos_conhecidos:
if os.path.isfile(caminho) and os.access(caminho, os.X_OK):
return caminho
raise FileNotFoundError(
"Node.js não encontrado. Instale o Node.js ou configure PREMIERE_NODE_BIN "
"com o caminho completo do executável."
)
def executar(caminho_do_plano: Path) -> dict:
"""Lê o plano em ``caminho_do_plano`` e o aplica na sequência ativa do Premiere.
@@ -52,7 +88,8 @@ def executar(caminho_do_plano: Path) -> dict:
# A pausa é aplicada no cliente, e não dentro do aplicador, para valer
# igualmente nas leituras da timeline e nas escritas — são todas
# round-trips síncronos no processo principal do Premiere.
cliente = ClienteMCPComPausa(ClienteMCPPorStdio(["node", str(CAMINHO_DO_SERVIDOR_MCP)]))
caminho_do_node = resolver_caminho_do_node()
cliente = ClienteMCPComPausa(ClienteMCPPorStdio([caminho_do_node, str(CAMINHO_DO_SERVIDOR_MCP)]))
with SessaoMCP(cliente):
acesso_ao_editor = AcessoAoEditor(AcessoATimeline(cliente))
conversor_de_timeline = ConversorDeTimeline()
@@ -0,0 +1,39 @@
"""Testes da resolução do executável Node usada pelo aplicador de planos."""
import os
import unittest
from unittest.mock import patch
from engine import aplicar_plano_de_edicao
class TesteResolucaoDoNode(unittest.TestCase):
"""Garante que a engine não dependa do PATH herdado pelo painel CEP."""
def test_prioriza_executavel_configurado_no_ambiente(self):
"""Usa o caminho explícito quando o painel ou sistema o fornece."""
with patch.dict(os.environ, {"PREMIERE_NODE_BIN": "/custom/node"}, clear=False), patch(
"engine.aplicar_plano_de_edicao.os.path.isfile", return_value=True
), patch("engine.aplicar_plano_de_edicao.os.access", return_value=True):
self.assertEqual(aplicar_plano_de_edicao.resolver_caminho_do_node(), "/custom/node")
def test_usa_caminho_conhecido_quando_node_nao_esta_no_path(self):
"""Encontra a instalação Homebrew mesmo com PATH incompleto."""
with patch.dict(os.environ, {}, clear=True), patch(
"engine.aplicar_plano_de_edicao.shutil.which", return_value=None
), patch("engine.aplicar_plano_de_edicao.os.path.isfile", side_effect=lambda caminho: caminho == "/opt/homebrew/bin/node"), patch(
"engine.aplicar_plano_de_edicao.os.access", return_value=True
):
self.assertEqual(aplicar_plano_de_edicao.resolver_caminho_do_node(), "/opt/homebrew/bin/node")
def test_informa_instalacao_ausente(self):
"""Levanta um erro explicativo quando nenhum executável existe."""
with patch.dict(os.environ, {}, clear=True), patch(
"engine.aplicar_plano_de_edicao.shutil.which", return_value=None
), patch("engine.aplicar_plano_de_edicao.os.path.isfile", return_value=False):
with self.assertRaisesRegex(FileNotFoundError, "Node.js não encontrado"):
aplicar_plano_de_edicao.resolver_caminho_do_node()
if __name__ == "__main__":
unittest.main()