arkormx 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1,14 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "fs";
3
- import path, { dirname, extname, join, relative } from "path";
4
- import { createRequire } from "module";
5
- import { existsSync as existsSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, writeFileSync as writeFileSync$1 } from "node:fs";
6
- import { join as join$1, resolve } from "node:path";
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { dirname, extname, join, resolve } from "node:path";
7
4
  import { spawnSync } from "node:child_process";
8
5
  import { str } from "@h3ravel/support";
6
+ import path, { dirname as dirname$1, extname as extname$1, join as join$1, relative } from "path";
7
+ import { copyFileSync, existsSync as existsSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, rmSync as rmSync$1, writeFileSync as writeFileSync$1 } from "fs";
9
8
  import { fileURLToPath, pathToFileURL } from "url";
9
+ import { createRequire } from "module";
10
10
  import { Logger } from "@h3ravel/shared";
11
11
  import { Command, Kernel } from "@h3ravel/musket";
12
+ import { createHash } from "node:crypto";
12
13
  import { pathToFileURL as pathToFileURL$1 } from "node:url";
13
14
 
14
15
  //#region src/Exceptions/ArkormException.ts
@@ -1007,14 +1008,14 @@ const generateMigrationFile = (name, options = {}) => {
1007
1008
  const fileSlug = toMigrationFileSlug(name);
1008
1009
  const className = resolveMigrationClassName(name);
1009
1010
  const extension = options.extension ?? "ts";
1010
- const directory = options.directory ?? join$1(process.cwd(), "database", "migrations");
1011
+ const directory = options.directory ?? join(process.cwd(), "database", "migrations");
1011
1012
  const fileName = `${timestamp}_${fileSlug}.${extension}`;
1012
- const filePath = join$1(directory, fileName);
1013
+ const filePath = join(directory, fileName);
1013
1014
  const content = buildMigrationSource(className, extension);
1014
1015
  if (options.write ?? true) {
1015
- if (!existsSync$1(directory)) mkdirSync$1(directory, { recursive: true });
1016
- if (existsSync$1(filePath)) throw new ArkormException(`Migration file already exists: ${filePath}`);
1017
- writeFileSync$1(filePath, content);
1016
+ if (!existsSync(directory)) mkdirSync(directory, { recursive: true });
1017
+ if (existsSync(filePath)) throw new ArkormException(`Migration file already exists: ${filePath}`);
1018
+ writeFileSync(filePath, content);
1018
1019
  }
1019
1020
  return {
1020
1021
  fileName,
@@ -1047,12 +1048,32 @@ const getMigrationPlan = async (migration, direction = "up") => {
1047
1048
  * @returns A promise that resolves to an object containing the updated schema, schema path, and list of operations applied.
1048
1049
  */
1049
1050
  const applyMigrationToPrismaSchema = async (migration, options = {}) => {
1050
- const schemaPath = options.schemaPath ?? join$1(process.cwd(), "prisma", "schema.prisma");
1051
- if (!existsSync$1(schemaPath)) throw new ArkormException(`Prisma schema file not found: ${schemaPath}`);
1052
- const source = readFileSync$1(schemaPath, "utf-8");
1051
+ const schemaPath = options.schemaPath ?? join(process.cwd(), "prisma", "schema.prisma");
1052
+ if (!existsSync(schemaPath)) throw new ArkormException(`Prisma schema file not found: ${schemaPath}`);
1053
+ const source = readFileSync(schemaPath, "utf-8");
1053
1054
  const operations = await getMigrationPlan(migration, "up");
1054
1055
  const schema = applyOperationsToPrismaSchema(source, operations);
1055
- if (options.write ?? true) writeFileSync$1(schemaPath, schema);
1056
+ if (options.write ?? true) writeFileSync(schemaPath, schema);
1057
+ return {
1058
+ schema,
1059
+ schemaPath,
1060
+ operations
1061
+ };
1062
+ };
1063
+ /**
1064
+ * Apply the rollback (down) operations defined in a migration to a Prisma schema file.
1065
+ *
1066
+ * @param migration The migration class or instance to rollback.
1067
+ * @param options Options for applying the rollback, including schema path and write flag.
1068
+ * @returns A promise that resolves to an object containing the updated schema, schema path, and rollback operations applied.
1069
+ */
1070
+ const applyMigrationRollbackToPrismaSchema = async (migration, options = {}) => {
1071
+ const schemaPath = options.schemaPath ?? join(process.cwd(), "prisma", "schema.prisma");
1072
+ if (!existsSync(schemaPath)) throw new ArkormException(`Prisma schema file not found: ${schemaPath}`);
1073
+ const source = readFileSync(schemaPath, "utf-8");
1074
+ const operations = await getMigrationPlan(migration, "down");
1075
+ const schema = applyOperationsToPrismaSchema(source, operations);
1076
+ if (options.write ?? true) writeFileSync(schemaPath, schema);
1056
1077
  return {
1057
1078
  schema,
1058
1079
  schemaPath,
@@ -1067,7 +1088,7 @@ const resolveDefaultStubsPath = () => {
1067
1088
  while (true) {
1068
1089
  const packageJsonPath = path.join(current, "package.json");
1069
1090
  const stubsPath = path.join(current, "stubs");
1070
- if (existsSync(packageJsonPath) && existsSync(stubsPath)) return stubsPath;
1091
+ if (existsSync$1(packageJsonPath) && existsSync$1(stubsPath)) return stubsPath;
1071
1092
  const parent = path.dirname(current);
1072
1093
  if (parent === current) break;
1073
1094
  current = parent;
@@ -1166,7 +1187,7 @@ const loadRuntimeConfigSync = () => {
1166
1187
  const require = createRequire(import.meta.url);
1167
1188
  const syncConfigPaths = [path.join(process.cwd(), "arkormx.config.cjs")];
1168
1189
  for (const configPath of syncConfigPaths) {
1169
- if (!existsSync(configPath)) continue;
1190
+ if (!existsSync$1(configPath)) continue;
1170
1191
  try {
1171
1192
  resolveAndApplyConfig(require(configPath));
1172
1193
  return true;
@@ -1188,7 +1209,7 @@ const loadArkormConfig = async () => {
1188
1209
  runtimeConfigLoadingPromise = (async () => {
1189
1210
  const configPaths = [path.join(process.cwd(), "arkormx.config.js"), path.join(process.cwd(), "arkormx.config.ts")];
1190
1211
  for (const configPath of configPaths) {
1191
- if (!existsSync(configPath)) continue;
1212
+ if (!existsSync$1(configPath)) continue;
1192
1213
  try {
1193
1214
  resolveAndApplyConfig(await importConfigFile(configPath));
1194
1215
  return;
@@ -1232,8 +1253,8 @@ var CliApp = class {
1232
1253
  * @param filePath
1233
1254
  */
1234
1255
  ensureDirectory(filePath) {
1235
- const dir = dirname(filePath);
1236
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
1256
+ const dir = dirname$1(filePath);
1257
+ if (!existsSync$1(dir)) mkdirSync$1(dir, { recursive: true });
1237
1258
  }
1238
1259
  /**
1239
1260
  * Convert absolute paths under current working directory into relative display paths.
@@ -1282,13 +1303,13 @@ var CliApp = class {
1282
1303
  * @returns
1283
1304
  */
1284
1305
  resolveRuntimeDirectoryPath(directoryPath) {
1285
- if (existsSync(directoryPath)) return directoryPath;
1306
+ if (existsSync$1(directoryPath)) return directoryPath;
1286
1307
  const { buildOutput } = this.getConfig("paths") || {};
1287
1308
  if (typeof buildOutput !== "string" || buildOutput.trim().length === 0) return directoryPath;
1288
1309
  const relativeSource = relative(process.cwd(), directoryPath);
1289
1310
  if (!relativeSource || relativeSource.startsWith("..")) return directoryPath;
1290
- const mappedDirectory = join(buildOutput, relativeSource);
1291
- return existsSync(mappedDirectory) ? mappedDirectory : directoryPath;
1311
+ const mappedDirectory = join$1(buildOutput, relativeSource);
1312
+ return existsSync$1(mappedDirectory) ? mappedDirectory : directoryPath;
1292
1313
  }
1293
1314
  /**
1294
1315
  * Resolve a script file path for runtime execution.
@@ -1299,7 +1320,7 @@ var CliApp = class {
1299
1320
  * @returns
1300
1321
  */
1301
1322
  resolveRuntimeScriptPath(filePath) {
1302
- const extension = extname(filePath).toLowerCase();
1323
+ const extension = extname$1(filePath).toLowerCase();
1303
1324
  const isTsFile = extension === ".ts" || extension === ".mts" || extension === ".cts";
1304
1325
  const candidates = [];
1305
1326
  if (isTsFile) {
@@ -1310,15 +1331,15 @@ var CliApp = class {
1310
1331
  if (typeof buildOutput === "string" && buildOutput.trim().length > 0) {
1311
1332
  const relativeSource = relative(process.cwd(), filePath);
1312
1333
  if (relativeSource && !relativeSource.startsWith("..")) {
1313
- const mappedFile = join(buildOutput, relativeSource);
1314
- const mappedExtension = extname(mappedFile).toLowerCase();
1334
+ const mappedFile = join$1(buildOutput, relativeSource);
1335
+ const mappedExtension = extname$1(mappedFile).toLowerCase();
1315
1336
  if (mappedExtension === ".ts" || mappedExtension === ".mts" || mappedExtension === ".cts") {
1316
1337
  const mappedBase = mappedFile.slice(0, -mappedExtension.length);
1317
1338
  candidates.push(`${mappedBase}.js`, `${mappedBase}.cjs`, `${mappedBase}.mjs`);
1318
1339
  } else candidates.push(mappedFile);
1319
1340
  }
1320
1341
  }
1321
- const runtimeMatch = candidates.find((path) => existsSync(path));
1342
+ const runtimeMatch = candidates.find((path) => existsSync$1(path));
1322
1343
  if (runtimeMatch) return runtimeMatch;
1323
1344
  return filePath;
1324
1345
  }
@@ -1330,14 +1351,14 @@ var CliApp = class {
1330
1351
  * @param replacements
1331
1352
  */
1332
1353
  generateFile(stubPath, outputPath, replacements, options) {
1333
- if (existsSync(outputPath) && !options?.force) {
1354
+ if (existsSync$1(outputPath) && !options?.force) {
1334
1355
  this.command.error(`Error: ${this.formatPathForLog(outputPath)} already exists.`);
1335
1356
  process.exit(1);
1336
- } else if (existsSync(outputPath) && options?.force) rmSync(outputPath);
1337
- let content = readFileSync(stubPath, "utf-8");
1357
+ } else if (existsSync$1(outputPath) && options?.force) rmSync$1(outputPath);
1358
+ let content = readFileSync$1(stubPath, "utf-8");
1338
1359
  for (const [key, value] of Object.entries(replacements)) content = content.replace(new RegExp(`{{${key}}}`, "g"), value);
1339
1360
  this.ensureDirectory(outputPath);
1340
- writeFileSync(outputPath, content);
1361
+ writeFileSync$1(outputPath, content);
1341
1362
  return outputPath;
1342
1363
  }
1343
1364
  /**
@@ -1359,7 +1380,7 @@ var CliApp = class {
1359
1380
  * @returns
1360
1381
  */
1361
1382
  resolveStubPath(stubName) {
1362
- return join(this.resolveConfigPath("stubs", getDefaultStubsPath()), stubName);
1383
+ return join$1(this.resolveConfigPath("stubs", getDefaultStubsPath()), stubName);
1363
1384
  }
1364
1385
  /**
1365
1386
  * Generate a factory file for a given model name.
@@ -1373,9 +1394,9 @@ var CliApp = class {
1373
1394
  const factoryName = `${baseName}Factory`;
1374
1395
  const modelName = options.modelName ? str(options.modelName).pascal() : baseName;
1375
1396
  const outputExt = this.resolveOutputExt();
1376
- const outputPath = join(this.resolveConfigPath("factories", join(process.cwd(), "database", "factories")), `${factoryName}.${outputExt}`);
1377
- const modelPath = join(this.resolveConfigPath("models", join(process.cwd(), "src", "models")), `${modelName}.${outputExt}`);
1378
- const relativeImport = options.modelImportPath ?? `./${this.stripKnownSourceExtension(relative(dirname(outputPath), modelPath).replace(/\\/g, "/"))}${outputExt === "js" ? ".js" : ""}`;
1397
+ const outputPath = join$1(this.resolveConfigPath("factories", join$1(process.cwd(), "database", "factories")), `${factoryName}.${outputExt}`);
1398
+ const modelPath = join$1(this.resolveConfigPath("models", join$1(process.cwd(), "src", "models")), `${modelName}.${outputExt}`);
1399
+ const relativeImport = options.modelImportPath ?? `./${this.stripKnownSourceExtension(relative(dirname$1(outputPath), modelPath).replace(/\\/g, "/"))}${outputExt === "js" ? ".js" : ""}`;
1379
1400
  const stubPath = this.resolveStubPath(outputExt === "js" ? "factory.js.stub" : "factory.stub");
1380
1401
  return {
1381
1402
  name: factoryName,
@@ -1396,7 +1417,7 @@ var CliApp = class {
1396
1417
  makeSeeder(name, options = {}) {
1397
1418
  const seederName = `${str(name.replace(/Seeder$/, "")).pascal()}Seeder`;
1398
1419
  const outputExt = this.resolveOutputExt();
1399
- const outputPath = join(this.resolveConfigPath("seeders", join(process.cwd(), "database", "seeders")), `${seederName}.${outputExt}`);
1420
+ const outputPath = join$1(this.resolveConfigPath("seeders", join$1(process.cwd(), "database", "seeders")), `${seederName}.${outputExt}`);
1400
1421
  const stubPath = this.resolveStubPath(outputExt === "js" ? "seeder.js.stub" : "seeder.stub");
1401
1422
  return {
1402
1423
  name: seederName,
@@ -1411,7 +1432,7 @@ var CliApp = class {
1411
1432
  */
1412
1433
  makeMigration(name) {
1413
1434
  const generated = generateMigrationFile(name, {
1414
- directory: this.resolveConfigPath("migrations", join(process.cwd(), "database", "migrations")),
1435
+ directory: this.resolveConfigPath("migrations", join$1(process.cwd(), "database", "migrations")),
1415
1436
  extension: this.resolveOutputExt()
1416
1437
  });
1417
1438
  return {
@@ -1431,13 +1452,13 @@ var CliApp = class {
1431
1452
  const modelName = `${baseName}`;
1432
1453
  const delegateName = str(baseName).camel().plural().toString();
1433
1454
  const outputExt = this.resolveOutputExt();
1434
- const outputPath = join(this.resolveConfigPath("models", join(process.cwd(), "src", "models")), `${modelName}.${outputExt}`);
1455
+ const outputPath = join$1(this.resolveConfigPath("models", join$1(process.cwd(), "src", "models")), `${modelName}.${outputExt}`);
1435
1456
  const shouldBuildFactory = options.all || options.factory;
1436
1457
  const shouldBuildSeeder = options.all || options.seeder;
1437
1458
  const shouldBuildMigration = options.all || options.migration;
1438
1459
  const factoryName = `${baseName}Factory`;
1439
- const factoryPath = join(this.resolveConfigPath("factories", join(process.cwd(), "database", "factories")), `${factoryName}.${outputExt}`);
1440
- const factoryImportPath = `./${relative(dirname(outputPath), factoryPath).replace(/\\/g, "/").replace(/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i, "")}${outputExt === "js" ? ".js" : ""}`;
1460
+ const factoryPath = join$1(this.resolveConfigPath("factories", join$1(process.cwd(), "database", "factories")), `${factoryName}.${outputExt}`);
1461
+ const factoryImportPath = `./${relative(dirname$1(outputPath), factoryPath).replace(/\\/g, "/").replace(/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i, "")}${outputExt === "js" ? ".js" : ""}`;
1441
1462
  const stubPath = this.resolveStubPath(outputExt === "js" ? "model.js.stub" : "model.stub");
1442
1463
  const modelPath = this.generateFile(stubPath, outputPath, {
1443
1464
  ModelName: modelName,
@@ -1459,7 +1480,7 @@ var CliApp = class {
1459
1480
  if (shouldBuildFactory) created.factory = this.makeFactory(baseName, {
1460
1481
  force: options.force,
1461
1482
  modelName,
1462
- modelImportPath: `./${relative(dirname(factoryPath), outputPath).replace(/\\/g, "/").replace(/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i, "")}${outputExt === "js" ? ".js" : ""}`
1483
+ modelImportPath: `./${relative(dirname$1(factoryPath), outputPath).replace(/\\/g, "/").replace(/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i, "")}${outputExt === "js" ? ".js" : ""}`
1463
1484
  });
1464
1485
  if (shouldBuildSeeder) created.seeder = this.makeSeeder(baseName, { force: options.force });
1465
1486
  if (shouldBuildMigration) created.migration = this.makeMigration(`create ${delegateName} table`);
@@ -1474,26 +1495,28 @@ var CliApp = class {
1474
1495
  * @param delegateName The name of the delegate (table) to ensure in the Prisma schema.
1475
1496
  */
1476
1497
  ensurePrismaModelEntry(modelName, delegateName) {
1477
- const schemaPath = join(process.cwd(), "prisma", "schema.prisma");
1478
- if (!existsSync(schemaPath)) return {
1498
+ const schemaPath = join$1(process.cwd(), "prisma", "schema.prisma");
1499
+ if (!existsSync$1(schemaPath)) return {
1479
1500
  path: schemaPath,
1480
1501
  updated: false
1481
1502
  };
1482
- const source = readFileSync(schemaPath, "utf-8");
1503
+ const source = readFileSync$1(schemaPath, "utf-8");
1483
1504
  const existingByTable = findModelBlock(source, delegateName);
1484
1505
  const existingByName = new RegExp(`model\\s+${modelName}\\s*\\{`, "m").test(source);
1485
1506
  if (existingByTable || existingByName) return {
1486
1507
  path: schemaPath,
1487
1508
  updated: false
1488
1509
  };
1489
- writeFileSync(schemaPath, applyCreateTableOperation(source, {
1510
+ writeFileSync$1(schemaPath, applyCreateTableOperation(source, {
1490
1511
  type: "createTable",
1491
1512
  table: delegateName,
1492
1513
  columns: [{
1493
1514
  name: "id",
1494
1515
  type: "id",
1495
1516
  primary: true
1496
- }]
1517
+ }],
1518
+ indexes: [],
1519
+ foreignKeys: []
1497
1520
  }));
1498
1521
  return {
1499
1522
  path: schemaPath,
@@ -1545,14 +1568,14 @@ var CliApp = class {
1545
1568
  body.split("\n").forEach((rawLine) => {
1546
1569
  const line = rawLine.trim();
1547
1570
  if (!line || line.startsWith("@@") || line.startsWith("//")) return;
1548
- const fieldMatch = line.match(/^(\w+)\s+([A-Za-z]+)(\?)?\b/);
1571
+ const fieldMatch = line.match(/^(\w+)\s+([A-Za-z]+)(\?)?(?:\s|$)/);
1549
1572
  if (!fieldMatch) return;
1550
1573
  const fieldType = fieldMatch[2];
1551
1574
  if (!scalarTypes.has(fieldType)) return;
1552
1575
  fields.push({
1553
1576
  name: fieldMatch[1],
1554
1577
  type: this.prismaTypeToTs(fieldType),
1555
- optional: Boolean(fieldMatch[3])
1578
+ nullable: Boolean(fieldMatch[3])
1556
1579
  });
1557
1580
  });
1558
1581
  models.push({
@@ -1619,18 +1642,18 @@ var CliApp = class {
1619
1642
  * @returns An object with details about the synchronization process, including updated and skipped files.
1620
1643
  */
1621
1644
  syncModelsFromPrisma(options = {}) {
1622
- const schemaPath = options.schemaPath ?? join(process.cwd(), "prisma", "schema.prisma");
1623
- const modelsDir = options.modelsDir ?? this.resolveConfigPath("models", join(process.cwd(), "src", "models"));
1624
- if (!existsSync(schemaPath)) throw new Error(`Prisma schema file not found: ${schemaPath}`);
1625
- if (!existsSync(modelsDir)) throw new Error(`Models directory not found: ${modelsDir}`);
1626
- const schema = readFileSync(schemaPath, "utf-8");
1645
+ const schemaPath = options.schemaPath ?? join$1(process.cwd(), "prisma", "schema.prisma");
1646
+ const modelsDir = options.modelsDir ?? this.resolveConfigPath("models", join$1(process.cwd(), "src", "models"));
1647
+ if (!existsSync$1(schemaPath)) throw new Error(`Prisma schema file not found: ${schemaPath}`);
1648
+ if (!existsSync$1(modelsDir)) throw new Error(`Models directory not found: ${modelsDir}`);
1649
+ const schema = readFileSync$1(schemaPath, "utf-8");
1627
1650
  const prismaModels = this.parsePrismaModels(schema);
1628
- const modelFiles = readdirSync(modelsDir).filter((file) => file.endsWith(".ts"));
1651
+ const modelFiles = readdirSync$1(modelsDir).filter((file) => file.endsWith(".ts"));
1629
1652
  const updated = [];
1630
1653
  const skipped = [];
1631
1654
  modelFiles.forEach((file) => {
1632
- const filePath = join(modelsDir, file);
1633
- const source = readFileSync(filePath, "utf-8");
1655
+ const filePath = join$1(modelsDir, file);
1656
+ const source = readFileSync$1(filePath, "utf-8");
1634
1657
  const classMatch = source.match(/export\s+class\s+(\w+)\s+extends\s+Model<'([^']+)'>/);
1635
1658
  if (!classMatch) {
1636
1659
  skipped.push(filePath);
@@ -1643,13 +1666,13 @@ var CliApp = class {
1643
1666
  skipped.push(filePath);
1644
1667
  return;
1645
1668
  }
1646
- const declarations = prismaModel.fields.map((field) => `declare ${field.name}${field.optional ? "?" : ""}: ${field.type}`);
1669
+ const declarations = prismaModel.fields.map((field) => `declare ${field.name}: ${field.type}${field.nullable ? " | null" : ""}`);
1647
1670
  const synced = this.syncModelDeclarations(source, declarations);
1648
1671
  if (!synced.updated) {
1649
1672
  skipped.push(filePath);
1650
1673
  return;
1651
1674
  }
1652
- writeFileSync(filePath, synced.content);
1675
+ writeFileSync$1(filePath, synced.content);
1653
1676
  updated.push(filePath);
1654
1677
  });
1655
1678
  return {
@@ -1681,23 +1704,23 @@ var InitCommand = class extends Command {
1681
1704
  */
1682
1705
  async handle() {
1683
1706
  this.app.command = this;
1684
- const outputDir = join$1(process.cwd(), "arkormx.config.js");
1707
+ const outputDir = join(process.cwd(), "arkormx.config.js");
1685
1708
  const { stubs } = getUserConfig("paths") ?? {};
1686
1709
  const stubsDir = typeof stubs === "string" && stubs.trim().length > 0 ? stubs : getDefaultStubsPath();
1687
- const preferredStubPath = join$1(stubsDir, "arkormx.config.stub");
1688
- const legacyStubPath = join$1(stubsDir, "arkorm.config.stub");
1689
- const stubPath = existsSync(preferredStubPath) ? preferredStubPath : legacyStubPath;
1690
- if (existsSync(outputDir) && !this.option("force")) {
1710
+ const preferredStubPath = join(stubsDir, "arkormx.config.stub");
1711
+ const legacyStubPath = join(stubsDir, "arkorm.config.stub");
1712
+ const stubPath = existsSync$1(preferredStubPath) ? preferredStubPath : legacyStubPath;
1713
+ if (existsSync$1(outputDir) && !this.option("force")) {
1691
1714
  this.error("Error: Arkormˣ has already been initialized. Use --force to reinitialize.");
1692
1715
  process.exit(1);
1693
1716
  }
1694
1717
  this.app.ensureDirectory(outputDir);
1695
- if (existsSync(outputDir) && this.option("force")) copyFileSync(outputDir, outputDir.replace(/\.js$/, `.backup.${Date.now()}.js`));
1696
- if (!existsSync(stubPath)) {
1718
+ if (existsSync$1(outputDir) && this.option("force")) copyFileSync(outputDir, outputDir.replace(/\.js$/, `.backup.${Date.now()}.js`));
1719
+ if (!existsSync$1(stubPath)) {
1697
1720
  this.error(`Error: Missing config stub at ${preferredStubPath} (or ${legacyStubPath})`);
1698
1721
  process.exit(1);
1699
1722
  }
1700
- writeFileSync(outputDir, readFileSync(stubPath, "utf-8"));
1723
+ writeFileSync$1(outputDir, readFileSync$1(stubPath, "utf-8"));
1701
1724
  this.success("Arkormˣ initialized successfully!");
1702
1725
  }
1703
1726
  };
@@ -1823,6 +1846,98 @@ var MakeSeederCommand = class extends Command {
1823
1846
  }
1824
1847
  };
1825
1848
 
1849
+ //#endregion
1850
+ //#region src/helpers/migration-history.ts
1851
+ const DEFAULT_STATE = {
1852
+ version: 1,
1853
+ migrations: [],
1854
+ runs: []
1855
+ };
1856
+ const resolveMigrationStateFilePath = (cwd, configuredPath) => {
1857
+ if (configuredPath && configuredPath.trim().length > 0) return resolve(configuredPath);
1858
+ return join(cwd, ".arkormx", "migrations.applied.json");
1859
+ };
1860
+ const buildMigrationIdentity = (filePath, className) => {
1861
+ const fileName = filePath.split("/").pop()?.split("\\").pop() ?? filePath;
1862
+ return `${fileName.slice(0, fileName.length - extname(fileName).length)}:${className}`;
1863
+ };
1864
+ const computeMigrationChecksum = (filePath) => {
1865
+ const source = readFileSync(filePath, "utf-8");
1866
+ return createHash("sha256").update(source).digest("hex");
1867
+ };
1868
+ const readAppliedMigrationsState = (stateFilePath) => {
1869
+ if (!existsSync(stateFilePath)) return { ...DEFAULT_STATE };
1870
+ try {
1871
+ const parsed = JSON.parse(readFileSync(stateFilePath, "utf-8"));
1872
+ if (!Array.isArray(parsed.migrations)) return { ...DEFAULT_STATE };
1873
+ return {
1874
+ version: 1,
1875
+ migrations: parsed.migrations.filter((migration) => {
1876
+ return typeof migration?.id === "string" && typeof migration?.file === "string" && typeof migration?.className === "string" && typeof migration?.appliedAt === "string" && (migration?.checksum === void 0 || typeof migration?.checksum === "string");
1877
+ }),
1878
+ runs: Array.isArray(parsed.runs) ? parsed.runs.filter((run) => {
1879
+ return typeof run?.id === "string" && typeof run?.appliedAt === "string" && Array.isArray(run?.migrationIds) && run.migrationIds.every((item) => typeof item === "string");
1880
+ }) : []
1881
+ };
1882
+ } catch {
1883
+ return { ...DEFAULT_STATE };
1884
+ }
1885
+ };
1886
+ const writeAppliedMigrationsState = (stateFilePath, state) => {
1887
+ const directory = dirname(stateFilePath);
1888
+ if (!existsSync(directory)) mkdirSync(directory, { recursive: true });
1889
+ writeFileSync(stateFilePath, JSON.stringify(state, null, 2));
1890
+ };
1891
+ const isMigrationApplied = (state, identity, checksum) => {
1892
+ const matched = state.migrations.find((migration) => migration.id === identity);
1893
+ if (!matched) return false;
1894
+ if (checksum && matched.checksum) return matched.checksum === checksum;
1895
+ if (checksum && !matched.checksum) return false;
1896
+ return true;
1897
+ };
1898
+ const findAppliedMigration = (state, identity) => {
1899
+ return state.migrations.find((migration) => migration.id === identity);
1900
+ };
1901
+ const markMigrationApplied = (state, entry) => {
1902
+ const next = state.migrations.filter((migration) => migration.id !== entry.id);
1903
+ next.push(entry);
1904
+ return {
1905
+ version: 1,
1906
+ migrations: next,
1907
+ runs: state.runs ?? []
1908
+ };
1909
+ };
1910
+ const removeAppliedMigration = (state, identity) => {
1911
+ return {
1912
+ version: 1,
1913
+ migrations: state.migrations.filter((migration) => migration.id !== identity),
1914
+ runs: (state.runs ?? []).map((run) => ({
1915
+ ...run,
1916
+ migrationIds: run.migrationIds.filter((id) => id !== identity)
1917
+ })).filter((run) => run.migrationIds.length > 0)
1918
+ };
1919
+ };
1920
+ const buildMigrationRunId = () => {
1921
+ return `run_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
1922
+ };
1923
+ const markMigrationRun = (state, run) => {
1924
+ const nextRuns = (state.runs ?? []).filter((existing) => existing.id !== run.id);
1925
+ nextRuns.push(run);
1926
+ return {
1927
+ version: 1,
1928
+ migrations: state.migrations,
1929
+ runs: nextRuns
1930
+ };
1931
+ };
1932
+ const getLastMigrationRun = (state) => {
1933
+ const runs = state.runs ?? [];
1934
+ if (runs.length === 0) return void 0;
1935
+ return [...runs].sort((left, right) => right.appliedAt.localeCompare(left.appliedAt))[0];
1936
+ };
1937
+ const getLatestAppliedMigrations = (state, steps) => {
1938
+ return [...state.migrations].sort((left, right) => right.appliedAt.localeCompare(left.appliedAt)).slice(0, Math.max(0, steps));
1939
+ };
1940
+
1826
1941
  //#endregion
1827
1942
  //#region src/database/Migration.ts
1828
1943
  const MIGRATION_BRAND = Symbol.for("arkormx.migration");
@@ -1854,6 +1969,7 @@ var MigrateCommand = class extends Command {
1854
1969
  {--deploy : Use prisma migrate deploy instead of migrate dev}
1855
1970
  {--skip-generate : Skip prisma generate}
1856
1971
  {--skip-migrate : Skip prisma migrate command}
1972
+ {--state-file= : Path to applied migration state file}
1857
1973
  {--schema= : Explicit prisma schema path}
1858
1974
  {--migration-name= : Name for prisma migrate dev}
1859
1975
  `;
@@ -1869,16 +1985,59 @@ var MigrateCommand = class extends Command {
1869
1985
  */
1870
1986
  async handle() {
1871
1987
  this.app.command = this;
1872
- const configuredMigrationsDir = this.app.getConfig("paths")?.migrations ?? join$1(process.cwd(), "database", "migrations");
1988
+ const configuredMigrationsDir = this.app.getConfig("paths")?.migrations ?? join(process.cwd(), "database", "migrations");
1873
1989
  const migrationsDir = this.app.resolveRuntimeDirectoryPath(configuredMigrationsDir);
1874
- if (!existsSync$1(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
1875
- const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join$1(process.cwd(), "prisma", "schema.prisma");
1990
+ if (!existsSync(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
1991
+ const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join(process.cwd(), "prisma", "schema.prisma");
1876
1992
  const classes = this.option("all") || !this.argument("name") ? await this.loadAllMigrations(migrationsDir) : (await this.loadNamedMigration(migrationsDir, this.argument("name"))).filter(([cls]) => cls !== void 0);
1877
1993
  if (classes.length === 0) return void this.error("Error: No migration classes found to run.");
1878
- for (const [MigrationClassItem] of classes) await applyMigrationToPrismaSchema(MigrationClassItem, {
1994
+ const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
1995
+ let appliedState = readAppliedMigrationsState(stateFilePath);
1996
+ const skipped = [];
1997
+ const changed = [];
1998
+ const pending = classes.filter(([migrationClass, file]) => {
1999
+ if (!appliedState) return true;
2000
+ const identity = buildMigrationIdentity(file, migrationClass.name);
2001
+ const checksum = computeMigrationChecksum(file);
2002
+ const alreadyApplied = isMigrationApplied(appliedState, identity, checksum);
2003
+ if (alreadyApplied) skipped.push([migrationClass, file]);
2004
+ else if (findAppliedMigration(appliedState, identity)) changed.push([migrationClass, file]);
2005
+ return !alreadyApplied;
2006
+ });
2007
+ skipped.forEach(([migrationClass, file]) => {
2008
+ this.success(this.app.splitLogger("Skipped", `${file} (${migrationClass.name})`));
2009
+ });
2010
+ changed.forEach(([migrationClass, file]) => {
2011
+ this.success(this.app.splitLogger("Changed", `${file} (${migrationClass.name})`));
2012
+ });
2013
+ if (pending.length === 0) {
2014
+ this.success("No pending migration classes to apply.");
2015
+ return;
2016
+ }
2017
+ for (const [MigrationClassItem] of pending) await applyMigrationToPrismaSchema(MigrationClassItem, {
1879
2018
  schemaPath,
1880
2019
  write: true
1881
2020
  });
2021
+ if (appliedState) {
2022
+ const runAppliedIds = [];
2023
+ for (const [migrationClass, file] of pending) {
2024
+ const identity = buildMigrationIdentity(file, migrationClass.name);
2025
+ appliedState = markMigrationApplied(appliedState, {
2026
+ id: identity,
2027
+ file,
2028
+ className: migrationClass.name,
2029
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
2030
+ checksum: computeMigrationChecksum(file)
2031
+ });
2032
+ runAppliedIds.push(identity);
2033
+ }
2034
+ appliedState = markMigrationRun(appliedState, {
2035
+ id: buildMigrationRunId(),
2036
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
2037
+ migrationIds: runAppliedIds
2038
+ });
2039
+ writeAppliedMigrationsState(stateFilePath, appliedState);
2040
+ }
1882
2041
  if (!this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
1883
2042
  if (!this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
1884
2043
  else runPrismaCommand([
@@ -1887,8 +2046,8 @@ var MigrateCommand = class extends Command {
1887
2046
  "--name",
1888
2047
  this.option("migration-name") ? String(this.option("migration-name")) : `arkorm_cli_${Date.now()}`
1889
2048
  ], process.cwd());
1890
- this.success(`Applied ${classes.length} migration(s).`);
1891
- classes.forEach(([_, file]) => this.success(this.app.splitLogger("Migrated", file)));
2049
+ this.success(`Applied ${pending.length} migration(s).`);
2050
+ pending.forEach(([_, file]) => this.success(this.app.splitLogger("Migrated", file)));
1892
2051
  }
1893
2052
  /**
1894
2053
  * Load all migration classes from the specified directory.
@@ -1896,7 +2055,7 @@ var MigrateCommand = class extends Command {
1896
2055
  * @param migrationsDir The directory to load migration classes from.
1897
2056
  */
1898
2057
  async loadAllMigrations(migrationsDir) {
1899
- const files = readdirSync$1(migrationsDir).filter((file) => /\.(ts|js|mjs|cjs)$/i.test(file)).sort((left, right) => left.localeCompare(right)).map((file) => this.app.resolveRuntimeScriptPath(join$1(migrationsDir, file)));
2058
+ const files = readdirSync(migrationsDir).filter((file) => /\.(ts|js|mjs|cjs)$/i.test(file)).sort((left, right) => left.localeCompare(right)).map((file) => this.app.resolveRuntimeScriptPath(join(migrationsDir, file)));
1900
2059
  return (await Promise.all(files.map(async (file) => (await this.loadMigrationClassesFromFile(file)).map((cls) => [cls, file])))).flat();
1901
2060
  }
1902
2061
  /**
@@ -1918,7 +2077,7 @@ var MigrateCommand = class extends Command {
1918
2077
  `${base}Migration.js`,
1919
2078
  `${base}Migration.mjs`,
1920
2079
  `${base}Migration.cjs`
1921
- ].map((file) => join$1(migrationsDir, file)).find((file) => existsSync$1(file));
2080
+ ].map((file) => join(migrationsDir, file)).find((file) => existsSync(file));
1922
2081
  if (!target) return [[void 0, name]];
1923
2082
  const runtimeTarget = this.app.resolveRuntimeScriptPath(target);
1924
2083
  return (await this.loadMigrationClassesFromFile(runtimeTarget)).map((cls) => [cls, runtimeTarget]);
@@ -1940,6 +2099,147 @@ var MigrateCommand = class extends Command {
1940
2099
  }
1941
2100
  };
1942
2101
 
2102
+ //#endregion
2103
+ //#region src/cli/commands/MigrateRollbackCommand.ts
2104
+ /**
2105
+ * Rollback migration classes from the Prisma schema and run Prisma workflow.
2106
+ * By default, rolls back classes applied in the last migrate run.
2107
+ *
2108
+ * @author Legacy (3m1n3nc3)
2109
+ * @since 0.2.4
2110
+ */
2111
+ var MigrateRollbackCommand = class extends Command {
2112
+ signature = `migrate:rollback
2113
+ {--step= : Number of latest applied migration classes to rollback}
2114
+ {--dry-run : Preview rollback targets without applying changes}
2115
+ {--deploy : Use prisma migrate deploy instead of migrate dev}
2116
+ {--skip-generate : Skip prisma generate}
2117
+ {--skip-migrate : Skip prisma migrate command}
2118
+ {--state-file= : Path to applied migration state file}
2119
+ {--schema= : Explicit prisma schema path}
2120
+ {--migration-name= : Name for prisma migrate dev}
2121
+ `;
2122
+ description = "Rollback migration classes from schema.prisma and run Prisma workflow";
2123
+ async handle() {
2124
+ this.app.command = this;
2125
+ const configuredMigrationsDir = this.app.getConfig("paths")?.migrations ?? join(process.cwd(), "database", "migrations");
2126
+ const migrationsDir = this.app.resolveRuntimeDirectoryPath(configuredMigrationsDir);
2127
+ if (!existsSync(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
2128
+ const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join(process.cwd(), "prisma", "schema.prisma");
2129
+ const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
2130
+ let appliedState = readAppliedMigrationsState(stateFilePath);
2131
+ const stepOption = this.option("step");
2132
+ const stepCount = stepOption == null ? void 0 : Number(stepOption);
2133
+ if (stepCount != null && (!Number.isFinite(stepCount) || stepCount <= 0 || !Number.isInteger(stepCount))) return void this.error("Error: --step must be a positive integer.");
2134
+ const targets = stepCount ? getLatestAppliedMigrations(appliedState, stepCount) : (() => {
2135
+ const lastRun = getLastMigrationRun(appliedState);
2136
+ if (!lastRun) return [];
2137
+ return lastRun.migrationIds.map((id) => appliedState.migrations.find((migration) => migration.id === id)).filter((migration) => Boolean(migration));
2138
+ })();
2139
+ if (targets.length === 0) return void this.error("Error: No tracked migrations available to rollback.");
2140
+ const available = await this.loadAllMigrations(migrationsDir);
2141
+ const rollbackClasses = targets.map((target) => {
2142
+ return available.find(([migrationClass, file]) => {
2143
+ return buildMigrationIdentity(file, migrationClass.name) === target.id || migrationClass.name === target.className;
2144
+ });
2145
+ }).filter((entry) => Boolean(entry));
2146
+ if (rollbackClasses.length === 0) return void this.error("Error: Unable to resolve rollback migration classes from tracked history.");
2147
+ if (this.option("dry-run")) {
2148
+ this.success(`Dry run: ${rollbackClasses.length} migration(s) would be rolled back.`);
2149
+ rollbackClasses.forEach(([_, file]) => this.success(this.app.splitLogger("WouldRollback", file)));
2150
+ return;
2151
+ }
2152
+ for (const [MigrationClassItem] of rollbackClasses) await applyMigrationRollbackToPrismaSchema(MigrationClassItem, {
2153
+ schemaPath,
2154
+ write: true
2155
+ });
2156
+ for (const [migrationClass, file] of rollbackClasses) {
2157
+ const identity = buildMigrationIdentity(file, migrationClass.name);
2158
+ appliedState = removeAppliedMigration(appliedState, identity);
2159
+ }
2160
+ writeAppliedMigrationsState(stateFilePath, appliedState);
2161
+ if (!this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
2162
+ if (!this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
2163
+ else runPrismaCommand([
2164
+ "migrate",
2165
+ "dev",
2166
+ "--name",
2167
+ this.option("migration-name") ? String(this.option("migration-name")) : `arkorm_cli_rollback_${Date.now()}`
2168
+ ], process.cwd());
2169
+ this.success(`Rolled back ${rollbackClasses.length} migration(s).`);
2170
+ rollbackClasses.forEach(([_, file]) => this.success(this.app.splitLogger("RolledBack", file)));
2171
+ }
2172
+ async loadAllMigrations(migrationsDir) {
2173
+ const files = readdirSync(migrationsDir).filter((file) => /\.(ts|js|mjs|cjs)$/i.test(file)).sort((left, right) => left.localeCompare(right)).map((file) => this.app.resolveRuntimeScriptPath(join(migrationsDir, file)));
2174
+ return (await Promise.all(files.map(async (file) => (await this.loadMigrationClassesFromFile(file)).map((cls) => [cls, file])))).flat();
2175
+ }
2176
+ async loadMigrationClassesFromFile(filePath) {
2177
+ const imported = await import(`${pathToFileURL$1(resolve(filePath)).href}?arkorm_rollback=${Date.now()}`);
2178
+ return Object.values(imported).filter((value) => {
2179
+ if (typeof value !== "function") return false;
2180
+ const candidate = value;
2181
+ const prototype = candidate.prototype;
2182
+ return candidate[MIGRATION_BRAND] === true || typeof prototype?.up === "function" && typeof prototype?.down === "function";
2183
+ });
2184
+ }
2185
+ };
2186
+
2187
+ //#endregion
2188
+ //#region src/cli/commands/MigrationHistoryCommand.ts
2189
+ /**
2190
+ * The MigrationHistoryCommand class manages tracked migration run history.
2191
+ *
2192
+ * @author Legacy (3m1n3nc3)
2193
+ * @since 0.2.4
2194
+ */
2195
+ var MigrationHistoryCommand = class extends Command {
2196
+ signature = `migrate:history
2197
+ {--state-file= : Path to applied migration state file}
2198
+ {--reset : Clear tracked migration history file}
2199
+ {--delete : Delete tracked migration history file}
2200
+ {--json : Print raw JSON output}
2201
+ `;
2202
+ description = "Inspect or reset tracked migration history";
2203
+ async handle() {
2204
+ this.app.command = this;
2205
+ const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
2206
+ if (this.option("delete")) {
2207
+ if (!existsSync(stateFilePath)) {
2208
+ this.success(`No migration state file found at ${this.app.formatPathForLog(stateFilePath)}`);
2209
+ return;
2210
+ }
2211
+ rmSync(stateFilePath);
2212
+ this.success(`Deleted migration state file: ${this.app.formatPathForLog(stateFilePath)}`);
2213
+ return;
2214
+ }
2215
+ if (this.option("reset")) {
2216
+ writeAppliedMigrationsState(stateFilePath, {
2217
+ version: 1,
2218
+ migrations: []
2219
+ });
2220
+ this.success(`Reset migration state: ${this.app.formatPathForLog(stateFilePath)}`);
2221
+ return;
2222
+ }
2223
+ const state = readAppliedMigrationsState(stateFilePath);
2224
+ if (this.option("json")) {
2225
+ this.success(JSON.stringify({
2226
+ path: stateFilePath,
2227
+ ...state
2228
+ }, null, 2));
2229
+ return;
2230
+ }
2231
+ this.success(this.app.splitLogger("State", stateFilePath));
2232
+ this.success(this.app.splitLogger("Tracked", String(state.migrations.length)));
2233
+ if (state.migrations.length === 0) {
2234
+ this.success("No tracked migrations found.");
2235
+ return;
2236
+ }
2237
+ state.migrations.sort((left, right) => left.appliedAt.localeCompare(right.appliedAt)).forEach((migration) => {
2238
+ this.success(this.app.splitLogger("Applied", `${migration.id} @ ${migration.appliedAt}`));
2239
+ });
2240
+ }
2241
+ };
2242
+
1943
2243
  //#endregion
1944
2244
  //#region src/cli/commands/ModelsSyncCommand.ts
1945
2245
  var ModelsSyncCommand = class extends Command {
@@ -2035,9 +2335,9 @@ var SeedCommand = class extends Command {
2035
2335
  */
2036
2336
  async handle() {
2037
2337
  this.app.command = this;
2038
- const configuredSeedersDir = this.app.getConfig("paths")?.seeders ?? join$1(process.cwd(), "database", "seeders");
2338
+ const configuredSeedersDir = this.app.getConfig("paths")?.seeders ?? join(process.cwd(), "database", "seeders");
2039
2339
  const seedersDir = this.app.resolveRuntimeDirectoryPath(configuredSeedersDir);
2040
- if (!existsSync$1(seedersDir)) return void this.error(`ERROR: Seeders directory not found: ${this.app.formatPathForLog(configuredSeedersDir)}`);
2340
+ if (!existsSync(seedersDir)) return void this.error(`ERROR: Seeders directory not found: ${this.app.formatPathForLog(configuredSeedersDir)}`);
2041
2341
  const classes = this.option("all") ? await this.loadAllSeeders(seedersDir) : await this.loadNamedSeeder(seedersDir, this.argument("name") ?? "DatabaseSeeder");
2042
2342
  if (classes.length === 0) return void this.error("ERROR: No seeder classes found to run.");
2043
2343
  for (const SeederClassItem of classes) await new SeederClassItem().run();
@@ -2051,7 +2351,7 @@ var SeedCommand = class extends Command {
2051
2351
  * @returns
2052
2352
  */
2053
2353
  async loadAllSeeders(seedersDir) {
2054
- const files = readdirSync$1(seedersDir).filter((file) => /\.(ts|js|mjs|cjs)$/i.test(file)).map((file) => this.app.resolveRuntimeScriptPath(join$1(seedersDir, file)));
2354
+ const files = readdirSync(seedersDir).filter((file) => /\.(ts|js|mjs|cjs)$/i.test(file)).map((file) => this.app.resolveRuntimeScriptPath(join(seedersDir, file)));
2055
2355
  return (await Promise.all(files.map(async (file) => await this.loadSeederClassesFromFile(file)))).flat();
2056
2356
  }
2057
2357
  /**
@@ -2072,7 +2372,7 @@ var SeedCommand = class extends Command {
2072
2372
  `${base}Seeder.js`,
2073
2373
  `${base}Seeder.mjs`,
2074
2374
  `${base}Seeder.cjs`
2075
- ].map((file) => join$1(seedersDir, file)).find((file) => existsSync$1(file));
2375
+ ].map((file) => join(seedersDir, file)).find((file) => existsSync(file));
2076
2376
  if (!target) return [];
2077
2377
  const runtimeTarget = this.app.resolveRuntimeScriptPath(target);
2078
2378
  return await this.loadSeederClassesFromFile(runtimeTarget);
@@ -2119,7 +2419,9 @@ await Kernel.init(app, {
2119
2419
  MakeMigrationCommand,
2120
2420
  ModelsSyncCommand,
2121
2421
  SeedCommand,
2122
- MigrateCommand
2422
+ MigrateCommand,
2423
+ MigrateRollbackCommand,
2424
+ MigrationHistoryCommand
2123
2425
  ],
2124
2426
  exceptionHandler(exception) {
2125
2427
  throw exception;