Last active
April 7, 2024 08:26
-
-
Save pipethedev/68d7157369ff5cd29db473edd33a5510 to your computer and use it in GitHub Desktop.
Convert object model to knex migrations
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fs from 'fs'; | |
import path from 'path'; | |
import { exec } from 'child_process'; | |
enum KnexMigrationType { | |
Id = 'uuid', | |
String = 'string', | |
Number = 'integer', | |
Float = 'float', | |
Array = 'enum', | |
Date = 'timestamp', | |
Boolean = 'boolean', | |
} | |
enum DataTypes { | |
String = 'string', | |
Number = 'number', | |
Boolean = 'boolean', | |
Date = 'Date', | |
} | |
interface ModelField { | |
field: string; | |
type: KnexMigrationType; | |
nullable: boolean; | |
} | |
interface ModelFile { | |
filePath: string; | |
tableName: string; | |
} | |
//Use your own directories (I could make it passed as a flag, later on) | |
const modelsFolderPath = path.join(__dirname, '../../../models'); | |
const migrationsDir = path.join(__dirname, '../../../database/migrations'); | |
const modelFiles = getTableNamesFromModelsFolder(modelsFolderPath); | |
const transformedFiles = modelFiles.map(({ filePath, tableName }) => { | |
const newFilePath = filePath.replace('dist/', '').replace('.js', '.ts'); | |
return { filePath: newFilePath, tableName }; | |
}); | |
function getTableNamesFromModelsFolder(modelsFolderPath: string): ModelFile[] { | |
const modelFiles: ModelFile[] = []; | |
fs.readdirSync(modelsFolderPath).forEach((file) => { | |
const filePath = path.join(modelsFolderPath, file); | |
const tableName = getTableNameFromFile(filePath); | |
if (tableName) { | |
modelFiles.push({ filePath, tableName }); | |
} | |
}); | |
return modelFiles; | |
} | |
function getTableNameFromFile(filePath: string): string | null { | |
try { | |
const fileContent = fs.readFileSync(filePath, 'utf8'); | |
const match = fileContent.match(/static theTableName\(\) {\s*return '(.*)';\s*}/); | |
if (match && match[1]) { | |
return match[1]; | |
} | |
} catch (err) { | |
console.error('Error reading file:', err); | |
} | |
return null; | |
} | |
async function writeMigrationFile(migrationScript: string, fileName: string): Promise<void> { | |
if (!fs.existsSync(migrationsDir)) { | |
fs.mkdirSync(migrationsDir); | |
} | |
const filePath = path.join(migrationsDir, fileName).replace(/\/dist\//, '/'); | |
await fs.promises.writeFile(filePath, migrationScript); | |
} | |
async function createKnexMigrationFromModel(modelPath: string): Promise<string> { | |
const timestamp = new Date().toISOString().replace(/[^0-9]/g, ''); | |
const modelData = await fs.promises.readFile(modelPath, 'utf8'); | |
const modelName = path.basename(modelPath, '.ts'); | |
const tableNameMatch = modelData.match(/static theTableName\(\) {\s*return '(.*)';\s*}/); | |
const tableName = tableNameMatch ? tableNameMatch[1] : modelName.toLowerCase() + 's'; | |
const fileName = `${timestamp}_${tableName}.ts`; | |
const fieldRegex = /(\w+)\s*(!|\?)?\s*:\s*(\w+\s*\|\s*\w+|\w+)(\[\])?;/g; | |
const fields: ModelField[] = []; | |
const supportedTypes = Object.values(DataTypes); | |
const notNullableFields = ['id', 'created_at', 'createdAt']; | |
let match: Array<any>; | |
while ((match = fieldRegex.exec(modelData)) !== null) { | |
let [, field, isRequired, type, isArray] = match; | |
if (!isRequired || isRequired === '!' || isRequired === '?') { | |
const nullable = isRequired === '?' && !notNullableFields.includes(field); | |
if (type.includes('|')) type = KnexMigrationType.String; | |
if (isArray) type = KnexMigrationType.Array; | |
if (!supportedTypes.includes(type as DataTypes) && !isArray) type = DataTypes.String; | |
if (field.includes('id')) type = KnexMigrationType.Id; | |
if (type === 'Date') type = KnexMigrationType.Date; | |
if (type === 'number') type = KnexMigrationType.Number; | |
if (field.includes('balance')) type = KnexMigrationType.Float; | |
fields.push({ field, type, nullable }); | |
} | |
} | |
let migrationScript = `import { Knex as KnexType } from 'knex';\n\n`; | |
migrationScript += ` export async function up(knex: KnexType): Promise<void> {\n`; | |
migrationScript += ` await knex.schema.createTable('${tableName}', (table) => {\n`; | |
fields.forEach((field) => { | |
if (field.field === 'created_at' || field.field === 'updated_at') { | |
migrationScript += ` table.${field.type}('${field.field}').defaultTo(knex.fn.now());\n`; | |
} else if (field.type == KnexMigrationType.String) { | |
migrationScript += ` table.${field.type}('${field.field}', 225);\n`; | |
} else if (field.type == KnexMigrationType.Array) { | |
migrationScript += ` table.${field.type}('${field.field}', []);\n`; | |
} else { | |
migrationScript += ` table.${field.type}('${field.field}')${field.field === 'id' ? '.primary()' : ''}${ | |
field.nullable ? '.nullable()' : '' | |
};\n`; | |
} | |
}); | |
migrationScript += ` });\n`; | |
migrationScript += `};\n\n`; | |
migrationScript += `export async function down(knex: KnexType): Promise<void> {\n`; | |
migrationScript += ` await knex.schema.dropTable('${tableName}');\n`; | |
migrationScript += `};\n`; | |
await writeMigrationFile(migrationScript, fileName); | |
return migrationScript; | |
} | |
function main() { | |
transformedFiles.forEach(async (transformedFile) => { | |
await createKnexMigrationFromModel(transformedFile.filePath); | |
}); | |
exec('npx prettier --write .', (error, stdout, stderr) => { | |
if (error) { | |
console.error(`exec error: ${error}`); | |
return; | |
} | |
console.log(`stdout: ${stdout}`); | |
console.error(`stderr: ${stderr}`); | |
}); | |
} | |
main(); | |
//Add to package.json [yarn auto:migrate OR npm run auto:migrate] | |
"auto:migrate": "npm run build && ts-node -r tsconfig-paths/register ./<REPLACE_BUILD_PATH>/auto-migrate.script.js", |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment