UNPKG

meld

Version:

Meld: A template language for LLM prompts

285 lines (241 loc) 9.21 kB
#!/usr/bin/env node /** * Script to compare output between meld-ast versions * This script: * 1. Runs tests with different versions of meld-ast * 2. Captures test output and failure details * 3. Creates sample AST outputs for comparison */ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); const { promisify } = require('util'); const writeFileAsync = promisify(fs.writeFile); const mkdirAsync = promisify(fs.mkdir); const COMPARISON_DIR = path.join(process.cwd(), 'meld-ast-comparison'); const TEST_SAMPLES = [ { name: 'simple-field-access', content: ` @data person = { name: "John Doe", age: 30, address: { street: "123 Main St", city: "Anytown" } } Field access: {{person.name}} ` }, { name: 'array-access', content: ` @data items = [ "apple", "banana", "cherry" ] First item: {{items[0]}} Second item: {{items[1]}} ` }, { name: 'nested-array-access', content: ` @data users = [ { name: "Alice", tasks: ["coding", "testing"] }, { name: "Bob", tasks: ["design", "documentation"] } ] User: {{users[0].name}} Task: {{users[0].tasks[1]}} ` }, { name: 'code-fence-test', content: '```javascript\nconst array = [1, 2, 3];\nconsole.log(array[0]);\n```' } ]; async function ensureDirectoryExists(directory) { try { await mkdirAsync(directory, { recursive: true }); } catch (err) { if (err.code !== 'EEXIST') throw err; } } async function run() { try { // Ensure the comparison directory exists await ensureDirectoryExists(COMPARISON_DIR); await ensureDirectoryExists(path.join(COMPARISON_DIR, '3.0.1')); await ensureDirectoryExists(path.join(COMPARISON_DIR, '3.3.0')); // Create sample test files for (const sample of TEST_SAMPLES) { const filePath = path.join(COMPARISON_DIR, `${sample.name}.meld`); await writeFileAsync(filePath, sample.content); } // Run comparison for both versions await compareVersions('3.0.1'); await compareVersions('3.3.0'); // Generate comparison report await generateComparisonReport(); console.log('Comparison completed successfully. Check the meld-ast-comparison directory for results.'); } catch (error) { console.error('Error during comparison:', error); process.exit(1); } } async function compareVersions(version) { console.log(`\n=== Testing with meld-ast ${version} ===`); try { // Clean node_modules and install specific version console.log(`Installing meld-ast@${version}...`); execSync('rm -rf node_modules', { stdio: 'inherit' }); execSync('npm install', { stdio: 'inherit' }); execSync(`npm install meld-ast@${version}`, { stdio: 'inherit' }); // Run tests and capture output console.log(`Running tests with meld-ast@${version}...`); try { const testOutput = execSync('npm test', { encoding: 'utf8' }); await writeFileAsync(path.join(COMPARISON_DIR, version, 'test-output.log'), testOutput); console.log(`Test output saved for version ${version}`); } catch (testError) { // If tests fail, still capture the output const errorOutput = testError.stdout || testError.message; await writeFileAsync(path.join(COMPARISON_DIR, version, 'test-output.log'), errorOutput); console.log(`Test failures captured for version ${version}`); } // Generate AST for each sample for (const sample of TEST_SAMPLES) { const samplePath = path.join(COMPARISON_DIR, `${sample.name}.meld`); try { // Create a small test script that will output the AST const astScript = ` const { parse } = require('meld-ast'); async function main() { const content = \`${sample.content.replace(/`/g, '\\`')}\`; const result = await parse(content, { preserveCodeFences: true, failFast: false, trackLocations: true, validateNodes: true }); console.log(JSON.stringify(result, null, 2)); } main().catch(console.error); `; const scriptPath = path.join(COMPARISON_DIR, version, `${sample.name}-ast-generator.js`); await writeFileAsync(scriptPath, astScript); // Execute the AST script const astOutput = execSync(`node ${scriptPath}`, { encoding: 'utf8' }); await writeFileAsync(path.join(COMPARISON_DIR, version, `${sample.name}-ast.json`), astOutput); } catch (error) { await writeFileAsync( path.join(COMPARISON_DIR, version, `${sample.name}-ast-error.log`), error.toString() ); } } } catch (error) { console.error(`Error processing version ${version}:`, error); await writeFileAsync( path.join(COMPARISON_DIR, version, 'installation-error.log'), error.toString() ); } } async function generateComparisonReport() { console.log('\n=== Generating Comparison Report ==='); let report = `# meld-ast Version Comparison Report\n\n`; report += `Comparing version 3.0.1 with 3.3.0\n\n`; // Check if test outputs indicate different results const v301TestOutput = fs.existsSync(path.join(COMPARISON_DIR, '3.0.1', 'test-output.log')) ? fs.readFileSync(path.join(COMPARISON_DIR, '3.0.1', 'test-output.log'), 'utf8') : 'No test output captured'; const v330TestOutput = fs.existsSync(path.join(COMPARISON_DIR, '3.3.0', 'test-output.log')) ? fs.readFileSync(path.join(COMPARISON_DIR, '3.3.0', 'test-output.log'), 'utf8') : 'No test output captured'; report += `## Test Results Comparison\n\n`; report += `Version 3.0.1 test result summary: ${v301TestOutput.includes('FAIL') ? 'FAILURES DETECTED' : 'PASSED'}\n`; report += `Version 3.3.0 test result summary: ${v330TestOutput.includes('FAIL') ? 'FAILURES DETECTED' : 'PASSED'}\n\n`; // Compare AST outputs for each sample report += `## AST Comparison\n\n`; for (const sample of TEST_SAMPLES) { report += `### ${sample.name}\n\n`; const ast301Path = path.join(COMPARISON_DIR, '3.0.1', `${sample.name}-ast.json`); const ast330Path = path.join(COMPARISON_DIR, '3.3.0', `${sample.name}-ast.json`); if (fs.existsSync(ast301Path) && fs.existsSync(ast330Path)) { const ast301 = JSON.parse(fs.readFileSync(ast301Path, 'utf8')); const ast330 = JSON.parse(fs.readFileSync(ast330Path, 'utf8')); // Simple diff to find key differences (this is a basic implementation) const differences = findASTDifferences(ast301, ast330); if (differences.length > 0) { report += `Differences found:\n\n`; differences.forEach(diff => { report += `- ${diff}\n`; }); } else { report += `No structural differences detected in AST output.\n`; } } else { report += `Could not compare ASTs - one or both files missing.\n`; } report += `\n`; } await writeFileAsync(path.join(COMPARISON_DIR, 'comparison-report.md'), report); } function findASTDifferences(ast1, ast2, path = '') { const differences = []; // Check for structural differences if (typeof ast1 !== typeof ast2) { differences.push(`${path || 'root'}: Type mismatch - ${typeof ast1} vs ${typeof ast2}`); return differences; } if (Array.isArray(ast1) !== Array.isArray(ast2)) { differences.push(`${path || 'root'}: Array structure mismatch`); return differences; } if (Array.isArray(ast1)) { // Compare arrays if (ast1.length !== ast2.length) { differences.push(`${path || 'root'}: Array length differs - ${ast1.length} vs ${ast2.length}`); } // Check array contents (limited to first few elements for simplicity) const maxCheck = Math.min(ast1.length, ast2.length, 5); for (let i = 0; i < maxCheck; i++) { const childPath = path ? `${path}[${i}]` : `[${i}]`; differences.push(...findASTDifferences(ast1[i], ast2[i], childPath)); } } else if (typeof ast1 === 'object' && ast1 !== null && ast2 !== null) { // Compare objects const keys1 = Object.keys(ast1); const keys2 = Object.keys(ast2); // Check for key differences const uniqueKeys1 = keys1.filter(k => !keys2.includes(k)); const uniqueKeys2 = keys2.filter(k => !keys1.includes(k)); if (uniqueKeys1.length > 0) { differences.push(`${path || 'root'}: Keys only in v3.0.1: ${uniqueKeys1.join(', ')}`); } if (uniqueKeys2.length > 0) { differences.push(`${path || 'root'}: Keys only in v3.3.0: ${uniqueKeys2.join(', ')}`); } // Check common keys recursively const commonKeys = keys1.filter(k => keys2.includes(k)); for (const key of commonKeys) { const childPath = path ? `${path}.${key}` : key; differences.push(...findASTDifferences(ast1[key], ast2[key], childPath)); } } else if (ast1 !== ast2) { // Compare primitive values differences.push(`${path || 'root'}: Value changed from "${ast1}" to "${ast2}"`); } return differences; } // Run the comparison run().catch(console.error);