breaking: rework how links database migrations are managed (#8162)

This commit is contained in:
Adrien de Peretti
2024-07-22 13:12:23 +05:30
committed by GitHub
parent f435c6c7f6
commit f74fdcb644
24 changed files with 1090 additions and 264 deletions
+176
View File
@@ -0,0 +1,176 @@
import boxen from "boxen"
import chalk from "chalk"
import checkbox from "@inquirer/checkbox"
import Logger from "../loaders/logger"
import { initializeContainer } from "../loaders"
import { ContainerRegistrationKeys } from "@medusajs/utils"
import { getResolvedPlugins } from "../loaders/helpers/resolve-plugins"
import { resolvePluginsLinks } from "../loaders/helpers/resolve-plugins-links"
import { getLinksExecutionPlanner } from "../loaders/medusa-app"
import { LinkMigrationsPlannerAction } from "@medusajs/types"
type Action = "sync"
/**
* Groups action tables by their "action" property
* @param actionPlan LinkMigrationsPlannerAction
*/
function groupByActionPlan(actionPlan: LinkMigrationsPlannerAction[]) {
return actionPlan.reduce((acc, action) => {
acc[action.action] ??= []
acc[action.action].push(action)
return acc
}, {} as Record<"noop" | "notify" | "create" | "update" | "delete", LinkMigrationsPlannerAction[]>)
}
/**
* Creates the link description for printing it to the
* console
*
* @param action: LinkMigrationsPlannerAction
*/
function buildLinkDescription(action: LinkMigrationsPlannerAction) {
const { linkDescriptor } = action
const from = chalk.yellow(
`${linkDescriptor.fromModule}.${linkDescriptor.fromModel}`
)
const to = chalk.yellow(
`${linkDescriptor.toModule}.${linkDescriptor.toModel}`
)
const table = chalk.dim(`(${action.tableName})`)
return `${from} <> ${to} ${table}`
}
/**
* Logs the actions of a given action type with a nice border and
* a title
*/
function logActions(
title: string,
actionsOrContext: LinkMigrationsPlannerAction[]
) {
const actionsList = actionsOrContext
.map((action) => ` - ${buildLinkDescription(action)}`)
.join("\n")
console.log(boxen(`${title}\n${actionsList}`, { padding: 1 }))
}
/**
* Displays a prompt to select tables that must be impacted with
* action
*/
async function askForLinkActionsToPerform(
message: string,
actions: LinkMigrationsPlannerAction[]
) {
console.log(boxen(message, { borderColor: "red", padding: 1 }))
return await checkbox({
message: "Select tables to act upon",
instructions: chalk.dim(
" <space> select, <a> select all, <i> inverse, <enter> submit"
),
choices: actions.map((action) => {
return {
name: buildLinkDescription(action),
value: action,
checked: false,
}
}),
})
}
const main = async function ({ directory }) {
const args = process.argv
args.shift()
args.shift()
args.shift()
const action = args[0] as Action
if (action !== "sync") {
return process.exit()
}
try {
const container = await initializeContainer(directory)
const configModule = container.resolve(
ContainerRegistrationKeys.CONFIG_MODULE
)
const plugins = getResolvedPlugins(directory, configModule, true) || []
const pluginLinks = await resolvePluginsLinks(plugins, container)
const planner = await getLinksExecutionPlanner({
configModule,
linkModules: pluginLinks,
container,
})
Logger.info("Syncing links...")
const actionPlan = await planner.createPlan()
const groupActionPlan = groupByActionPlan(actionPlan)
if (groupActionPlan.delete?.length) {
groupActionPlan.delete = await askForLinkActionsToPerform(
`Select the tables to ${chalk.red(
"DELETE"
)}. The following links have been removed`,
groupActionPlan.delete
)
}
if (groupActionPlan.notify?.length) {
const answer = await askForLinkActionsToPerform(
`Select the tables to ${chalk.red(
"UPDATE"
)}. The following links have been updated`,
groupActionPlan.notify
)
groupActionPlan.update ??= []
groupActionPlan.update.push(
...answer.map((action) => {
return {
...action,
action: "update",
} as LinkMigrationsPlannerAction
})
)
}
const toCreate = groupActionPlan.create ?? []
const toUpdate = groupActionPlan.update ?? []
const toDelete = groupActionPlan.delete ?? []
const actionsToExecute = [...toCreate, ...toUpdate, ...toDelete]
await planner.executePlan(actionsToExecute)
if (toCreate.length) {
logActions("Created following links tables", toCreate)
}
if (toUpdate.length) {
logActions("Updated following links tables", toUpdate)
}
if (toDelete.length) {
logActions("Deleted following links tables", toDelete)
}
if (actionsToExecute.length) {
Logger.info("Links sync completed")
} else {
Logger.info("Database already up-to-date")
}
process.exit()
} catch (e) {
Logger.error(e)
process.exit(1)
}
}
export default main
+1 -1
View File
@@ -40,7 +40,7 @@ const main = async function ({ directory }) {
args.shift()
args.shift()
const action = args[0] as "run" | "revert" | "generate" | "show"
const action = args[0] as Action
const modules = args.splice(1)
validateInputArgs({ action, modules })
+54 -1
View File
@@ -1,5 +1,6 @@
import {
MedusaApp,
MedusaAppGetLinksExecutionPlanner,
MedusaAppMigrateDown,
MedusaAppMigrateGenerate,
MedusaAppMigrateUp,
@@ -10,6 +11,7 @@ import {
import {
CommonTypes,
ConfigModule,
ILinkMigrationsPlanner,
InternalModuleDeclaration,
LoadedModule,
MedusaContainer,
@@ -134,6 +136,57 @@ export async function runMedusaAppMigrations({
}
}
/**
* Return an instance of the link module migration planner.
*
* @param configModule
* @param container
* @param linkModules
*/
export async function getLinksExecutionPlanner({
configModule,
container,
linkModules,
}: {
configModule: {
modules?: CommonTypes.ConfigModule["modules"]
projectConfig: CommonTypes.ConfigModule["projectConfig"]
}
linkModules?: MedusaAppOptions["linkModules"]
container: MedusaContainer
}): Promise<ILinkMigrationsPlanner> {
const injectedDependencies = {
[ContainerRegistrationKeys.PG_CONNECTION]: container.resolve(
ContainerRegistrationKeys.PG_CONNECTION
),
[ContainerRegistrationKeys.LOGGER]: container.resolve(
ContainerRegistrationKeys.LOGGER
),
}
const sharedResourcesConfig = {
database: {
clientUrl:
injectedDependencies[ContainerRegistrationKeys.PG_CONNECTION]?.client
?.config?.connection?.connectionString ??
configModule.projectConfig.databaseUrl,
driverOptions: configModule.projectConfig.databaseDriverOptions,
debug: !!(configModule.projectConfig.databaseLogging ?? false),
},
}
const configModules = mergeDefaultModules(configModule.modules)
const migrationOptions = {
modulesConfig: configModules,
sharedContainer: container,
linkModules,
sharedResourcesConfig,
injectedDependencies,
}
return await MedusaAppGetLinksExecutionPlanner(migrationOptions)
}
export const loadMedusaApp = async (
{
container,
@@ -200,7 +253,7 @@ export const loadMedusaApp = async (
)
}
// Register all unresolved modules as undefined to be present in the container with undefined value by defaul
// Register all unresolved modules as undefined to be present in the container with undefined value by default
// but still resolvable
for (const moduleDefinition of Object.values(ModulesDefinition)) {
if (!container.hasRegistration(moduleDefinition.registrationName)) {