2weekmail/api/scripts/sql2migration.js
2025-03-19 19:56:57 -05:00

223 lines
6.3 KiB
JavaScript

#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
// Function to generate a timestamp for the migration filename
function generateTimestamp() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
return `${year}${month}${day}${hours}${minutes}${seconds}`;
}
// Function to parse SQL CREATE TABLE statement
function parseCreateTable(sql) {
// Extract table name
const tableNameMatch = sql.match(/CREATE TABLE `([^`]+)`/i);
if (!tableNameMatch) {
throw new Error('Could not find table name in SQL statement');
}
const tableName = tableNameMatch[1];
// Extract column definitions
const columnsSection = sql.match(/\(([^)]+)\)/)[1];
// Split by commas, but be careful with commas inside parentheses (for default values)
let depth = 0;
let columns = [];
let currentColumn = '';
for (let i = 0; i < columnsSection.length; i++) {
const char = columnsSection[i];
if (char === '(') depth++;
if (char === ')') depth--;
if (char === ',' && depth === 0) {
columns.push(currentColumn.trim());
currentColumn = '';
} else {
currentColumn += char;
}
}
if (currentColumn.trim()) {
columns.push(currentColumn.trim());
}
// Filter out non-column definitions (like PRIMARY KEY)
const columnDefs = columns.filter(col => col.trim().startsWith('`'));
// Extract primary key
const primaryKeyMatch = columnsSection.match(/PRIMARY KEY \(`([^`]+)`\)/i);
const primaryKey = primaryKeyMatch ? primaryKeyMatch[1] : null;
// Extract table options
const engineMatch = sql.match(/ENGINE=(\w+)/i);
const engine = engineMatch ? engineMatch[1] : null;
const charsetMatch = sql.match(/CHARSET=([^\s]+)/i);
const charset = charsetMatch ? charsetMatch[1] : null;
const collateMatch = sql.match(/COLLATE=([^\s]+)/i);
const collate = collateMatch ? collateMatch[1] : null;
const commentMatch = sql.match(/COMMENT='([^']+)'/i);
const comment = commentMatch ? commentMatch[1] : null;
return {
tableName,
columns: columnDefs,
primaryKey,
engine,
charset,
collate,
comment
};
}
// Function to convert SQL column definition to Knex column builder
function convertColumnToKnex(columnDef) {
// Extract column name
const nameMatch = columnDef.match(/`([^`]+)`/);
if (!nameMatch) return null;
const name = nameMatch[1];
// Determine column type
let type = '';
let knexType = '';
if (columnDef.includes('varchar')) {
const sizeMatch = columnDef.match(/varchar\((\d+)\)/i);
const size = sizeMatch ? parseInt(sizeMatch[1]) : 255;
type = `varchar(${size})`;
knexType = `string('${name}', ${size})`;
} else if (columnDef.includes('datetime')) {
type = 'datetime';
knexType = `datetime('${name}')`;
} else if (columnDef.includes('tinyint')) {
type = 'tinyint';
knexType = `boolean('${name}')`;
} else {
// Default to string if type is not recognized
knexType = `string('${name}')`;
}
// Check for NOT NULL
const isNullable = !columnDef.includes('NOT NULL');
if (isNullable) {
knexType += '.nullable()';
}
// Check for DEFAULT value
const defaultMatch = columnDef.match(/DEFAULT\s+(?:'([^']+)'|(\d+))/i);
if (defaultMatch) {
const defaultValue = defaultMatch[1] || defaultMatch[2];
if (type === 'tinyint') {
knexType += `.defaultTo(${defaultValue})`;
} else {
knexType += `.defaultTo('${defaultValue}')`;
}
}
// Check for character set and collation
if (columnDef.includes('CHARACTER SET') || columnDef.includes('COLLATE')) {
const charsetMatch = columnDef.match(/CHARACTER SET\s+([^\s]+)/i);
const collateMatch = columnDef.match(/COLLATE\s+([^\s]+)/i);
if (charsetMatch) {
knexType += `.charset('${charsetMatch[1]}')`;
}
if (collateMatch) {
knexType += `.collate('${collateMatch[1]}')`;
}
}
return knexType;
}
// Function to generate Knex migration file content
function generateKnexMigration(parsedTable) {
const { tableName, columns, primaryKey, engine, charset, collate, comment } = parsedTable;
let knexColumns = columns.map(convertColumnToKnex).filter(Boolean);
let migrationContent = `exports.up = function(knex) {
return knex.schema.createTable('${tableName}', function(table) {
${knexColumns.map(col => ` table.${col};`).join('\n')}
`;
if (primaryKey) {
migrationContent += ` table.primary(['${primaryKey}']);\n`;
}
if (engine || charset || collate || comment) {
migrationContent += ` // Table options\n`;
if (comment) {
migrationContent += ` table.comment('${comment}');\n`;
}
}
migrationContent += ` })`;
if (engine || charset || collate) {
migrationContent += `.options({`;
const options = [];
if (engine) options.push(`engine: '${engine}'`);
if (charset) options.push(`charset: '${charset}'`);
if (collate) options.push(`collate: '${collate}'`);
migrationContent += `\n ${options.join(',\n ')}\n })`;
}
migrationContent += `;\n};
exports.down = function(knex) {
return knex.schema.dropTable('${tableName}');
};
`;
return migrationContent;
}
// Main function
function main() {
// Get SQL from command line argument or stdin
let sql = '';
if (process.argv.length > 2) {
// Read from file if provided
const filePath = process.argv[2];
sql = fs.readFileSync(filePath, 'utf8');
} else {
return console.log('No file provided');
}
try {
const parsedTable = parseCreateTable(sql);
const migrationContent = generateKnexMigration(parsedTable);
// Generate filename with timestamp
const timestamp = generateTimestamp();
const filename = `${timestamp}_create_${parsedTable.tableName}_table.js`;
// Write to file
fs.writeFileSync(filename, migrationContent);
console.log(`Migration file created: ${filename}`);
// Also output to console
console.log('\nMigration content:');
console.log(migrationContent);
} catch (error) {
console.error('Error:', error.message);
}
}
main();