mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-24 13:25:26 +02:00
192 lines
5.4 KiB
JavaScript
192 lines
5.4 KiB
JavaScript
/**
|
|
* Validates that schema files exist for all declared node versions.
|
|
*
|
|
* This script checks nodes that have __schema__ directories and verifies
|
|
* that schema files exist for the node's default version. This prevents
|
|
* issues where a node version is bumped but schema files are not updated.
|
|
*
|
|
* Run as part of: pnpm lint
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const NODES_DIR = path.join(__dirname, '../nodes');
|
|
const DIST_DIR = path.join(__dirname, '../dist/nodes');
|
|
|
|
/**
|
|
* Recursively find all directories containing __schema__
|
|
*/
|
|
function findSchemaDirectories(dir, results = []) {
|
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
|
|
for (const entry of entries) {
|
|
const fullPath = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
if (entry.name === '__schema__') {
|
|
results.push(path.dirname(fullPath));
|
|
} else {
|
|
findSchemaDirectories(fullPath, results);
|
|
}
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Get available schema versions from __schema__ directory
|
|
*/
|
|
function getAvailableSchemaVersions(nodeDir) {
|
|
const schemaDir = path.join(nodeDir, '__schema__');
|
|
if (!fs.existsSync(schemaDir)) return [];
|
|
|
|
return fs
|
|
.readdirSync(schemaDir, { withFileTypes: true })
|
|
.filter((entry) => entry.isDirectory() && entry.name.startsWith('v'))
|
|
.map((entry) => entry.name.replace('v', ''));
|
|
}
|
|
|
|
/**
|
|
* Find the main node file in a directory
|
|
*/
|
|
function findNodeFile(nodeDir) {
|
|
const entries = fs.readdirSync(nodeDir);
|
|
const nodeFile = entries.find(
|
|
(f) => f.endsWith('.node.ts') && !f.includes('Trigger') && !f.includes('.test.'),
|
|
);
|
|
return nodeFile ? path.join(nodeDir, nodeFile) : null;
|
|
}
|
|
|
|
/**
|
|
* Extract version info from node source file using regex
|
|
* This handles both simple version: X and defaultVersion: X patterns
|
|
*/
|
|
function extractVersionFromSource(nodeFilePath) {
|
|
const content = fs.readFileSync(nodeFilePath, 'utf8');
|
|
|
|
// Check for defaultVersion (versioned nodes)
|
|
const defaultVersionMatch = content.match(/defaultVersion:\s*([\d.]+)/);
|
|
if (defaultVersionMatch) {
|
|
return {
|
|
defaultVersion: parseFloat(defaultVersionMatch[1]),
|
|
isVersioned: true,
|
|
};
|
|
}
|
|
|
|
// Check for simple version (non-versioned nodes)
|
|
const versionMatch = content.match(/version:\s*(\[[\d\s,.]+\]|[\d.]+)/);
|
|
if (versionMatch) {
|
|
const versionValue = versionMatch[1];
|
|
// Handle array format: version: [1, 2, 3]
|
|
if (versionValue.startsWith('[')) {
|
|
const versions = versionValue
|
|
.replace(/[\[\]]/g, '')
|
|
.split(',')
|
|
.map((v) => parseFloat(v.trim()))
|
|
.filter((v) => !isNaN(v));
|
|
return {
|
|
defaultVersion: Math.max(...versions),
|
|
isVersioned: false,
|
|
};
|
|
}
|
|
return {
|
|
defaultVersion: parseFloat(versionValue),
|
|
isVersioned: false,
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Convert version number to schema directory format (e.g., 2.2 -> "2.2.0")
|
|
*/
|
|
function versionToSchemaFormat(version) {
|
|
const parts = version.toString().split('.');
|
|
while (parts.length < 3) parts.push('0');
|
|
return parts.slice(0, 3).join('.');
|
|
}
|
|
|
|
/**
|
|
* Main validation function
|
|
*/
|
|
function validateSchemaVersions() {
|
|
console.log('Validating schema versions for nodes...\n');
|
|
|
|
const nodesWithSchemas = findSchemaDirectories(NODES_DIR);
|
|
const errors = [];
|
|
const warnings = [];
|
|
|
|
for (const nodeDir of nodesWithSchemas) {
|
|
const relativePath = path.relative(NODES_DIR, nodeDir);
|
|
const nodeFile = findNodeFile(nodeDir);
|
|
|
|
if (!nodeFile) {
|
|
// Try parent directory for versioned nodes
|
|
const parentNodeFile = findNodeFile(path.dirname(nodeDir));
|
|
if (!parentNodeFile) {
|
|
warnings.push(`${relativePath}: Could not find node file`);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
const actualNodeFile = nodeFile || findNodeFile(path.dirname(nodeDir));
|
|
if (!actualNodeFile) {
|
|
warnings.push(`${relativePath}: Could not find node file`);
|
|
continue;
|
|
}
|
|
const versionInfo = extractVersionFromSource(actualNodeFile);
|
|
|
|
if (!versionInfo) {
|
|
warnings.push(`${relativePath}: Could not extract version info`);
|
|
continue;
|
|
}
|
|
|
|
const availableSchemas = getAvailableSchemaVersions(nodeDir);
|
|
const expectedSchemaVersion = versionToSchemaFormat(versionInfo.defaultVersion);
|
|
|
|
if (!availableSchemas.includes(expectedSchemaVersion)) {
|
|
const nodeName = path.basename(actualNodeFile, '.node.ts');
|
|
errors.push({
|
|
node: nodeName,
|
|
path: relativePath,
|
|
defaultVersion: versionInfo.defaultVersion,
|
|
expectedSchema: `v${expectedSchemaVersion}`,
|
|
availableSchemas: availableSchemas.map((v) => `v${v}`),
|
|
});
|
|
}
|
|
}
|
|
|
|
// Report results
|
|
if (errors.length > 0) {
|
|
console.warn('⚠️ WARNING: The following nodes have missing schema versions:\n');
|
|
for (const error of errors) {
|
|
console.warn(` ${error.node} (${error.path})`);
|
|
console.warn(` Default version: ${error.defaultVersion}`);
|
|
console.warn(` Expected schema: ${error.expectedSchema}`);
|
|
console.warn(` Available schemas: ${error.availableSchemas.join(', ') || 'none'}`);
|
|
console.warn('');
|
|
}
|
|
console.warn('Schema files should be generated for these versions.');
|
|
console.warn('');
|
|
}
|
|
|
|
if (warnings.length > 0) {
|
|
console.warn('⚠️ Warnings:\n');
|
|
for (const warning of warnings) {
|
|
console.warn(` ${warning}`);
|
|
}
|
|
console.warn('');
|
|
}
|
|
|
|
console.log(`✅ All ${nodesWithSchemas.length} nodes with schemas have valid version mappings.`);
|
|
}
|
|
|
|
try {
|
|
validateSchemaVersions();
|
|
} catch (error) {
|
|
console.warn('⚠️ Schema validation script encountered an error:', error.message);
|
|
console.warn('Continuing without blocking...');
|
|
}
|