Skip to content

Instantly share code, notes, and snippets.

@joadr
Last active June 29, 2024 09:00
Show Gist options
  • Save joadr/7137774f47dee243ae31e8a6dff44013 to your computer and use it in GitHub Desktop.
Save joadr/7137774f47dee243ae31e8a6dff44013 to your computer and use it in GitHub Desktop.
Mikro-orm v5 to v6 migration script

Mikro-orm v5 to v6 migration script

We ran npm install and updated some dependencies, and found out that our nestjs projects that used v5 mikro-orm development project stopped working. We started getting depedency injection errors and looking around in the github issues some other people where having the same problems. The proposed solution in one of them was updating from v6 to v6.0.1

As we had not updated iu. It seems a dependency of @mikro-orm/nestjs package received a breaking change update with minor version bump. Resulting in breaking compatibility with v5 mikro-orm.

As this issue happened, and updating package-lock.json manually without much information on where the problem actually is, and any update to packages in production environment could lead to application issues and stalled development, we chose update mikro-orm from v5 to v6.

Inspired by Storybook's migrations scripts, and because of the large quantity of files we had to update in all our nestjs microservices. I decided to create my own migration script to make things way easier.

Note: I started this by doing the migration manually with regex and replace, then I tought it could be easily reproduced with a migration script. Also, I'm working a lot with LLM's and local LLM's as a hobby, so I wanted to test differences in the result code from different models for different languages given a set of very clear and precise instructions in plain english. So, the migration script was generated big part by GPT-4 using the prompt at prompt.txt. Then the code was modified to fix some small issues. I left it all there in case you want to play with it.

DISCLAIMER: I am not responsible for the results of this migration, I chose to open source it to other people as it could be helpful for others, but I do not guarantee at all that is going to work for you. You might need to make some changes.

const fs = require('fs');
const path = require('path');
const glob = require('glob');
const updatedFiles = [];
const driver = 'postgresql'; // Change to the appropriate driver if necessary
// Step 1: Replace persistAndFlush with em.persist and em.flush
function replacePersistAndFlush(content, filePath) {
const regex = /(.*)await this\.(.*)\.persistAndFlush\((.*)\);/g;
const replacement = `$1this.em.persist($3);\n$1await this.em.flush();`;
if (regex.test(content)) {
updatedFiles.push(filePath);
return content.replace(regex, replacement);
}
return content;
}
// Step 2: Complete imports and constructor
function updateImportsAndConstructor(content) {
const importRegex =
/import { (.*)EntityRepository(.*) } from '@mikro-orm\/(core|mongodb|mysql|postgresql|sqlite)';/g;
const importReplacement = `import { $1EntityRepository$2, EntityManager } from '@mikro-orm/$3';`;
content = content.replace(importRegex, importReplacement);
const duplicatedImportRegex =
/import {(.*)EntityManager,(.*)EntityManager(?:,)?(.*)} from '@mikro-orm\/(core|mongodb|mysql|postgresql|sqlite)';/g;
const duplicatedImportReplacement = `import {$1EntityManager,$2$3} from '@mikro-orm/$4';`;
content = content.replace(duplicatedImportRegex, duplicatedImportReplacement);
const constructorRegex = /(.*)constructor\(/g;
const constructorReplacement = `$1constructor(\n$1 private readonly em: EntityManager,\n`;
content = content.replace(constructorRegex, constructorReplacement);
const duplicatedConstructorRegex =
/(.*)(private readonly em: EntityManager,)([\s\S\r]+)private readonly em: EntityManager,/g;
const duplicatedConstructorReplacement = `$1private readonly em: EntityManager,$3`;
content = content.replace(duplicatedConstructorRegex, duplicatedConstructorReplacement);
const packageRegex =
/import {(.*?)(EntityRepository|EntityManager)(?:, )?(.*)(EntityRepository|EntityManager)(?:, )?(.*)} from '@mikro-orm\/core';/g;
const packageReplacement = `import { $1$3$5 } from '@mikro-orm/core';\nimport { $2, $4 } from '@mikro-orm/${driver}';`;
content = content.replace(packageRegex, packageReplacement);
const emptyImportRegex = /import {\s*} from '@mikro-orm\/core';/g;
content = content.replace(emptyImportRegex, '');
content = content.replace(/,\n\n/g, ',\n');
return content;
}
// Function to process each file
function processFile(filePath) {
let content = fs.readFileSync(filePath, 'utf8');
// Apply Step 1 transformations
content = replacePersistAndFlush(content, filePath);
// Apply Step 2 transformations only if the file was updated in Step 1
if (updatedFiles.includes(filePath)) {
content = updateImportsAndConstructor(content);
}
fs.writeFileSync(filePath, content, 'utf8');
}
// Main function to traverse and process files
function main() {
const srcDir = path.resolve(__dirname, 'apps'); // Adjust the source directory if necessary
// Use glob to find all JavaScript/TypeScript files excluding node_modules
glob(`${srcDir}/**/*.{js,ts}`, { ignore: '**/node_modules/**' }, (err, files) => {
if (err) throw err;
files.forEach((file) => {
processFile(file);
});
console.log('Migration completed. Processed files:', updatedFiles.length);
});
}
main();
Assistant, we are senior software engineers that work in a nestjs project with mikro-orm. We are going to code a migration script for mikro-orm v5 to v6. I will guide you through the necessary steps and you will code it in javascript.
The way I found easiest for this migration, is to use regex in vscode to find and replace. I already did it and worked, but I would like to make steps reproducible for other repos, so a migration script is the better option.
These are the mikro-orm changes:
Persisting and Flushing is now a job of entity manager and not of the entity repository. So everywhere there is a persisAndFlush from the Entity Repository we have to migrate it to be flushed by the entity manager.
These are the steps:
# Step 1: Replace persisAndFlush for the new `em.persist` and `em.flush`.
In the current directory, we will open each file recursively, not including node_modules folder. The regex expressions are already escaped. We will use regex replace as follows:
Find:
`(.*)await this\.(.*)\.persistAndFlush\((.*)\);`
replace:
`$1this.em.persist($3);\n$1await this.em.flush();`
Because later we will have to add imports and initialize in constructor, these changed files paths we will store in an array called `updatedFiles`
# Step 2: Complete imports and constructor
As changes in Step 1 require entity manager to be available, we need to import the entity manager and add it to the constructor, ONLY in the `updatedFiles` (files changed in step 1). To do this, we will use the following regular expressions:
Find:
`import \{ (.*)EntityRepository(.*) \} from '@mikro-orm\/(core|mongodb|mysql|postgresql|sqlite)';`
Replace:
`import { $1EntityRepository$2, EntityManager } from '@mikro-orm/$3';`
This could create duplicated imports, so we have to remove them with regex as well.
Find:
`import \{(.*)EntityManager,(.*)EntityManager(?:,)?(.*)\} from '@mikro-orm\/(core|mongodb|mysql|postgresql|sqlite)';`
Replace:
`import {$1EntityManager,$2$3} from '@mikro-orm/$4';`
Now, we have to add the entity manager to the constructor of the class.
Find:
`(.*)constructor\(`
Replace:
`$1constructor(\n$1 private readonly em: EntityManager,\n`
This could also create duplicated imports, so we have to remove them with regex as well.
Find:
`(.*)(private readonly em: EntityManager,)([\s\S\r]+)private readonly em: EntityManager,`
Replace:
`$1private readonly em: EntityManager,$3`
Now the entity manager is no longer imported from `mikro-orm/core`, but from `mikro-orm/<driver>` so we have change the package from where the manager and repository are imported.
We use postgresql in our project, but it could be a static variable for now.
Find:
`import \{(.*?)(EntityRepository|EntityManager)(?:, )?(.*)(EntityRepository|EntityManager)(?:, )?(.*)\} from '@mikro-orm\/core';`
Replace:
`import { $1$3$5 } from '@mikro-orm/core';\nimport { $2, $4 } from '@mikro-orm/postgresql';`
This could lead to empty imports to `mikro-orm/core` so we will fix that too:
Find:
`import \{\s*\} from '@mikro-orm\/core';`
Replace:
``
Finally, it's possible some spaces were left in between constructor definitions, so we can fix that too with this:
Find:
`,\n\n`
Replace:
`,\n`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment