- Add English/Spanish translation system for UI pages - Add bilingual AI audit notes using translation LLM chains in n8n - Support zero-trust Docker routing to dev stack via internal hostnames - Quiet down Next.js database query and middleware logging in test runs
222 lines
7.8 KiB
JavaScript
222 lines
7.8 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
require('dotenv').config();
|
|
|
|
async function bootstrap() {
|
|
console.log("=== STARTING N8N WORKFLOW BOOTSTRAP / KICKSTART ===");
|
|
|
|
// 1. Fetch n8n API configuration from .agents/mcp_config.json
|
|
const mcpConfigPath = path.join(__dirname, '../.agents/mcp_config.json');
|
|
if (!fs.existsSync(mcpConfigPath)) {
|
|
console.error("Error: .agents/mcp_config.json not found.");
|
|
process.exit(1);
|
|
}
|
|
|
|
const mcpConfig = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf8'));
|
|
const n8nEnv = mcpConfig.mcpServers.n8n.env;
|
|
const n8nUrl = n8nEnv.N8N_API_URL || "https://n8n.gaboggamer.online";
|
|
const n8nApiKey = n8nEnv.N8N_API_KEY;
|
|
|
|
if (!n8nApiKey) {
|
|
console.error("Error: N8N_API_KEY is missing in mcp_config.json.");
|
|
process.exit(1);
|
|
}
|
|
|
|
// 2. Fetch credentials dynamically to resolve AI accounts
|
|
console.log("Fetching credentials from n8n to resolve AI accounts...");
|
|
let deepSeekCred = null;
|
|
let geminiCred = null;
|
|
try {
|
|
const credsRes = await fetch(`${n8nUrl}/api/v1/credentials`, {
|
|
method: 'GET',
|
|
headers: {
|
|
'X-N8N-API-KEY': n8nApiKey
|
|
}
|
|
});
|
|
if (credsRes.ok) {
|
|
const credsData = await credsRes.json();
|
|
const credentials = Array.isArray(credsData.data) ? credsData.data : (credsData.data.credentials || []);
|
|
|
|
const deepSeekCreds = credentials.filter(c => c.type === 'deepSeekApi');
|
|
if (deepSeekCreds.length > 0) {
|
|
deepSeekCred = deepSeekCreds.find(c => c.name.toLowerCase().includes('deepseek')) || deepSeekCreds[0];
|
|
}
|
|
|
|
const geminiCreds = credentials.filter(c => c.type === 'googlePalmApi');
|
|
if (geminiCreds.length > 0) {
|
|
geminiCred = geminiCreds.find(c => c.name.toLowerCase().includes('gemini') || c.name.toLowerCase().includes('google')) || geminiCreds[0];
|
|
}
|
|
|
|
if (deepSeekCred) {
|
|
console.log(`Resolved DeepSeek credential: "${deepSeekCred.name}" (ID: ${deepSeekCred.id})`);
|
|
}
|
|
if (geminiCred) {
|
|
console.log(`Resolved Gemini credential: "${geminiCred.name}" (ID: ${geminiCred.id})`);
|
|
}
|
|
} else {
|
|
console.warn(`Warning: Failed to fetch credentials. Status: ${credsRes.status}`);
|
|
}
|
|
} catch (err) {
|
|
console.warn("Warning: Could not fetch credentials dynamically:", err.message);
|
|
}
|
|
|
|
// 3. Fetch project ID dynamically
|
|
console.log("Fetching workflows list to resolve project ID...");
|
|
const listRes = await fetch(`${n8nUrl}/api/v1/workflows`, {
|
|
method: 'GET',
|
|
headers: {
|
|
'X-N8N-API-KEY': n8nApiKey
|
|
}
|
|
});
|
|
|
|
if (!listRes.ok) {
|
|
console.error(`Failed to list workflows from n8n. Status: ${listRes.status}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const listData = await listRes.json();
|
|
let projectId;
|
|
const firstWf = listData.data.find(w => w.shared && w.shared.length > 0);
|
|
if (firstWf) {
|
|
projectId = firstWf.shared[0].projectId;
|
|
console.log(`Resolved project ID dynamically: ${projectId}`);
|
|
}
|
|
|
|
const workflowsToBootstrap = [
|
|
'sales_import_workflow.json',
|
|
'settlement_calculation_workflow.json'
|
|
];
|
|
|
|
for (const filename of workflowsToBootstrap) {
|
|
console.log(`\n--- Bootstrapping workflow: ${filename} ---`);
|
|
const workflowPath = path.join(__dirname, '../n8n', filename);
|
|
if (!fs.existsSync(workflowPath)) {
|
|
console.error(`Error: ${workflowPath} not found.`);
|
|
continue;
|
|
}
|
|
|
|
const workflowJson = JSON.parse(fs.readFileSync(workflowPath, 'utf8'));
|
|
const targetName = workflowJson.name;
|
|
|
|
// Inject resolved credentials dynamically into the workflow JSON
|
|
for (const node of workflowJson.nodes) {
|
|
if (node.type === '@n8n/n8n-nodes-langchain.chainLlm') {
|
|
// Find deepseek node and gemini node to map credentials
|
|
}
|
|
if (node.type === '@n8n/n8n-nodes-langchain.lmChatDeepSeek' && deepSeekCred) {
|
|
node.credentials = {
|
|
deepSeekApi: {
|
|
id: deepSeekCred.id,
|
|
name: deepSeekCred.name
|
|
}
|
|
};
|
|
console.log(`Injected DeepSeek credential into node "${node.name}" in ${filename}`);
|
|
}
|
|
if (node.type === '@n8n/n8n-nodes-langchain.lmChatGoogleGemini' && geminiCred) {
|
|
node.credentials = {
|
|
googlePalmApi: {
|
|
id: geminiCred.id,
|
|
name: geminiCred.name
|
|
}
|
|
};
|
|
console.log(`Injected Gemini credential into node "${node.name}" in ${filename}`);
|
|
}
|
|
if (node.id === 'Set-Dev-Env') {
|
|
const appUrl = process.env.APP_DEV_INTERNAL_URL || "http://special-hotel-dev:3000";
|
|
const signature = process.env.N8N_WEBHOOK_SECRET_DEV || process.env.N8N_WEBHOOK_SECRET || "qUHuPqPjA65psdtwQU7zgp/DVkvd1xXk2WP/vzbnEdc=";
|
|
node.parameters.values.string = [
|
|
{ name: "appUrl", value: appUrl },
|
|
{ name: "signature", value: signature }
|
|
];
|
|
console.log(`Injected Dev environment configuration into node "${node.name}" in ${filename}`);
|
|
}
|
|
if (node.id === 'Set-Prod-Env') {
|
|
const appUrl = process.env.APP_PROD_INTERNAL_URL || "http://special-hotel-prod:3000";
|
|
const signature = process.env.N8N_WEBHOOK_SECRET || "Ecjb2s33tHJppNBDJ/DxXEjHWKow8bNWmsQrk1sQKyQ=";
|
|
node.parameters.values.string = [
|
|
{ name: "appUrl", value: appUrl },
|
|
{ name: "signature", value: signature }
|
|
];
|
|
console.log(`Injected Prod environment configuration into node "${node.name}" in ${filename}`);
|
|
}
|
|
}
|
|
|
|
const existingWorkflow = listData.data.find(w => w.name === targetName);
|
|
let workflowId;
|
|
|
|
if (existingWorkflow) {
|
|
workflowId = existingWorkflow.id;
|
|
console.log(`Found existing workflow "${targetName}". ID: ${workflowId}. Updating...`);
|
|
|
|
const updateRes = await fetch(`${n8nUrl}/api/v1/workflows/${workflowId}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-N8N-API-KEY': n8nApiKey
|
|
},
|
|
body: JSON.stringify({
|
|
name: targetName,
|
|
nodes: workflowJson.nodes,
|
|
connections: workflowJson.connections,
|
|
settings: workflowJson.settings || {}
|
|
})
|
|
});
|
|
|
|
if (!updateRes.ok) {
|
|
console.error(`Failed to update workflow. Status: ${updateRes.status}`);
|
|
process.exit(1);
|
|
}
|
|
console.log(`Workflow updated successfully!`);
|
|
} else {
|
|
console.log(`No existing workflow found for "${targetName}". Creating new workflow...`);
|
|
|
|
const createRes = await fetch(`${n8nUrl}/api/v1/workflows`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-N8N-API-KEY': n8nApiKey
|
|
},
|
|
body: JSON.stringify({
|
|
name: targetName,
|
|
nodes: workflowJson.nodes,
|
|
connections: workflowJson.connections,
|
|
settings: workflowJson.settings || {},
|
|
projectId
|
|
})
|
|
});
|
|
|
|
if (!createRes.ok) {
|
|
console.error(`Failed to create workflow. Status: ${createRes.status}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const createData = await createRes.json();
|
|
workflowId = createData.id;
|
|
console.log(`Workflow created successfully! ID: ${workflowId}`);
|
|
}
|
|
|
|
// Activate the workflow
|
|
console.log(`Activating workflow "${targetName}" (${workflowId})...`);
|
|
const activateRes = await fetch(`${n8nUrl}/api/v1/workflows/${workflowId}/activate`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-N8N-API-KEY': n8nApiKey
|
|
},
|
|
body: JSON.stringify({})
|
|
});
|
|
|
|
if (!activateRes.ok) {
|
|
console.error(`Warning: Failed to activate workflow. Status: ${activateRes.status}`);
|
|
} else {
|
|
console.log(`Workflow "${targetName}" activated and ready!`);
|
|
}
|
|
}
|
|
|
|
console.log("\n=== N8N WORKFLOW BOOTSTRAP COMPLETED SUCCESSFULLY ===");
|
|
}
|
|
|
|
bootstrap().catch(err => {
|
|
console.error("Fatal bootstrap error:", err);
|
|
process.exit(1);
|
|
});
|