R2: git = source de vérité — src/nodes/ + build/extract intégrés au push
- src/nodes/*.js (6 nodes + template snapshot) = source de vérité unique. - n8n-push.mjs reconstruit le JSON depuis src/ avant syntaxe+golden+PUT (src gagne toujours; n8n-extract.mjs pour resynchroniser si édition UI). - n8n-build.mjs --check pour vérifier la synchro. - Copies historiques déplacées dans archive/ (mortes, dérivées de la prod). - .gitignore: backups locaux de rollback. - AGENTS.md: règle R2 pour toutes les futures sessions (dont Qwen local). - Validé: idempotence, mutation/restauration, pipeline push complet en réel.
This commit is contained in:
32
tools/lib-node-map.mjs
Normal file
32
tools/lib-node-map.mjs
Normal file
@@ -0,0 +1,32 @@
|
||||
// R2 — Cartographie src/nodes/ ↔ workflow JSON (source de vérité : src/nodes/).
|
||||
// Utilisé par tools/n8n-extract.mjs (JSON → src) et tools/n8n-build.mjs (src → JSON).
|
||||
|
||||
export const WORKFLOW_FILE = 'workflows/evcs-surplus-controller-v2.6.json';
|
||||
export const SRC_DIR = 'src/nodes';
|
||||
|
||||
export const NODE_MAP = [
|
||||
{ file: 'INPUT_PARSE.js', node: 'INPUT_PARSE', kind: 'jsCode' },
|
||||
{ file: 'WINDOWS_SOURCEV2.js', node: 'WINDOWS_SOURCEV2', kind: 'jsCode' },
|
||||
{ file: 'BATTERY_CLOUD_LOCKS.js', node: 'BATTERY_CLOUD_LOCKS', kind: 'jsCode' },
|
||||
{ file: 'DECISION_ENGINEV2.js', node: 'DECISION_ENGINEV2', kind: 'jsCode' },
|
||||
{ file: 'HA_SERVICE_CALLS_OUTPUT.js', node: 'HA_SERVICE_CALLS_OUTPUT', kind: 'jsCode' },
|
||||
{ file: 'BUILD_ACTIONS.js', node: 'Build EVCS Surplus Controller V2.0 Actions', kind: 'jsCode' },
|
||||
{ file: 'HA_SNAPSHOT_TEMPLATE.json.tpl', node: 'HA Snapshot EVCS Surplus Controller V2.2', kind: 'template' },
|
||||
];
|
||||
|
||||
export function getCode(workflowNode, kind) {
|
||||
if (kind === 'jsCode') return workflowNode.parameters.jsCode;
|
||||
if (kind === 'template') {
|
||||
return workflowNode.parameters.bodyParameters.parameters.find(p => p.name === 'template').value;
|
||||
}
|
||||
throw new Error(`kind inconnu: ${kind}`);
|
||||
}
|
||||
|
||||
export function setCode(workflowNode, kind, code) {
|
||||
if (kind === 'jsCode') { workflowNode.parameters.jsCode = code; return; }
|
||||
if (kind === 'template') {
|
||||
workflowNode.parameters.bodyParameters.parameters.find(p => p.name === 'template').value = code;
|
||||
return;
|
||||
}
|
||||
throw new Error(`kind inconnu: ${kind}`);
|
||||
}
|
||||
38
tools/n8n-build.mjs
Normal file
38
tools/n8n-build.mjs
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env node
|
||||
// R2 — BUILD : src/nodes/*.js → workflow JSON (src/ = SOURCE DE VÉRITÉ, src gagne).
|
||||
// Usage : node tools/n8n-build.mjs [--check]
|
||||
// --check : vérifie que le JSON est déjà synchronisé avec src/ (exit 1 sinon),
|
||||
// sans rien écrire.
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { WORKFLOW_FILE, SRC_DIR, NODE_MAP, getCode, setCode } from './lib-node-map.mjs';
|
||||
|
||||
const CHECK = process.argv.includes('--check');
|
||||
const wf = JSON.parse(fs.readFileSync(WORKFLOW_FILE, 'utf8'));
|
||||
|
||||
let changes = 0;
|
||||
for (const { file, node, kind } of NODE_MAP) {
|
||||
const srcPath = path.join(SRC_DIR, file);
|
||||
if (!fs.existsSync(srcPath)) throw new Error(`Fichier source manquant: ${srcPath}`);
|
||||
const srcCode = fs.readFileSync(srcPath, 'utf8');
|
||||
const n = wf.nodes.find(x => x.name === node);
|
||||
if (!n) throw new Error(`Node introuvable dans le JSON: ${node}`);
|
||||
if (getCode(n, kind) !== srcCode) {
|
||||
changes++;
|
||||
console.log(`${CHECK ? 'DÉSYNCHRONISÉ' : 'inject'}: ${file} → "${node}"`);
|
||||
if (!CHECK) setCode(n, kind, srcCode);
|
||||
}
|
||||
}
|
||||
|
||||
if (CHECK) {
|
||||
console.log(changes ? `\n${changes} node(s) désynchronisé(s) : lancer node tools/n8n-build.mjs` : 'JSON synchronisé avec src/nodes/');
|
||||
process.exit(changes ? 1 : 0);
|
||||
}
|
||||
|
||||
if (changes) {
|
||||
fs.writeFileSync(WORKFLOW_FILE, JSON.stringify(wf, null, 2) + '\n');
|
||||
console.log(`\nBuild OK: ${changes} node(s) injecté(s) dans ${WORKFLOW_FILE}`);
|
||||
} else {
|
||||
console.log('Build: aucun changement (déjà synchronisé)');
|
||||
}
|
||||
21
tools/n8n-extract.mjs
Normal file
21
tools/n8n-extract.mjs
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env node
|
||||
// R2 — EXTRACT : workflow JSON → src/nodes/*.js (+ template snapshot).
|
||||
// À utiliser UNIQUEMENT pour (re)synchroniser src/ après une modification faite
|
||||
// directement dans l'UI n8n (à éviter). En temps normal : éditer src/nodes/,
|
||||
// puis tools/n8n-push.mjs reconstruit le JSON automatiquement.
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { WORKFLOW_FILE, SRC_DIR, NODE_MAP, getCode } from './lib-node-map.mjs';
|
||||
|
||||
const wf = JSON.parse(fs.readFileSync(WORKFLOW_FILE, 'utf8'));
|
||||
fs.mkdirSync(SRC_DIR, { recursive: true });
|
||||
|
||||
for (const { file, node, kind } of NODE_MAP) {
|
||||
const n = wf.nodes.find(x => x.name === node);
|
||||
if (!n) throw new Error(`Node introuvable: ${node}`);
|
||||
const code = getCode(n, kind);
|
||||
fs.writeFileSync(path.join(SRC_DIR, file), code);
|
||||
console.log(`${file} ← "${node}" (${code.length} chars)`);
|
||||
}
|
||||
console.log(`\nExtraction OK dans ${SRC_DIR}/`);
|
||||
@@ -14,6 +14,21 @@ if (!INPUT) {
|
||||
console.error('Usage: node tools/n8n-push.mjs <inputFile> [workflowId]');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// R2 : src/nodes/ = SOURCE DE VÉRITÉ pour le workflow EVCS.
|
||||
// Le build injecte src/nodes/*.js dans le JSON AVANT toute vérification :
|
||||
// toute édition faite directement dans le JSON (ou l'UI n8n non ré-extraite)
|
||||
// est écrasée par src/. Workflow EVCS uniquement.
|
||||
if (path.basename(INPUT) === 'evcs-surplus-controller-v2.6.json' && fs.existsSync('src/nodes')) {
|
||||
console.log('Build src/nodes/ → JSON (source de vérité : src/)...');
|
||||
const { spawnSync } = await import('child_process');
|
||||
const b = spawnSync('node', ['tools/n8n-build.mjs'], { stdio: 'inherit' });
|
||||
if (b.status !== 0) {
|
||||
console.error('ABANDON: échec du build src → JSON.');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const local = JSON.parse(fs.readFileSync(INPUT, 'utf8'));
|
||||
const WORKFLOW_ID = process.argv[3] || local.id || 'ATswvGfG4KJUls0K';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user