From d4d79c1141c33596a66c2c90e95cff9b6e9d1da6 Mon Sep 17 00:00:00 2001 From: Alessio Gravili Date: Thu, 12 Dec 2024 09:39:20 -0700 Subject: [PATCH] feat: allow loading predefined migrations from proper exports (#9872) Currently, predefined migrations can only be loaded if they are part of one of our db adapters. With this PR, plugins will be able to export their own predefined migrations that can be created like this: `pnpm payload migrate:create --file @payloadcms/plugin-someplugin/someMigration` with the plugin exporting it in their package.json: ```json "exports": { "./someMigration": { "import": "./someMigration.mjs", "types": "./someMigration.mjs", "default": "./someMigration.mjs" } }, ``` --- .../migrations/getPredefinedMigration.ts | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/payload/src/database/migrations/getPredefinedMigration.ts b/packages/payload/src/database/migrations/getPredefinedMigration.ts index be0486bee61..eaa425f9d61 100644 --- a/packages/payload/src/database/migrations/getPredefinedMigration.ts +++ b/packages/payload/src/database/migrations/getPredefinedMigration.ts @@ -21,11 +21,12 @@ export const getPredefinedMigration = async ({ }): Promise => { // Check for predefined migration. // Either passed in via --file or prefixed with '@payloadcms/db-mongodb/' for example - if (file || migrationNameArg?.startsWith('@payloadcms/')) { + const importPath = file ?? migrationNameArg + + if (importPath?.startsWith('@payloadcms/db-')) { // removes the package name from the migrationName. - const migrationName = (file || migrationNameArg).split('/').slice(2).join('/') + const migrationName = importPath.split('/').slice(2).join('/') let cleanPath = path.join(dirname, `./predefinedMigrations/${migrationName}`) - if (fs.existsSync(`${cleanPath}.mjs`)) { cleanPath = `${cleanPath}.mjs` } else if (fs.existsSync(`${cleanPath}.js`)) { @@ -36,12 +37,15 @@ export const getPredefinedMigration = async ({ }) process.exit(1) } - cleanPath = cleanPath.replaceAll('\\', '/') const moduleURL = pathToFileURL(cleanPath) try { const { downSQL, imports, upSQL } = await eval(`import('${moduleURL.href}')`) - return { downSQL, imports, upSQL } + return { + downSQL, + imports, + upSQL, + } } catch (err) { payload.logger.error({ err, @@ -49,6 +53,22 @@ export const getPredefinedMigration = async ({ }) process.exit(1) } + } else if (importPath) { + try { + const { downSQL, imports, upSQL } = await eval(`import('${importPath}')`) + return { + downSQL, + imports, + upSQL, + } + } catch (_err) { + if (importPath?.includes('/')) { + // We can assume that the intent was to import a file, thus we throw an error. + throw new Error(`Error importing migration file from ${importPath}`) + } + // Silently fail. If the migration cannot be imported, it will be created as a blank migration and the import path will be used as the migration name. + return {} + } } return {} }