Skip to content

Integrate payment-templates-validator for local validation #trivial - #8

Open
carolkrroo wants to merge 1 commit into
feat/dynamic-payment-mockerfrom
feat/integrate-validator
Open

Integrate payment-templates-validator for local validation #trivial#8
carolkrroo wants to merge 1 commit into
feat/dynamic-payment-mockerfrom
feat/integrate-validator

Conversation

@carolkrroo

@carolkrroo carolkrroo commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Wire @vtex/payment-templates-validator into a shared pipeline (lib/validate-bundle.js) that builds full ValidationInput from the local bundle, icon, and displayName
  • Expose validation via npm run validate (strict), grunt/validate:soft (dev server keeps running), /validation.json, and a live banner in the checkout shell
  • Console and --json output use the same { ok, errors } model as upload validation (rule, severity, message, ref)

Test plan

  • npm run validate passes on template/reference/
  • npm run validate -- --json prints raw { ok, errors }
  • yarn grunt starts the dev server even when validation fails; terminal and banner show findings
  • Edit a template file (e.g. add a forbidden tag) — banner updates within ~1.5s and iframe reloads
  • curl http://localhost:8080/validation.json returns validator output
  • Payment tab icon and template images render in the preview iframe

Template with error
Screenshot 2026-08-14 at 4 22 19 PM

Template without error
Screenshot 2026-08-14 at 4 23 26 PM

Error on terminal while running the app
Screenshot 2026-08-14 at 4 23 59 PM

Error on terminal
Screenshot 2026-08-14 at 4 25 41 PM

Wire the shared validator into CLI, Grunt, /validation.json, and a live checkout-shell banner so partners get the same { ok, errors } output as upload validation.
@vtex-pr-sentinel

vtex-pr-sentinel Bot commented Aug 14, 2026

Copy link
Copy Markdown

🛡️ SDD Check — action required

I couldn't detect an SDD in this PR. Please check one option below (requires write access to the repo):

  • SDD lives in another PR — paste the SDD PR URL here:
  • This PR doesn't need an SDD
  • SDD applies, but I'm not adopting it in this PR

@carolkrroo carolkrroo self-assigned this Aug 14, 2026
@carolkrroo
carolkrroo requested review from fdaciuk and huandrey August 14, 2026 19:21
@carolkrroo carolkrroo changed the title Integrate payment-templates-validator for local validation Integrate payment-templates-validator for local validation #trivial Aug 14, 2026

@fdaciuk fdaciuk left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alguns pontos bloqueantes: injeção de HTML no banner do host, o servidor de dev morre com preview.config.json inválido (o oposto do que validate:soft promete), displayName fora do formato esperado é ignorado em silêncio e o endpoint novo devolve caminho absoluto na rede. Detalhes nos comentários inline.

}).join('');

validationBanner.className = 'payment-template-validation-banner' + (result.ok ? ' has-warnings' : '');
validationBanner.innerHTML = '<strong>' + title + '</strong><ul>' + items + '</ul>';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O innerHTML recebe finding.message e finding.ref.file sem escape. As mensagens do validador embutem trechos do template (tag, classe, valor de atributo) e ref.file é um nome de arquivo do bundle, então markup vindo do template executa aqui — e este banner fica na página do host, fora do sandbox="allow-scripts" que só protege o iframe (linha 228). Mesmo sem má intenção quebra a interface: uma mensagem contendo <script> engole o resto do <ul>.

Escapar &, <, >, " antes de concatenar, ou montar os <li> com textContent.

if (!iframe) return;
appliedHeight = 0;
iframe.src = IFRAME_SRC + '?' + Date.now();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depois desta recarga o listener de load da linha 239 já se removeu (linha 240), então setLocale() não roda de novo e o iframe reinicia em defaultLocale (lib/wrapper-runtime.js, applyLocale(payload.defaultLocale)). Se o usuário tiver trocado para en-US e editado um arquivo do template, o iframe volta para pt-BR enquanto o rótulo do meio de pagamento continua em en-US.

Reaplicar currentLocale após o reload.

function applyValidationResult(result, options) {
options = options || {};
var signature = validationSignature(result);
renderValidationBanner(result);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renderiza o banner em toda verificação, mesmo quando o resultado é idêntico — a comparação de assinatura da linha 88 só controla a recarga do iframe. Como o elemento tem role="alert" (src/index.html:27), o leitor de tela relê a lista inteira a cada 1,5s e a seleção de texto se perde a cada tick.

Renderizar só quando a assinatura mudar, preservando a primeira renderização (options.initial).

renderValidationBanner(null);
}
} else {
renderValidationBanner(null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Qualquer resposta não-2xx cai em "Validation unavailable — restart the dev server", conselho que não se aplica: o servidor está no ar. O message que o middleware monta (lib/validation-middleware.js:21 e :34) nunca chega à tela — com um ícone renomeado o usuário lê "reinicie o servidor" em vez de "icon.jpg não encontrado".

Ler o corpo da resposta de erro e exibir o message.

function startValidationPolling() {
loadValidationResult({ initial: true }, function () {});
if (validationTimer) clearInterval(validationTimer);
validationTimer = setInterval(function () {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sem guarda de requisição em voo e sem clearInterval ao sair da página. Como o servidor revalida o bundle inteiro em cada chamada (lib/validation-middleware.js:25), as requisições se acumulam e disputam o mesmo processo que serve o iframe e o livereload.

Ignorar o tick enquanto houver requisição pendente e limpar o timer no beforeunload.

.catch(function (error) {
res.statusCode = 500;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify({ ok: false, errors: [], message: error.message }));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error.message vai cru para a rede e carrega o caminho absoluto: readFileEntry monta 'File not found: ' + name + ' in ' + dir, onde dir é .../payment-mocker/template/reference sob o home do usuário. Com hostname: '*' no Gruntfile.js:59, qualquer máquina na mesma rede faz GET /validation.json e recebe nome de usuário e diretório.

Devolver mensagem genérica na resposta e deixar o detalhe apenas no terminal. Mesmo caso na linha 21.

Comment thread scripts/validate.js
process.exit(result.ok ? 0 : 1);
}

main().catch(function (error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No modo --json este caminho não imprime nada no stdout — só a pilha no stderr. Um passo de CI com npm run validate -- --json > result.json fica com arquivo vazio para parsear. A flag json é lida na linha 8 e não é consultada aqui.

Imprimir { ok: false, errors: [], message } no stdout, como o middleware já faz.

Comment thread scripts/write-png.js

writePng(path.join(root, 'asset-logo.png'), 120, 60);
writePng(path.join(root, 'asset-badge.png'), 80, 80);
writePng(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O gerador deixou de criar asset-logo.png e asset-badge.png, mas o bundle de referência continua carregando os dois: template/reference/index.html:3 (src="asset-logo.png") e template/reference/style.css:44,49,54 (url(asset-badge.png)). Nada quebra hoje só porque os dois PNGs estão comitados.

E o icon.png gerado aqui não é usado, porque template/preview.config.json aponta para icon.jpg. Como está, npm run generate:assets não reconstitui o bundle como a descrição do arquivo afirma.

Comment thread template/CONTRACT.md
Failed run:

```
validate: failed (1 error)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A saída documentada não corresponde à real. lib/format-validation-output.js:69 sempre concatena o sufixo no resumo e scripts/validate.js:14 sempre passa um, então a linha é validate: failed (1 error) — template at template/reference (o exemplo de sucesso na linha 102 já traz o sufixo). A linha em branco seguinte também não é impressa.

Além disso o texto Forbidden tag <script> in index.html não parece ser a mensagem que @vtex/payment-templates-validator emite — vale colar a saída de uma execução real.

Comment thread template/CONTRACT.md
"bundleDir": "reference",
"defaultLocale": "pt-BR",
"icon": "asset-logo.png",
"icon": "icon.png",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O exemplo documenta "icon": "icon.png", mas o template/preview.config.json entregue no PR usa "icon": "icon.jpg". Alinhar os dois para o parceiro não copiar um valor que não corresponde ao bundle de referência.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants