backup: workflow V2.6 production (avant patch charge HC auto) + outils n8n-pull/n8n-push
This commit is contained in:
47
tools/n8n-pull.mjs
Normal file
47
tools/n8n-pull.mjs
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env node
|
||||
// Usage: node tools/n8n-pull.mjs [workflowId] [outputFile]
|
||||
// GET un workflow n8n et l'écrit en JSON (pretty) dans un fichier local.
|
||||
// Clé API lue depuis ~/.config/opencode/opencode.json (mcp.n8n-mcp.environment).
|
||||
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
const WORKFLOW_ID = process.argv[2] || 'ATswvGfG4KJUls0K';
|
||||
const OUT = process.argv[3] || `workflows/pull-${WORKFLOW_ID}.json`;
|
||||
|
||||
function loadApiConfig() {
|
||||
if (process.env.N8N_API_KEY) {
|
||||
return {
|
||||
url: process.env.N8N_URL || 'https://n8n.airtria.pro',
|
||||
key: process.env.N8N_API_KEY
|
||||
};
|
||||
}
|
||||
const cfgPath = path.join(os.homedir(), '.config/opencode/opencode.json');
|
||||
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
||||
const env = cfg?.mcp?.['n8n-mcp']?.environment || {};
|
||||
if (!env.N8N_API_KEY) {
|
||||
console.error('Clé API n8n introuvable (env N8N_API_KEY ou opencode.json)');
|
||||
process.exit(1);
|
||||
}
|
||||
return {
|
||||
url: (env.N8N_API_URL || 'https://n8n.airtria.pro/api/v1').replace(/\/api\/v1\/?$/, ''),
|
||||
key: env.N8N_API_KEY
|
||||
};
|
||||
}
|
||||
|
||||
const { url, key } = loadApiConfig();
|
||||
|
||||
const res = await fetch(`${url}/api/v1/workflows/${WORKFLOW_ID}`, {
|
||||
headers: { 'X-N8N-API-KEY': key }
|
||||
});
|
||||
if (!res.ok) {
|
||||
console.error(`GET échoué: ${res.status} ${res.statusText}`);
|
||||
console.error(await res.text());
|
||||
process.exit(1);
|
||||
}
|
||||
const wf = await res.json();
|
||||
fs.mkdirSync(path.dirname(OUT), { recursive: true });
|
||||
fs.writeFileSync(OUT, JSON.stringify(wf, null, 2) + '\n');
|
||||
console.log(`OK: "${wf.name}" (${wf.nodes?.length} nodes, active=${wf.active}) -> ${OUT}`);
|
||||
console.log(`updatedAt: ${wf.updatedAt}`);
|
||||
102
tools/n8n-push.mjs
Normal file
102
tools/n8n-push.mjs
Normal file
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env node
|
||||
// Usage: node tools/n8n-push.mjs <inputFile> [workflowId]
|
||||
// PUT un workflow n8n depuis un fichier JSON local (méthode validée 04/08/2026 :
|
||||
// body minimal {name, nodes, connections, settings, staticData}, settings épuré).
|
||||
// Fait automatiquement un backup GET avant le PUT dans modified/.
|
||||
// Clé API lue depuis ~/.config/opencode/opencode.json (mcp.n8n-mcp.environment).
|
||||
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
const INPUT = process.argv[2];
|
||||
if (!INPUT) {
|
||||
console.error('Usage: node tools/n8n-push.mjs <inputFile> [workflowId]');
|
||||
process.exit(1);
|
||||
}
|
||||
const local = JSON.parse(fs.readFileSync(INPUT, 'utf8'));
|
||||
const WORKFLOW_ID = process.argv[3] || local.id || 'ATswvGfG4KJUls0K';
|
||||
|
||||
function loadApiConfig() {
|
||||
if (process.env.N8N_API_KEY) {
|
||||
return {
|
||||
url: process.env.N8N_URL || 'https://n8n.airtria.pro',
|
||||
key: process.env.N8N_API_KEY
|
||||
};
|
||||
}
|
||||
const cfgPath = path.join(os.homedir(), '.config/opencode/opencode.json');
|
||||
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
||||
const env = cfg?.mcp?.['n8n-mcp']?.environment || {};
|
||||
if (!env.N8N_API_KEY) {
|
||||
console.error('Clé API n8n introuvable (env N8N_API_KEY ou opencode.json)');
|
||||
process.exit(1);
|
||||
}
|
||||
return {
|
||||
url: (env.N8N_API_URL || 'https://n8n.airtria.pro/api/v1').replace(/\/api\/v1\/?$/, ''),
|
||||
key: env.N8N_API_KEY
|
||||
};
|
||||
}
|
||||
|
||||
const { url, key } = loadApiConfig();
|
||||
const headers = { 'X-N8N-API-KEY': key, 'Content-Type': 'application/json' };
|
||||
|
||||
// 1. Backup automatique de l'état serveur AVANT push
|
||||
const getRes = await fetch(`${url}/api/v1/workflows/${WORKFLOW_ID}`, { headers });
|
||||
if (!getRes.ok) {
|
||||
console.error(`GET backup échoué: ${getRes.status} ${getRes.statusText}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const remote = await getRes.json();
|
||||
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const backupPath = `modified/prepush-${WORKFLOW_ID}-${stamp}.json`;
|
||||
fs.mkdirSync('modified', { recursive: true });
|
||||
fs.writeFileSync(backupPath, JSON.stringify(remote, null, 2) + '\n');
|
||||
console.log(`Backup serveur avant push: ${backupPath}`);
|
||||
|
||||
// 2. Body minimal (n8n API v1 rejette les propriétés additionnelles — erreur 1003)
|
||||
const body = {
|
||||
name: local.name,
|
||||
nodes: local.nodes,
|
||||
connections: local.connections,
|
||||
settings: {
|
||||
executionOrder: local.settings?.executionOrder || 'v1',
|
||||
callerPolicy: local.settings?.callerPolicy || 'workflowsFromSameOwner'
|
||||
},
|
||||
staticData: local.staticData ?? null
|
||||
};
|
||||
|
||||
// 3. Sanity check : node --check sur chaque jsCode
|
||||
let syntaxErrors = 0;
|
||||
for (const node of body.nodes || []) {
|
||||
const js = node.parameters?.jsCode;
|
||||
if (!js) continue;
|
||||
const tmp = path.join(os.tmpdir(), `n8n-check-${node.id || node.name}.js`.replace(/[^\w.-]/g, '_'));
|
||||
fs.writeFileSync(tmp, js);
|
||||
const { spawnSync } = await import('child_process');
|
||||
const r = spawnSync('node', ['--check', tmp]);
|
||||
if (r.status !== 0) {
|
||||
syntaxErrors++;
|
||||
console.error(`Syntaxe KO dans node "${node.name}":\n${r.stderr.toString().slice(0, 500)}`);
|
||||
}
|
||||
fs.unlinkSync(tmp);
|
||||
}
|
||||
if (syntaxErrors) {
|
||||
console.error(`ABANDON: ${syntaxErrors} node(s) avec erreur de syntaxe. Rien n'a été poussé.`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('Syntaxe OK sur tous les nodes jsCode.');
|
||||
|
||||
// 4. PUT
|
||||
const putRes = await fetch(`${url}/api/v1/workflows/${WORKFLOW_ID}`, {
|
||||
method: 'PUT',
|
||||
headers,
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
const text = await putRes.text();
|
||||
if (!putRes.ok) {
|
||||
console.error(`PUT REFUSÉ: ${putRes.status} ${putRes.statusText}`);
|
||||
console.error(text.slice(0, 1000));
|
||||
process.exit(1);
|
||||
}
|
||||
const updated = JSON.parse(text);
|
||||
console.log(`PUT OK: "${updated.name}" (updatedAt: ${updated.updatedAt}, active=${updated.active})`);
|
||||
Reference in New Issue
Block a user