feat(v2): add build script that inlines tested logic/*.js into n8n Code nodes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
00bb89f4f4
commit
ec596a29a8
File diff suppressed because one or more lines are too long
@@ -0,0 +1,94 @@
|
|||||||
|
{
|
||||||
|
"name": "[DEV] Coordenador GLPI - Chamados Abertos v2 (Shadow)",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "manual-trigger-v2",
|
||||||
|
"name": "Manual Trigger",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [180, 300],
|
||||||
|
"parameters": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "schedule-trigger-v2-desligado",
|
||||||
|
"name": "Schedule Trigger - Dias uteis 08h (desligado em DEV)",
|
||||||
|
"type": "n8n-nodes-base.scheduleTrigger",
|
||||||
|
"typeVersion": 1.2,
|
||||||
|
"position": [180, 640],
|
||||||
|
"parameters": {
|
||||||
|
"rule": {
|
||||||
|
"interval": [
|
||||||
|
{ "field": "cronExpression", "expression": "0 8 * * 1-5" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "config-v2",
|
||||||
|
"name": "Config - Coordenador GLPI v2",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 3.4,
|
||||||
|
"position": [440, 300],
|
||||||
|
"parameters": {
|
||||||
|
"assignments": {
|
||||||
|
"assignments": [
|
||||||
|
{
|
||||||
|
"id": "glpi-base-url-v2",
|
||||||
|
"name": "glpi_base_url",
|
||||||
|
"type": "string",
|
||||||
|
"value": "https://glpi.suaempresa.com.br"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "code-montar-sql-candidatos",
|
||||||
|
"name": "Code - Montar SQL Candidatos",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [700, 220],
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "__BUILD__:build-sql-candidatos.node.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "code-montar-sql-legado",
|
||||||
|
"name": "Code - Montar SQL Legado",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [700, 460],
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "__BUILD__:build-sql-legado.node.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": {
|
||||||
|
"Manual Trigger": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{ "node": "Config - Coordenador GLPI v2", "type": "main", "index": 0 }
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Config - Coordenador GLPI v2": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{ "node": "Code - Montar SQL Candidatos", "type": "main", "index": 0 },
|
||||||
|
{ "node": "Code - Montar SQL Legado", "type": "main", "index": 0 }
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pinData": {},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "",
|
||||||
|
"meta": {
|
||||||
|
"templateCredsSetupCompleted": false
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
|
||||||
|
const ROOT = path.join(__dirname, '..');
|
||||||
|
|
||||||
|
function readLogicSource(fileName) {
|
||||||
|
const raw = fs.readFileSync(path.join(ROOT, 'logic', fileName), 'utf8');
|
||||||
|
// Remove a linha final "module.exports = {...}" -- as funcoes ficam soltas no escopo do node.
|
||||||
|
return raw.replace(/module\.exports\s*=\s*\{[\s\S]*?\};?\s*$/m, '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function inlinedLogic(fileNames) {
|
||||||
|
return fileNames.map(readLogicSource).join('\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlAsJsTemplateLiteral(sqlRelativePath) {
|
||||||
|
const sql = fs.readFileSync(path.join(ROOT, sqlRelativePath), 'utf8');
|
||||||
|
const escaped = sql.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
||||||
|
return `\`${escaped}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDriverCode(driverRelativePath) {
|
||||||
|
const driverPath = path.join(ROOT, 'n8n', 'nodes', driverRelativePath);
|
||||||
|
let driver = fs.readFileSync(driverPath, 'utf8');
|
||||||
|
|
||||||
|
const requiresMatch = driver.match(/^\/\/ @requires (.+)$/m);
|
||||||
|
const requiredLogicFiles = requiresMatch ? requiresMatch[1].trim().split(/\s+/) : [];
|
||||||
|
driver = driver.replace(/^\/\/ @requires .+\n/m, '');
|
||||||
|
|
||||||
|
driver = driver.replace('/* __SQL_CANDIDATOS_V2__ */', sqlAsJsTemplateLiteral('sql/candidatos_v2_glpi.sql'));
|
||||||
|
driver = driver.replace('/* __SQL_LEGADO_V1_VERBATIM__ */', sqlAsJsTemplateLiteral('sql/top_10_chamados_abertos_glpi.sql'));
|
||||||
|
driver = driver.replace('/* __SQL_CONTEXTO_FOLLOWUPS__ */', sqlAsJsTemplateLiteral('sql/contexto_followups_glpi.sql'));
|
||||||
|
driver = driver.replace('/* __SQL_CONTEXTO_TAREFAS__ */', sqlAsJsTemplateLiteral('sql/contexto_tarefas_glpi.sql'));
|
||||||
|
|
||||||
|
const logicBlock = requiredLogicFiles.length ? `${inlinedLogic(requiredLogicFiles)}\n\n` : '';
|
||||||
|
return `${logicBlock}${driver.trim()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildWorkflow() {
|
||||||
|
const templatePath = path.join(ROOT, 'n8n', 'workflow.template.json');
|
||||||
|
const template = JSON.parse(fs.readFileSync(templatePath, 'utf8'));
|
||||||
|
|
||||||
|
for (const node of template.nodes) {
|
||||||
|
if (node.type === 'n8n-nodes-base.code' && typeof node.parameters.jsCode === 'string') {
|
||||||
|
const marker = node.parameters.jsCode.match(/^__BUILD__:(.+)$/);
|
||||||
|
if (marker) {
|
||||||
|
node.parameters.jsCode = buildDriverCode(marker[1].trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
process.stdout.write(JSON.stringify(buildWorkflow(), null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { buildWorkflow, inlinedLogic, readLogicSource, sqlAsJsTemplateLiteral, buildDriverCode };
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
const { test } = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const { buildWorkflow } = require('./build-workflow');
|
||||||
|
|
||||||
|
test('workflow montado tem nome e nodes esperados', () => {
|
||||||
|
const wf = buildWorkflow();
|
||||||
|
assert.equal(wf.name, '[DEV] Coordenador GLPI - Chamados Abertos v2 (Shadow)');
|
||||||
|
const names = wf.nodes.map((n) => n.name);
|
||||||
|
assert.ok(names.includes('Code - Montar SQL Candidatos'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Code - Montar SQL Candidatos tem a logica de config e o SQL inlined (sem marcador de build)', () => {
|
||||||
|
const wf = buildWorkflow();
|
||||||
|
const node = wf.nodes.find((n) => n.name === 'Code - Montar SQL Candidatos');
|
||||||
|
assert.ok(node.parameters.jsCode.includes('function parseConfig'));
|
||||||
|
assert.ok(node.parameters.jsCode.includes('WITH ultima_interacao'));
|
||||||
|
assert.ok(!node.parameters.jsCode.includes('__BUILD__'));
|
||||||
|
assert.ok(!node.parameters.jsCode.includes('__SQL_CANDIDATOS_V2__'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('nenhum node de Code sobra com marcador nao resolvido', () => {
|
||||||
|
const wf = buildWorkflow();
|
||||||
|
for (const node of wf.nodes) {
|
||||||
|
if (node.type === 'n8n-nodes-base.code') {
|
||||||
|
assert.ok(!node.parameters.jsCode.includes('__BUILD__'), `node ${node.name} tem marcador nao resolvido`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user