Skip to content

Commit 356283c

Browse files
authored
chore(): Accept an extra agument 'all-or-nothing' on the migrate command (medusajs#14262)
* chore(): Accept an extra agument 'all-or-nothing' on the migrate command * Create rich-camels-brush.md * chore(): Accept an extra agument 'all-or-nothing' on the migrate command * chore(): Accept an extra agument 'all-or-nothing' on the migrate command * chore(): Accept an extra agument 'all-or-nothing' on the migrate command * chore(): fix broken down migrations * chore(): update changeset
1 parent 9bcfb99 commit 356283c

File tree

13 files changed

+139
-56
lines changed

13 files changed

+139
-56
lines changed

.changeset/rich-camels-brush.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@medusajs/medusa": patch
3+
"@medusajs/framework": patch
4+
"@medusajs/modules-sdk": patch
5+
"@medusajs/cli": patch
6+
"@medusajs/product": patch
7+
"@medusajs/customer": patch
8+
"@medusajs/promotion": patch
9+
---
10+
11+
chore(): Accept an extra agument 'all-or-nothing' on the migrate command

packages/cli/medusa-cli/src/create-cli.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { sync as existsSync } from "fs-exists-cached"
21
import { setTelemetryEnabled } from "@medusajs/telemetry"
2+
import { sync as existsSync } from "fs-exists-cached"
33
import path from "path"
44
import resolveCwd from "resolve-cwd"
55
import { newStarter } from "./commands/new"
@@ -200,6 +200,12 @@ function buildLocalCommands(cli, isLocalProject) {
200200
type: "number",
201201
describe: "Number of concurrent migrations to run",
202202
})
203+
builder.option("all-or-nothing", {
204+
type: "boolean",
205+
describe:
206+
"If set, the command will fail if any migration fails and revert the migrations that were applied so far",
207+
default: false,
208+
})
203209
},
204210
handler: handlerP(
205211
getCommandHandler("db/migrate", (args, cmd) => {
@@ -431,12 +437,14 @@ function buildLocalCommands(cli, isLocalProject) {
431437
.option("workers", {
432438
type: "string",
433439
default: "0",
434-
describe: "Number of worker processes in cluster mode or a percentage of cluster size (e.g., 25%).",
440+
describe:
441+
"Number of worker processes in cluster mode or a percentage of cluster size (e.g., 25%).",
435442
})
436443
.option("servers", {
437444
type: "string",
438445
default: "0",
439-
describe: "Number of server processes in cluster mode or a percentage of cluster size (e.g., 25%).",
446+
describe:
447+
"Number of server processes in cluster mode or a percentage of cluster size (e.g., 25%).",
440448
}),
441449
handler: handlerP(
442450
getCommandHandler(`start`, (args, cmd) => {

packages/core/framework/src/medusa-app-loader.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -157,17 +157,15 @@ export class MedusaAppLoader {
157157
* @param action
158158
*/
159159
async runModulesMigrations(
160-
{
161-
moduleNames,
162-
action = "run",
163-
}:
160+
options:
164161
| {
165-
moduleNames?: never
166162
action: "run"
163+
allOrNothing?: boolean
167164
}
168165
| {
169-
moduleNames: string[]
170166
action: "revert" | "generate"
167+
moduleNames: string[]
168+
allOrNothing?: never
171169
} = {
172170
action: "run",
173171
}
@@ -185,14 +183,15 @@ export class MedusaAppLoader {
185183
injectedDependencies,
186184
medusaConfigPath: this.#medusaConfigPath,
187185
cwd: this.#cwd,
186+
allOrNothing: options.allOrNothing,
188187
}
189188

190-
if (action === "revert") {
191-
await MedusaAppMigrateDown(moduleNames!, migrationOptions)
192-
} else if (action === "run") {
189+
if (options.action === "revert") {
190+
await MedusaAppMigrateDown(options.moduleNames!, migrationOptions)
191+
} else if (options.action === "run") {
193192
await MedusaAppMigrateUp(migrationOptions)
194-
} else {
195-
await MedusaAppMigrateGenerate(moduleNames!, migrationOptions)
193+
} else if (options.action === "generate") {
194+
await MedusaAppMigrateGenerate(options.moduleNames!, migrationOptions)
196195
}
197196
}
198197

packages/core/modules-sdk/src/loaders/utils/load-internal.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ type ModuleResource = {
4848
type MigrationFunction = (
4949
options: LoaderOptions<any>,
5050
moduleDeclaration?: InternalModuleDeclaration
51+
) => Promise<{ name: string; path: string }[]>
52+
type RevertMigrationFunction = (
53+
options: LoaderOptions<any> & { migrationNames?: string[] },
54+
moduleDeclaration?: InternalModuleDeclaration
55+
) => Promise<void>
56+
type GenerateMigrationFunction = (
57+
options: LoaderOptions<any>,
58+
moduleDeclaration?: InternalModuleDeclaration
5159
) => Promise<void>
5260

5361
type ResolvedModule = ModuleExports & {
@@ -390,8 +398,8 @@ export async function loadModuleMigrations(
390398
moduleExports?: ModuleExports
391399
): Promise<{
392400
runMigrations?: MigrationFunction
393-
revertMigration?: MigrationFunction
394-
generateMigration?: MigrationFunction
401+
revertMigration?: RevertMigrationFunction
402+
generateMigration?: GenerateMigrationFunction
395403
}> {
396404
const runMigrationsFn: ((...args) => Promise<any>)[] = []
397405
const revertMigrationFn: ((...args) => Promise<any>)[] = []
@@ -488,9 +496,12 @@ export async function loadModuleMigrations(
488496
}
489497

490498
const runMigrations = async (...args) => {
499+
let result: { name: string; path: string }[] = []
491500
for (const migration of runMigrationsFn.filter(Boolean)) {
492-
await migration.apply(migration, args)
501+
const res = await migration.apply(migration, args)
502+
result.push(...res)
493503
}
504+
return result
494505
}
495506
const revertMigration = async (...args) => {
496507
for (const migration of revertMigrationFn.filter(Boolean)) {

packages/core/modules-sdk/src/medusa-app.ts

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ import { MODULE_SCOPE } from "./types"
4747

4848
const LinkModulePackage = MODULE_PACKAGE_NAMES[Modules.LINK]
4949

50-
export type RunMigrationFn = () => Promise<void>
50+
export type RunMigrationFn = (options?: {
51+
allOrNothing?: boolean
52+
}) => Promise<void>
5153
export type RevertMigrationFn = (moduleNames: string[]) => Promise<void>
5254
export type GenerateMigrations = (moduleNames: string[]) => Promise<void>
5355
export type GetLinkExecutionPlanner = () => ILinkMigrationsPlanner
@@ -498,10 +500,12 @@ async function MedusaApp_({
498500
const applyMigration = async ({
499501
modulesNames,
500502
action = "run",
503+
allOrNothing = false,
501504
}: {
502505
modulesNames: string[]
503506
action?: "run" | "revert" | "generate"
504-
}) => {
507+
allOrNothing?: boolean
508+
}): Promise<{ name: string; path: string }[] | void> => {
505509
const moduleResolutions = Array.from(new Set(modulesNames)).map(
506510
(moduleName) => {
507511
return {
@@ -527,7 +531,11 @@ async function MedusaApp_({
527531
throw error
528532
}
529533

530-
const run = async ({ resolution: moduleResolution }) => {
534+
let executedResolutions: [any, string[]][] = [] // [moduleResolution, migration names[]]
535+
const run = async (
536+
{ resolution: moduleResolution },
537+
migrationNames?: string[]
538+
) => {
531539
if (
532540
!moduleResolution.options?.database &&
533541
moduleResolution.moduleDeclaration?.scope === MODULE_SCOPE.INTERNAL
@@ -550,24 +558,62 @@ async function MedusaApp_({
550558
}
551559

552560
if (action === "revert") {
553-
await MedusaModule.migrateDown(migrationOptions)
561+
await MedusaModule.migrateDown(migrationOptions, migrationNames)
554562
} else if (action === "run") {
555-
await MedusaModule.migrateUp(migrationOptions)
563+
const ranMigrationsResult = await MedusaModule.migrateUp(
564+
migrationOptions
565+
)
566+
567+
// Store for revert if anything goes wrong later
568+
executedResolutions.push([
569+
moduleResolution,
570+
ranMigrationsResult?.map((r) => r.name) ?? [],
571+
])
556572
} else {
557573
await MedusaModule.migrateGenerate(migrationOptions)
558574
}
559575
}
560576

561577
const concurrency = parseInt(process.env.DB_MIGRATION_CONCURRENCY ?? "1")
562-
await executeWithConcurrency(
563-
moduleResolutions.map((a) => () => run(a)),
564-
concurrency
565-
)
578+
try {
579+
const results = await executeWithConcurrency(
580+
moduleResolutions.map((a) => () => run(a)),
581+
concurrency
582+
)
583+
const rejections = results.filter(
584+
(result) => result.status === "rejected"
585+
)
586+
if (rejections.length) {
587+
throw new Error(
588+
`Some migrations failed to ${action}: ${rejections
589+
.map((r) => r.reason)
590+
.join(", ")}`
591+
)
592+
}
593+
} catch (error) {
594+
if (allOrNothing) {
595+
action = "revert"
596+
await executeWithConcurrency(
597+
executedResolutions.map(
598+
([resolution, migrationNames]) =>
599+
() =>
600+
run({ resolution }, migrationNames)
601+
),
602+
concurrency
603+
)
604+
}
605+
throw error
606+
}
566607
}
567608

568-
const runMigrations: RunMigrationFn = async (): Promise<void> => {
609+
const runMigrations: RunMigrationFn = async (
610+
{ allOrNothing = false }: { allOrNothing?: boolean } = {
611+
allOrNothing: false,
612+
}
613+
): Promise<void> => {
569614
await applyMigration({
570615
modulesNames: Object.keys(allModules),
616+
allOrNothing,
571617
})
572618
}
573619

@@ -638,7 +684,7 @@ export async function MedusaApp(
638684
}
639685

640686
export async function MedusaAppMigrateUp(
641-
options: MedusaAppOptions = {}
687+
options: MedusaAppOptions & { allOrNothing?: boolean } = {}
642688
): Promise<void> {
643689
const migrationOnly = true
644690

@@ -647,7 +693,9 @@ export async function MedusaAppMigrateUp(
647693
migrationOnly,
648694
})
649695

650-
await runMigrations().finally(MedusaModule.clearInstances)
696+
await runMigrations({ allOrNothing: options.allOrNothing }).finally(
697+
MedusaModule.clearInstances
698+
)
651699
}
652700

653701
export async function MedusaAppMigrateDown(

packages/core/modules-sdk/src/medusa-module.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -828,7 +828,7 @@ class MedusaModule {
828828
moduleKey,
829829
modulePath,
830830
cwd,
831-
}: MigrationOptions): Promise<void> {
831+
}: MigrationOptions): Promise<{ name: string; path: string }[]> {
832832
const moduleResolutions = registerMedusaModule({
833833
moduleKey,
834834
moduleDeclaration: {
@@ -846,6 +846,7 @@ class MedusaModule {
846846

847847
container ??= createMedusaContainer()
848848

849+
let result: { name: string; path: string }[] = []
849850
for (const mod in moduleResolutions) {
850851
const { runMigrations } = await loadModuleMigrations(
851852
container,
@@ -854,23 +855,29 @@ class MedusaModule {
854855
)
855856

856857
if (typeof runMigrations === "function") {
857-
await runMigrations({
858+
const res = await runMigrations({
858859
options,
859860
container: container!,
860861
logger: logger_,
861862
})
863+
result.push(...res)
862864
}
863865
}
866+
867+
return result
864868
}
865869

866-
public static async migrateDown({
867-
options,
868-
container,
869-
moduleExports,
870-
moduleKey,
871-
modulePath,
872-
cwd,
873-
}: MigrationOptions): Promise<void> {
870+
public static async migrateDown(
871+
{
872+
options,
873+
container,
874+
moduleExports,
875+
moduleKey,
876+
modulePath,
877+
cwd,
878+
}: MigrationOptions,
879+
migrationNames?: string[]
880+
): Promise<void> {
874881
const moduleResolutions = registerMedusaModule({
875882
moduleKey,
876883
moduleDeclaration: {
@@ -900,6 +907,7 @@ class MedusaModule {
900907
options,
901908
container: container!,
902909
logger: logger_,
910+
migrationNames,
903911
})
904912
}
905913
}

packages/core/types/src/modules-sdk/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,10 +267,11 @@ export type ModuleExports<T = Constructor<any>> = {
267267
runMigrations?(
268268
options: LoaderOptions<any>,
269269
moduleDeclaration?: InternalModuleDeclaration
270-
): Promise<void>
270+
): Promise<{ name: string; path: string }[]>
271271
revertMigration?(
272272
options: LoaderOptions<any>,
273-
moduleDeclaration?: InternalModuleDeclaration
273+
moduleDeclaration?: InternalModuleDeclaration,
274+
migrationNames?: string[]
274275
): Promise<void>
275276
generateMigration?(
276277
options: LoaderOptions<any>,

packages/core/utils/src/modules-sdk/migration-scripts/migration-down.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ export function buildRevertMigrationScript({ moduleName, pathToMigrations }) {
2323
return async function ({
2424
options,
2525
logger,
26+
migrationNames,
2627
}: Pick<
2728
LoaderOptions<ModulesSdkTypes.ModuleServiceInitializeOptions>,
2829
"options" | "logger"
29-
> = {}) {
30+
> & { migrationNames?: string[] }) {
3031
logger ??= console as unknown as Logger
3132

3233
logger.info(new Array(TERMINAL_SIZE).join("-"))
@@ -48,7 +49,10 @@ export function buildRevertMigrationScript({ moduleName, pathToMigrations }) {
4849
})
4950

5051
try {
51-
const result = await migrations.revert()
52+
const revertOptions = migrationNames?.length
53+
? { step: migrationNames.length }
54+
: undefined
55+
const result = await migrations.revert(revertOptions as any)
5256
if (result.length) {
5357
logger.info("Reverted successfully")
5458
} else {

packages/core/utils/src/modules-sdk/migration-scripts/migration-up.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export function buildMigrationScript({ moduleName, pathToMigrations }) {
5151
} else {
5252
logger.info(`Skipped. Database is up-to-date for module.`)
5353
}
54+
return result
5455
} catch (error) {
5556
logger.error(`Failed with error ${error.message}`, error)
5657
throw new MedusaError(MedusaError.Types.DB_ERROR, error.message)

0 commit comments

Comments
 (0)