breaking: rework how links database migrations are managed (#8162)
This commit is contained in:
@@ -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
|
||||
@@ -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 })
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user