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 +380 -78
- package/dist/index.cjs +328 -12
- package/dist/index.d.cts +74 -1
- package/dist/index.d.mts +74 -1
- package/dist/index.mjs +379 -79
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
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
|
|
1011
|
+
const directory = options.directory ?? join(process.cwd(), "database", "migrations");
|
|
1011
1012
|
const fileName = `${timestamp}_${fileSlug}.${extension}`;
|
|
1012
|
-
const filePath = join
|
|
1013
|
+
const filePath = join(directory, fileName);
|
|
1013
1014
|
const content = buildMigrationSource(className, extension);
|
|
1014
1015
|
if (options.write ?? true) {
|
|
1015
|
-
if (!existsSync
|
|
1016
|
-
if (existsSync
|
|
1017
|
-
writeFileSync
|
|
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
|
|
1051
|
-
if (!existsSync
|
|
1052
|
-
const source = readFileSync
|
|
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
|
|
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]+)(\?)
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
1688
|
-
const legacyStubPath = join
|
|
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
|
|
1988
|
+
const configuredMigrationsDir = this.app.getConfig("paths")?.migrations ?? join(process.cwd(), "database", "migrations");
|
|
1873
1989
|
const migrationsDir = this.app.resolveRuntimeDirectoryPath(configuredMigrationsDir);
|
|
1874
|
-
if (!existsSync
|
|
1875
|
-
const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join
|
|
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
|
-
|
|
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 ${
|
|
1891
|
-
|
|
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
|
|
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
|
|
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
|
|
2338
|
+
const configuredSeedersDir = this.app.getConfig("paths")?.seeders ?? join(process.cwd(), "database", "seeders");
|
|
2039
2339
|
const seedersDir = this.app.resolveRuntimeDirectoryPath(configuredSeedersDir);
|
|
2040
|
-
if (!existsSync
|
|
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
|
|
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
|
|
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;
|