-
Notifications
You must be signed in to change notification settings - Fork 7
Integrate payment-templates-validator for local validation #trivial #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/dynamic-payment-mocker
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| 'use strict'; | ||
|
|
||
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| const { loadBundleForValidation } = require('./load-bundle'); | ||
|
|
||
| function readFileEntry(dir, name) { | ||
| const filePath = path.join(dir, name); | ||
| if (!fs.existsSync(filePath) || fs.statSync(filePath).isDirectory()) { | ||
| throw new Error('File not found: ' + name + ' in ' + dir); | ||
| } | ||
|
|
||
| const buffer = fs.readFileSync(filePath); | ||
| return { | ||
| name: path.basename(name), | ||
| size: buffer.byteLength, | ||
| buffer: new Uint8Array(buffer), | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Maps preview.config.json + bundle folder into ValidationInput for | ||
| * @vtex/payment-templates-validator — same shape the upload handler consumes. | ||
| */ | ||
| function buildValidationInput(config) { | ||
| const template = loadBundleForValidation(config.bundlePath, config.defaultLocale); | ||
| const input = { template }; | ||
|
|
||
| if (config.icon) { | ||
| const iconName = String(config.icon).replace(/^\.\//, ''); | ||
| input.icon = readFileEntry(config.bundlePath, iconName); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
|
|
||
| if (config.displayName && typeof config.displayName === 'object') { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Um Validar o tipo e reportar, ou repassar o valor e deixar o validador emitir a mensagem precisa. |
||
| input.displayName = config.displayName; | ||
| } | ||
|
|
||
| return input; | ||
| } | ||
|
|
||
| module.exports = { | ||
| buildValidationInput, | ||
| readFileEntry, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| 'use strict'; | ||
|
|
||
| function formatRef(ref) { | ||
| if (!ref || !ref.file) { | ||
| return ''; | ||
| } | ||
|
|
||
| if (ref.line == null) { | ||
| return ref.file; | ||
| } | ||
|
|
||
| if (ref.column == null) { | ||
| return ref.file + ':' + ref.line; | ||
| } | ||
|
|
||
| return ref.file + ':' + ref.line + ':' + ref.column; | ||
| } | ||
|
|
||
| function formatFinding(finding) { | ||
| var location = formatRef(finding.ref); | ||
| var prefix = '[' + finding.severity + '] ' + finding.rule; | ||
| if (location) { | ||
| prefix += ' ' + location; | ||
| } | ||
| return prefix + ' — ' + finding.message; | ||
| } | ||
|
|
||
| function countBySeverity(findings, severity) { | ||
| var count = 0; | ||
| for (var i = 0; i < findings.length; i += 1) { | ||
| if (findings[i].severity === severity) { | ||
| count += 1; | ||
| } | ||
| } | ||
| return count; | ||
| } | ||
|
|
||
| function printValidationResult(result, options) { | ||
| options = options || {}; | ||
|
|
||
| if (options.json) { | ||
| process.stdout.write(JSON.stringify(result, null, 2) + '\n'); | ||
| return; | ||
| } | ||
|
|
||
| var errorCount = countBySeverity(result.errors, 'error'); | ||
| var warningCount = countBySeverity(result.errors, 'warning'); | ||
| var suffix = options.suffix || ''; | ||
|
|
||
| if (result.ok) { | ||
| var okMessage = 'validate: ok'; | ||
| if (warningCount) { | ||
| okMessage += ' (' + warningCount + ' warning' + (warningCount === 1 ? '' : 's') + ')'; | ||
| } | ||
| console.log(okMessage + suffix); | ||
|
|
||
| for (var w = 0; w < result.errors.length; w += 1) { | ||
| if (result.errors[w].severity === 'warning') { | ||
| console.log(' ' + formatFinding(result.errors[w])); | ||
| } | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| var summary = 'validate: failed (' + errorCount + ' error' + (errorCount === 1 ? '' : 's'); | ||
| if (warningCount) { | ||
| summary += ', ' + warningCount + ' warning' + (warningCount === 1 ? '' : 's'); | ||
| } | ||
| summary += ')' + suffix; | ||
| console.error(summary); | ||
|
|
||
| for (var i = 0; i < result.errors.length; i += 1) { | ||
| var finding = result.errors[i]; | ||
| var line = ' ' + formatFinding(finding); | ||
| if (finding.severity === 'error') { | ||
| console.error(line); | ||
| } else { | ||
| console.log(line); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| module.exports = { | ||
| formatFinding, | ||
| formatRef, | ||
| printValidationResult, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| 'use strict'; | ||
|
|
||
| const { validate } = require('@vtex/payment-templates-validator'); | ||
| const { readPreviewConfig } = require('./preview-config'); | ||
| const { buildValidationInput } = require('./build-validation-input'); | ||
|
|
||
| /** | ||
| * Runs the shared validator against the configured preview bundle. | ||
| * Returns ValidateResult { ok, errors } — identical to server-side output. | ||
| */ | ||
| async function validateBundle(config) { | ||
| const resolvedConfig = config || readPreviewConfig(); | ||
| const input = buildValidationInput(resolvedConfig); | ||
| return validate(input); | ||
| } | ||
|
|
||
| module.exports = { | ||
| buildValidationInput, | ||
| validateBundle, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| 'use strict'; | ||
|
|
||
| const { readPreviewConfig } = require('./preview-config'); | ||
| const { validateBundle } = require('./validate-bundle'); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| const VALIDATION_PATH = '/validation.json'; | ||
|
|
||
| function createValidationMiddleware() { | ||
| return function validationMiddleware(req, res, next) { | ||
| if (!req.url || req.url.split('?')[0] !== VALIDATION_PATH) { | ||
| next(); | ||
| return; | ||
| } | ||
|
|
||
| let config; | ||
| try { | ||
| config = readPreviewConfig(); | ||
| } catch (error) { | ||
| res.statusCode = 500; | ||
| res.setHeader('Content-Type', 'application/json; charset=utf-8'); | ||
| res.end(JSON.stringify({ ok: false, errors: [], message: error.message })); | ||
| return; | ||
| } | ||
|
|
||
| validateBundle(config) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Relê o bundle do disco e roda o validador completo em cada requisição, sem cache. O trabalho pesado do validador (parse do HTML e do CSS, varredura de bytes dos assets) é síncrono, então este processo — que também serve o iframe e o livereload — fica bloqueado a cada chamada, e o cliente chama a cada 1,5s por aba aberta ( Cachear o resultado invalidando por |
||
| .then(function (result) { | ||
| res.setHeader('Content-Type', 'application/json; charset=utf-8'); | ||
| res.setHeader('Cache-Control', 'no-cache'); | ||
| res.end(JSON.stringify(result)); | ||
| }) | ||
| .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 })); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Devolver mensagem genérica na resposta e deixar o detalhe apenas no terminal. Mesmo caso na linha 21. |
||
| }); | ||
| }; | ||
| } | ||
|
|
||
| module.exports = { | ||
| VALIDATION_PATH, | ||
| createValidationMiddleware, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,29 +1,3 @@ | ||
| 'use strict'; | ||
|
|
||
| const { validate } = require('@vtex/payment-templates-validator'); | ||
| const { loadBundleForValidation } = require('../lib/load-bundle'); | ||
| const { readPreviewConfig } = require('../lib/preview-config'); | ||
|
|
||
| async function main() { | ||
| const config = readPreviewConfig(); | ||
| const template = loadBundleForValidation(config.bundlePath, config.defaultLocale); | ||
|
|
||
| const result = await validate({ template }); | ||
|
|
||
| if (result.ok) { | ||
| console.log('validate: ok — template at template/' + config.bundleDir + ' passed all applicable rules.'); | ||
| process.exit(0); | ||
| } | ||
|
|
||
| console.error('validate: failed'); | ||
| for (const finding of result.errors) { | ||
| const ref = finding.ref ? ' (' + finding.ref.file + ')' : ''; | ||
| console.error(' [' + finding.rule + '] ' + finding.message + ref); | ||
| } | ||
| process.exit(1); | ||
| } | ||
|
|
||
| main().catch(function (error) { | ||
| console.error(error); | ||
| process.exit(1); | ||
| }); | ||
| require('./validate.js'); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| 'use strict'; | ||
|
|
||
| const { validateBundle } = require('../lib/validate-bundle'); | ||
| const { printValidationResult } = require('../lib/format-validation-output'); | ||
| const { readPreviewConfig } = require('../lib/preview-config'); | ||
|
|
||
| async function main() { | ||
| const json = process.argv.indexOf('--json') !== -1; | ||
| const config = readPreviewConfig(); | ||
| const result = await validateBundle(config); | ||
|
|
||
| printValidationResult(result, { | ||
| json: json, | ||
| suffix: ' — template at template/' + config.bundleDir, | ||
| }); | ||
|
|
||
| process.exit(result.ok ? 0 : 1); | ||
| } | ||
|
|
||
| main().catch(function (error) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No modo Imprimir |
||
| console.error(error); | ||
| process.exit(1); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Esta chamada está fora da cadeia de promises e fora de
try/catch. Compreview.config.jsonmalformado, ausente ou sembundleDir/defaultLocale, ela lança de forma síncrona: o grunt aborta a task,done()não é chamado econnect/watchnão sobem — sem servidor e sem banner, exatamente o oposto do quevalidate:softpromete. Todos os outros erros nascem dentro devalidateBundle()e o.catchda linha 41 trata; só esta linha escapa.Vale notar que é uma regressão: a versão anterior rodava a validação em processo separado (
spawnSync), então esse erro ficava contido. Envolver emtry/catche, no modosoft, logar e chamardone().