arkormx 2.0.0-next.2 → 2.0.0-next.4
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 +615 -136
- package/dist/index.cjs +2810 -2058
- package/dist/index.d.cts +210 -97
- package/dist/index.d.mts +210 -97
- package/dist/index.mjs +2826 -2100
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync
|
|
|
3
3
|
import { dirname, extname, join, resolve } from "node:path";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
import { str } from "@h3ravel/support";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
6
7
|
import path, { dirname as dirname$1, extname as extname$1, join as join$1, relative } from "path";
|
|
7
8
|
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";
|
|
8
9
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -12,7 +13,6 @@ import { createRequire } from "module";
|
|
|
12
13
|
import { fileURLToPath } from "url";
|
|
13
14
|
import { Logger } from "@h3ravel/shared";
|
|
14
15
|
import { Command, Kernel } from "@h3ravel/musket";
|
|
15
|
-
import { createHash } from "node:crypto";
|
|
16
16
|
|
|
17
17
|
//#region src/Exceptions/ArkormException.ts
|
|
18
18
|
var ArkormException = class extends Error {
|
|
@@ -1371,6 +1371,28 @@ const getMigrationPlan = async (migration, direction = "up") => {
|
|
|
1371
1371
|
else await instance.down(schema);
|
|
1372
1372
|
return schema.getOperations();
|
|
1373
1373
|
};
|
|
1374
|
+
const supportsDatabaseMigrationExecution = (adapter) => {
|
|
1375
|
+
return typeof adapter?.executeSchemaOperations === "function";
|
|
1376
|
+
};
|
|
1377
|
+
const supportsDatabaseReset = (adapter) => {
|
|
1378
|
+
return typeof adapter?.resetDatabase === "function";
|
|
1379
|
+
};
|
|
1380
|
+
const stripPrismaSchemaModelsAndEnums = (schema) => {
|
|
1381
|
+
const stripped = schema.replace(PRISMA_MODEL_REGEX, "").replace(PRISMA_ENUM_REGEX, "").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
1382
|
+
return stripped.length > 0 ? `${stripped}\n` : "";
|
|
1383
|
+
};
|
|
1384
|
+
const applyMigrationToDatabase = async (adapter, migration) => {
|
|
1385
|
+
if (!supportsDatabaseMigrationExecution(adapter)) throw new ArkormException("The configured adapter does not support database-backed migration execution.");
|
|
1386
|
+
const operations = await getMigrationPlan(migration, "up");
|
|
1387
|
+
await adapter.executeSchemaOperations(operations);
|
|
1388
|
+
return { operations };
|
|
1389
|
+
};
|
|
1390
|
+
const applyMigrationRollbackToDatabase = async (adapter, migration) => {
|
|
1391
|
+
if (!supportsDatabaseMigrationExecution(adapter)) throw new ArkormException("The configured adapter does not support database-backed migration execution.");
|
|
1392
|
+
const operations = await getMigrationPlan(migration, "down");
|
|
1393
|
+
await adapter.executeSchemaOperations(operations);
|
|
1394
|
+
return { operations };
|
|
1395
|
+
};
|
|
1374
1396
|
/**
|
|
1375
1397
|
* Apply the schema operations defined in a migration to a Prisma schema
|
|
1376
1398
|
* file, updating the file on disk if specified, and return the updated
|
|
@@ -1414,6 +1436,377 @@ const applyMigrationRollbackToPrismaSchema = async (migration, options = {}) =>
|
|
|
1414
1436
|
};
|
|
1415
1437
|
};
|
|
1416
1438
|
|
|
1439
|
+
//#endregion
|
|
1440
|
+
//#region src/helpers/migration-history.ts
|
|
1441
|
+
const createEmptyAppliedMigrationsState = () => ({
|
|
1442
|
+
version: 1,
|
|
1443
|
+
migrations: [],
|
|
1444
|
+
runs: []
|
|
1445
|
+
});
|
|
1446
|
+
const supportsDatabaseMigrationState = (adapter) => {
|
|
1447
|
+
return typeof adapter?.readAppliedMigrationsState === "function" && typeof adapter?.writeAppliedMigrationsState === "function";
|
|
1448
|
+
};
|
|
1449
|
+
const resolveMigrationStateFilePath = (cwd, configuredPath) => {
|
|
1450
|
+
if (configuredPath && configuredPath.trim().length > 0) return resolve(configuredPath);
|
|
1451
|
+
return join(cwd, ".arkormx", "migrations.applied.json");
|
|
1452
|
+
};
|
|
1453
|
+
const buildMigrationIdentity = (filePath, className) => {
|
|
1454
|
+
const fileName = filePath.split("/").pop()?.split("\\").pop() ?? filePath;
|
|
1455
|
+
return `${fileName.slice(0, fileName.length - extname(fileName).length)}:${className}`;
|
|
1456
|
+
};
|
|
1457
|
+
const computeMigrationChecksum = (filePath) => {
|
|
1458
|
+
const source = readFileSync(filePath, "utf-8");
|
|
1459
|
+
return createHash("sha256").update(source).digest("hex");
|
|
1460
|
+
};
|
|
1461
|
+
const readAppliedMigrationsState = (stateFilePath) => {
|
|
1462
|
+
if (!existsSync(stateFilePath)) return createEmptyAppliedMigrationsState();
|
|
1463
|
+
try {
|
|
1464
|
+
const parsed = JSON.parse(readFileSync(stateFilePath, "utf-8"));
|
|
1465
|
+
if (!Array.isArray(parsed.migrations)) return createEmptyAppliedMigrationsState();
|
|
1466
|
+
return {
|
|
1467
|
+
version: 1,
|
|
1468
|
+
migrations: parsed.migrations.filter((migration) => {
|
|
1469
|
+
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");
|
|
1470
|
+
}),
|
|
1471
|
+
runs: Array.isArray(parsed.runs) ? parsed.runs.filter((run) => {
|
|
1472
|
+
return typeof run?.id === "string" && typeof run?.appliedAt === "string" && Array.isArray(run?.migrationIds) && run.migrationIds.every((item) => typeof item === "string");
|
|
1473
|
+
}) : []
|
|
1474
|
+
};
|
|
1475
|
+
} catch {
|
|
1476
|
+
return createEmptyAppliedMigrationsState();
|
|
1477
|
+
}
|
|
1478
|
+
};
|
|
1479
|
+
const readAppliedMigrationsStateFromStore = async (adapter, stateFilePath) => {
|
|
1480
|
+
if (supportsDatabaseMigrationState(adapter)) return await adapter.readAppliedMigrationsState();
|
|
1481
|
+
return readAppliedMigrationsState(stateFilePath);
|
|
1482
|
+
};
|
|
1483
|
+
const writeAppliedMigrationsState = (stateFilePath, state) => {
|
|
1484
|
+
const directory = dirname(stateFilePath);
|
|
1485
|
+
if (!existsSync(directory)) mkdirSync(directory, { recursive: true });
|
|
1486
|
+
writeFileSync(stateFilePath, JSON.stringify(state, null, 2));
|
|
1487
|
+
};
|
|
1488
|
+
const writeAppliedMigrationsStateToStore = async (adapter, stateFilePath, state) => {
|
|
1489
|
+
if (supportsDatabaseMigrationState(adapter)) {
|
|
1490
|
+
await adapter.writeAppliedMigrationsState(state);
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
writeAppliedMigrationsState(stateFilePath, state);
|
|
1494
|
+
};
|
|
1495
|
+
const isMigrationApplied = (state, identity, checksum) => {
|
|
1496
|
+
const matched = state.migrations.find((migration) => migration.id === identity);
|
|
1497
|
+
if (!matched) return false;
|
|
1498
|
+
if (checksum && matched.checksum) return matched.checksum === checksum;
|
|
1499
|
+
if (checksum && !matched.checksum) return false;
|
|
1500
|
+
return true;
|
|
1501
|
+
};
|
|
1502
|
+
const findAppliedMigration = (state, identity) => {
|
|
1503
|
+
return state.migrations.find((migration) => migration.id === identity);
|
|
1504
|
+
};
|
|
1505
|
+
const markMigrationApplied = (state, entry) => {
|
|
1506
|
+
const next = state.migrations.filter((migration) => migration.id !== entry.id);
|
|
1507
|
+
next.push(entry);
|
|
1508
|
+
return {
|
|
1509
|
+
version: 1,
|
|
1510
|
+
migrations: next,
|
|
1511
|
+
runs: state.runs ?? []
|
|
1512
|
+
};
|
|
1513
|
+
};
|
|
1514
|
+
const removeAppliedMigration = (state, identity) => {
|
|
1515
|
+
return {
|
|
1516
|
+
version: 1,
|
|
1517
|
+
migrations: state.migrations.filter((migration) => migration.id !== identity),
|
|
1518
|
+
runs: (state.runs ?? []).map((run) => ({
|
|
1519
|
+
...run,
|
|
1520
|
+
migrationIds: run.migrationIds.filter((id) => id !== identity)
|
|
1521
|
+
})).filter((run) => run.migrationIds.length > 0)
|
|
1522
|
+
};
|
|
1523
|
+
};
|
|
1524
|
+
const buildMigrationRunId = () => {
|
|
1525
|
+
return `run_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
1526
|
+
};
|
|
1527
|
+
const markMigrationRun = (state, run) => {
|
|
1528
|
+
const nextRuns = (state.runs ?? []).filter((existing) => existing.id !== run.id);
|
|
1529
|
+
nextRuns.push(run);
|
|
1530
|
+
return {
|
|
1531
|
+
version: 1,
|
|
1532
|
+
migrations: state.migrations,
|
|
1533
|
+
runs: nextRuns
|
|
1534
|
+
};
|
|
1535
|
+
};
|
|
1536
|
+
const getLastMigrationRun = (state) => {
|
|
1537
|
+
const runs = state.runs ?? [];
|
|
1538
|
+
if (runs.length === 0) return void 0;
|
|
1539
|
+
return runs.map((run, index) => ({
|
|
1540
|
+
run,
|
|
1541
|
+
index
|
|
1542
|
+
})).sort((left, right) => {
|
|
1543
|
+
const appliedAtOrder = right.run.appliedAt.localeCompare(left.run.appliedAt);
|
|
1544
|
+
if (appliedAtOrder !== 0) return appliedAtOrder;
|
|
1545
|
+
return right.index - left.index;
|
|
1546
|
+
})[0]?.run;
|
|
1547
|
+
};
|
|
1548
|
+
const getLatestAppliedMigrations = (state, steps) => {
|
|
1549
|
+
return state.migrations.map((migration, index) => ({
|
|
1550
|
+
migration,
|
|
1551
|
+
index
|
|
1552
|
+
})).sort((left, right) => {
|
|
1553
|
+
const appliedAtOrder = right.migration.appliedAt.localeCompare(left.migration.appliedAt);
|
|
1554
|
+
if (appliedAtOrder !== 0) return appliedAtOrder;
|
|
1555
|
+
return right.index - left.index;
|
|
1556
|
+
}).slice(0, Math.max(0, steps)).map((entry) => entry.migration);
|
|
1557
|
+
};
|
|
1558
|
+
|
|
1559
|
+
//#endregion
|
|
1560
|
+
//#region src/helpers/column-mappings.ts
|
|
1561
|
+
let cachedColumnMappingsPath;
|
|
1562
|
+
let cachedColumnMappingsState;
|
|
1563
|
+
const resolvePersistedMetadataFeatures = (features) => {
|
|
1564
|
+
return {
|
|
1565
|
+
persistedColumnMappings: features?.persistedColumnMappings !== false,
|
|
1566
|
+
persistedEnums: features?.persistedEnums !== false
|
|
1567
|
+
};
|
|
1568
|
+
};
|
|
1569
|
+
const createEmptyPersistedColumnMappingsState = () => ({
|
|
1570
|
+
version: 1,
|
|
1571
|
+
tables: {}
|
|
1572
|
+
});
|
|
1573
|
+
const resolveColumnMappingsFilePath = (cwd, configuredPath) => {
|
|
1574
|
+
if (configuredPath && configuredPath.trim().length > 0) return resolve(configuredPath);
|
|
1575
|
+
return join(cwd, ".arkormx", "column-mappings.json");
|
|
1576
|
+
};
|
|
1577
|
+
const normalizePersistedEnumValues = (values) => {
|
|
1578
|
+
if (!Array.isArray(values)) return [];
|
|
1579
|
+
return values.filter((value) => typeof value === "string" && value.trim().length > 0);
|
|
1580
|
+
};
|
|
1581
|
+
const normalizeLegacyTableColumns = (columns) => {
|
|
1582
|
+
return Object.entries(columns).reduce((mapped, [attribute, column]) => {
|
|
1583
|
+
if (attribute.trim().length === 0) return mapped;
|
|
1584
|
+
if (typeof column !== "string" || column.trim().length === 0) return mapped;
|
|
1585
|
+
mapped[attribute] = column;
|
|
1586
|
+
return mapped;
|
|
1587
|
+
}, {});
|
|
1588
|
+
};
|
|
1589
|
+
const normalizePersistedTableMetadata = (table) => {
|
|
1590
|
+
if (!table || typeof table !== "object" || Array.isArray(table)) return {
|
|
1591
|
+
columns: {},
|
|
1592
|
+
enums: {}
|
|
1593
|
+
};
|
|
1594
|
+
const candidate = table;
|
|
1595
|
+
if (!(Object.prototype.hasOwnProperty.call(candidate, "columns") || Object.prototype.hasOwnProperty.call(candidate, "enums"))) return {
|
|
1596
|
+
columns: normalizeLegacyTableColumns(candidate),
|
|
1597
|
+
enums: {}
|
|
1598
|
+
};
|
|
1599
|
+
return {
|
|
1600
|
+
columns: normalizeLegacyTableColumns(candidate.columns ?? {}),
|
|
1601
|
+
enums: Object.entries(candidate.enums ?? {}).reduce((all, [columnName, values]) => {
|
|
1602
|
+
if (columnName.trim().length === 0) return all;
|
|
1603
|
+
const normalizedValues = normalizePersistedEnumValues(values);
|
|
1604
|
+
if (normalizedValues.length > 0) all[columnName] = normalizedValues;
|
|
1605
|
+
return all;
|
|
1606
|
+
}, {})
|
|
1607
|
+
};
|
|
1608
|
+
};
|
|
1609
|
+
const normalizePersistedColumnMappingsState = (state) => {
|
|
1610
|
+
return {
|
|
1611
|
+
version: 1,
|
|
1612
|
+
tables: Object.entries(state?.tables ?? {}).reduce((all, [tableName, tableMetadata]) => {
|
|
1613
|
+
if (tableName.trim().length === 0) return all;
|
|
1614
|
+
const normalized = normalizePersistedTableMetadata(tableMetadata);
|
|
1615
|
+
if (Object.keys(normalized.columns).length > 0 || Object.keys(normalized.enums).length > 0) all[tableName] = normalized;
|
|
1616
|
+
return all;
|
|
1617
|
+
}, {})
|
|
1618
|
+
};
|
|
1619
|
+
};
|
|
1620
|
+
const buildPersistedFeatureDisabledError = (feature, table) => {
|
|
1621
|
+
return new ArkormException(`Table [${table}] requires ${feature === "persistedColumnMappings" ? "persisted column mappings" : "persisted enum metadata"}, but ${feature === "persistedColumnMappings" ? "features.persistedColumnMappings" : "features.persistedEnums"} is disabled in arkormx.config.*.`, {
|
|
1622
|
+
operation: "metadata.persisted",
|
|
1623
|
+
meta: {
|
|
1624
|
+
feature,
|
|
1625
|
+
table
|
|
1626
|
+
}
|
|
1627
|
+
});
|
|
1628
|
+
};
|
|
1629
|
+
const assertPersistedTableMetadataEnabled = (table, metadata, features, strict) => {
|
|
1630
|
+
if (!strict) return;
|
|
1631
|
+
if (!features.persistedColumnMappings && Object.keys(metadata.columns).length > 0) throw buildPersistedFeatureDisabledError("persistedColumnMappings", table);
|
|
1632
|
+
if (!features.persistedEnums && Object.keys(metadata.enums).length > 0) throw buildPersistedFeatureDisabledError("persistedEnums", table);
|
|
1633
|
+
};
|
|
1634
|
+
const buildEnumUnionType = (values) => {
|
|
1635
|
+
return values.map((value) => {
|
|
1636
|
+
return `'${value.replace(/'/g, String.raw`\'`)}'`;
|
|
1637
|
+
}).join(" | ");
|
|
1638
|
+
};
|
|
1639
|
+
const resetPersistedColumnMappingsCache = () => {
|
|
1640
|
+
cachedColumnMappingsPath = void 0;
|
|
1641
|
+
cachedColumnMappingsState = void 0;
|
|
1642
|
+
};
|
|
1643
|
+
const readPersistedColumnMappingsState = (filePath) => {
|
|
1644
|
+
if (cachedColumnMappingsPath === filePath && cachedColumnMappingsState) return cachedColumnMappingsState;
|
|
1645
|
+
if (!existsSync(filePath)) {
|
|
1646
|
+
const empty = createEmptyPersistedColumnMappingsState();
|
|
1647
|
+
cachedColumnMappingsPath = filePath;
|
|
1648
|
+
cachedColumnMappingsState = empty;
|
|
1649
|
+
return empty;
|
|
1650
|
+
}
|
|
1651
|
+
try {
|
|
1652
|
+
const normalized = normalizePersistedColumnMappingsState(JSON.parse(readFileSync(filePath, "utf-8")));
|
|
1653
|
+
cachedColumnMappingsPath = filePath;
|
|
1654
|
+
cachedColumnMappingsState = normalized;
|
|
1655
|
+
return normalized;
|
|
1656
|
+
} catch {
|
|
1657
|
+
const empty = createEmptyPersistedColumnMappingsState();
|
|
1658
|
+
cachedColumnMappingsPath = filePath;
|
|
1659
|
+
cachedColumnMappingsState = empty;
|
|
1660
|
+
return empty;
|
|
1661
|
+
}
|
|
1662
|
+
};
|
|
1663
|
+
const writePersistedColumnMappingsState = (filePath, state) => {
|
|
1664
|
+
const normalized = normalizePersistedColumnMappingsState(state);
|
|
1665
|
+
const directory = dirname(filePath);
|
|
1666
|
+
if (!existsSync(directory)) mkdirSync(directory, { recursive: true });
|
|
1667
|
+
writeFileSync(filePath, JSON.stringify(normalized, null, 2));
|
|
1668
|
+
cachedColumnMappingsPath = filePath;
|
|
1669
|
+
cachedColumnMappingsState = normalized;
|
|
1670
|
+
};
|
|
1671
|
+
const deletePersistedColumnMappingsState = (filePath) => {
|
|
1672
|
+
if (existsSync(filePath)) rmSync(filePath, { force: true });
|
|
1673
|
+
resetPersistedColumnMappingsCache();
|
|
1674
|
+
};
|
|
1675
|
+
const getPersistedTableMetadata = (table, options = {}) => {
|
|
1676
|
+
const metadata = readPersistedColumnMappingsState(resolveColumnMappingsFilePath(options.cwd ?? process.cwd(), options.configuredPath)).tables[table] ?? {
|
|
1677
|
+
columns: {},
|
|
1678
|
+
enums: {}
|
|
1679
|
+
};
|
|
1680
|
+
assertPersistedTableMetadataEnabled(table, metadata, options.features ?? resolvePersistedMetadataFeatures(), options.strict ?? false);
|
|
1681
|
+
return {
|
|
1682
|
+
columns: { ...metadata.columns },
|
|
1683
|
+
enums: Object.entries(metadata.enums).reduce((all, [columnName, values]) => {
|
|
1684
|
+
all[columnName] = [...values];
|
|
1685
|
+
return all;
|
|
1686
|
+
}, {})
|
|
1687
|
+
};
|
|
1688
|
+
};
|
|
1689
|
+
const getPersistedEnumMap = (table, options = {}) => {
|
|
1690
|
+
return getPersistedTableMetadata(table, options).enums;
|
|
1691
|
+
};
|
|
1692
|
+
const applyMappedColumn = (tableColumns, column, features, table) => {
|
|
1693
|
+
if (typeof column.map === "string" && column.map.trim().length > 0 && column.map !== column.name) {
|
|
1694
|
+
if (!features.persistedColumnMappings) throw buildPersistedFeatureDisabledError("persistedColumnMappings", table);
|
|
1695
|
+
tableColumns[column.name] = column.map;
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
delete tableColumns[column.name];
|
|
1699
|
+
};
|
|
1700
|
+
const applyEnumColumn = (tableEnums, column, features, table) => {
|
|
1701
|
+
const values = column.enumValues ?? [];
|
|
1702
|
+
if (column.type === "enum" && values.length > 0) {
|
|
1703
|
+
if (!features.persistedEnums) throw buildPersistedFeatureDisabledError("persistedEnums", table);
|
|
1704
|
+
tableEnums[column.name] = [...values];
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
delete tableEnums[column.name];
|
|
1708
|
+
};
|
|
1709
|
+
const removePersistedColumnMetadata = (tableMetadata, columnName) => {
|
|
1710
|
+
delete tableMetadata.columns[columnName];
|
|
1711
|
+
delete tableMetadata.enums[columnName];
|
|
1712
|
+
Object.entries(tableMetadata.columns).forEach(([attribute, mappedColumn]) => {
|
|
1713
|
+
if (mappedColumn === columnName) delete tableMetadata.columns[attribute];
|
|
1714
|
+
});
|
|
1715
|
+
};
|
|
1716
|
+
const applyOperationsToPersistedColumnMappingsState = (state, operations, features = resolvePersistedMetadataFeatures()) => {
|
|
1717
|
+
const nextTables = Object.entries(state.tables).reduce((all, [table, metadata]) => {
|
|
1718
|
+
all[table] = {
|
|
1719
|
+
columns: { ...metadata.columns },
|
|
1720
|
+
enums: Object.entries(metadata.enums).reduce((nextEnums, [columnName, values]) => {
|
|
1721
|
+
nextEnums[columnName] = [...values];
|
|
1722
|
+
return nextEnums;
|
|
1723
|
+
}, {})
|
|
1724
|
+
};
|
|
1725
|
+
return all;
|
|
1726
|
+
}, {});
|
|
1727
|
+
operations.forEach((operation) => {
|
|
1728
|
+
if (operation.type === "createTable") {
|
|
1729
|
+
const tableMetadata = nextTables[operation.table] ?? {
|
|
1730
|
+
columns: {},
|
|
1731
|
+
enums: {}
|
|
1732
|
+
};
|
|
1733
|
+
operation.columns.forEach((column) => {
|
|
1734
|
+
applyMappedColumn(tableMetadata.columns, column, features, operation.table);
|
|
1735
|
+
applyEnumColumn(tableMetadata.enums, column, features, operation.table);
|
|
1736
|
+
});
|
|
1737
|
+
if (Object.keys(tableMetadata.columns).length > 0 || Object.keys(tableMetadata.enums).length > 0) nextTables[operation.table] = tableMetadata;
|
|
1738
|
+
else delete nextTables[operation.table];
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
if (operation.type === "alterTable") {
|
|
1742
|
+
const tableMetadata = nextTables[operation.table] ?? {
|
|
1743
|
+
columns: {},
|
|
1744
|
+
enums: {}
|
|
1745
|
+
};
|
|
1746
|
+
operation.addColumns.forEach((column) => {
|
|
1747
|
+
applyMappedColumn(tableMetadata.columns, column, features, operation.table);
|
|
1748
|
+
applyEnumColumn(tableMetadata.enums, column, features, operation.table);
|
|
1749
|
+
});
|
|
1750
|
+
operation.dropColumns.forEach((columnName) => {
|
|
1751
|
+
removePersistedColumnMetadata(tableMetadata, columnName);
|
|
1752
|
+
});
|
|
1753
|
+
if (Object.keys(tableMetadata.columns).length > 0 || Object.keys(tableMetadata.enums).length > 0) nextTables[operation.table] = tableMetadata;
|
|
1754
|
+
else delete nextTables[operation.table];
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
delete nextTables[operation.table];
|
|
1758
|
+
});
|
|
1759
|
+
return {
|
|
1760
|
+
version: 1,
|
|
1761
|
+
tables: nextTables
|
|
1762
|
+
};
|
|
1763
|
+
};
|
|
1764
|
+
const rebuildPersistedColumnMappingsState = async (state, availableMigrations, features = resolvePersistedMetadataFeatures()) => {
|
|
1765
|
+
const availableByIdentity = new Map(availableMigrations.map(([migrationClass, file]) => [buildMigrationIdentity(file, migrationClass.name), migrationClass]));
|
|
1766
|
+
let nextState = createEmptyPersistedColumnMappingsState();
|
|
1767
|
+
const orderedMigrations = state.migrations.map((migration, index) => ({
|
|
1768
|
+
migration,
|
|
1769
|
+
index
|
|
1770
|
+
})).sort((left, right) => {
|
|
1771
|
+
const appliedAtOrder = left.migration.appliedAt.localeCompare(right.migration.appliedAt);
|
|
1772
|
+
if (appliedAtOrder !== 0) return appliedAtOrder;
|
|
1773
|
+
return left.index - right.index;
|
|
1774
|
+
});
|
|
1775
|
+
for (const { migration } of orderedMigrations) {
|
|
1776
|
+
const migrationClass = availableByIdentity.get(migration.id);
|
|
1777
|
+
if (!migrationClass) throw new ArkormException(`Unable to rebuild persisted column mappings because migration [${migration.id}] could not be resolved from the current migration files.`, {
|
|
1778
|
+
operation: "migration.columnMappings",
|
|
1779
|
+
meta: {
|
|
1780
|
+
migrationId: migration.id,
|
|
1781
|
+
file: migration.file,
|
|
1782
|
+
className: migration.className
|
|
1783
|
+
}
|
|
1784
|
+
});
|
|
1785
|
+
const operations = await getMigrationPlan(migrationClass, "up");
|
|
1786
|
+
nextState = applyOperationsToPersistedColumnMappingsState(nextState, operations, features);
|
|
1787
|
+
}
|
|
1788
|
+
return nextState;
|
|
1789
|
+
};
|
|
1790
|
+
const syncPersistedColumnMappingsFromState = async (cwd, state, availableMigrations, features = resolvePersistedMetadataFeatures()) => {
|
|
1791
|
+
const filePath = resolveColumnMappingsFilePath(cwd);
|
|
1792
|
+
const nextState = await rebuildPersistedColumnMappingsState(state, availableMigrations, features);
|
|
1793
|
+
if (Object.keys(nextState.tables).length === 0) {
|
|
1794
|
+
deletePersistedColumnMappingsState(filePath);
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
writePersistedColumnMappingsState(filePath, nextState);
|
|
1798
|
+
};
|
|
1799
|
+
const validatePersistedMetadataFeaturesForMigrations = async (migrations, features = resolvePersistedMetadataFeatures()) => {
|
|
1800
|
+
let nextState = createEmptyPersistedColumnMappingsState();
|
|
1801
|
+
for (const [migrationClass] of migrations) {
|
|
1802
|
+
const operations = await getMigrationPlan(migrationClass, "up");
|
|
1803
|
+
nextState = applyOperationsToPersistedColumnMappingsState(nextState, operations, features);
|
|
1804
|
+
}
|
|
1805
|
+
};
|
|
1806
|
+
const getPersistedEnumTsType = (values) => {
|
|
1807
|
+
return buildEnumUnionType(values);
|
|
1808
|
+
};
|
|
1809
|
+
|
|
1417
1810
|
//#endregion
|
|
1418
1811
|
//#region src/helpers/runtime-module-loader.ts
|
|
1419
1812
|
var RuntimeModuleLoader = class {
|
|
@@ -1441,6 +1834,10 @@ const resolveDefaultStubsPath = () => {
|
|
|
1441
1834
|
return path.join(process.cwd(), "stubs");
|
|
1442
1835
|
};
|
|
1443
1836
|
const baseConfig = {
|
|
1837
|
+
features: {
|
|
1838
|
+
persistedColumnMappings: true,
|
|
1839
|
+
persistedEnums: true
|
|
1840
|
+
},
|
|
1444
1841
|
paths: {
|
|
1445
1842
|
stubs: resolveDefaultStubsPath(),
|
|
1446
1843
|
seeders: path.join(process.cwd(), "database", "seeders"),
|
|
@@ -1453,6 +1850,7 @@ const baseConfig = {
|
|
|
1453
1850
|
};
|
|
1454
1851
|
const userConfig = {
|
|
1455
1852
|
...baseConfig,
|
|
1853
|
+
features: { ...baseConfig.features ?? {} },
|
|
1456
1854
|
paths: { ...baseConfig.paths ?? {} }
|
|
1457
1855
|
};
|
|
1458
1856
|
let runtimeConfigLoaded = false;
|
|
@@ -1475,6 +1873,15 @@ const mergePathConfig = (paths) => {
|
|
|
1475
1873
|
...incoming
|
|
1476
1874
|
};
|
|
1477
1875
|
};
|
|
1876
|
+
const mergeFeatureConfig = (features) => {
|
|
1877
|
+
const defaults = baseConfig.features ?? {};
|
|
1878
|
+
const current = userConfig.features ?? {};
|
|
1879
|
+
return {
|
|
1880
|
+
...defaults,
|
|
1881
|
+
...current,
|
|
1882
|
+
...features ?? {}
|
|
1883
|
+
};
|
|
1884
|
+
};
|
|
1478
1885
|
const bindAdapterToModels = (adapter, models) => {
|
|
1479
1886
|
models.forEach((model) => {
|
|
1480
1887
|
model.setAdapter(adapter);
|
|
@@ -1500,6 +1907,7 @@ const getUserConfig = (key) => {
|
|
|
1500
1907
|
const configureArkormRuntime = (prisma, options = {}) => {
|
|
1501
1908
|
const nextConfig = {
|
|
1502
1909
|
...userConfig,
|
|
1910
|
+
features: mergeFeatureConfig(options.features),
|
|
1503
1911
|
paths: mergePathConfig(options.paths)
|
|
1504
1912
|
};
|
|
1505
1913
|
nextConfig.prisma = prisma;
|
|
@@ -1544,6 +1952,7 @@ const resolveAndApplyConfig = (imported) => {
|
|
|
1544
1952
|
configureArkormRuntime(config.prisma, {
|
|
1545
1953
|
adapter: config.adapter,
|
|
1546
1954
|
boot: config.boot,
|
|
1955
|
+
features: config.features,
|
|
1547
1956
|
pagination: config.pagination,
|
|
1548
1957
|
paths: config.paths,
|
|
1549
1958
|
outputExt: config.outputExt
|
|
@@ -2135,6 +2544,26 @@ var CliApp = class {
|
|
|
2135
2544
|
skipped
|
|
2136
2545
|
};
|
|
2137
2546
|
}
|
|
2547
|
+
applyPersistedEnumMetadata(structure) {
|
|
2548
|
+
const persistedEnums = getPersistedEnumMap(structure.table, {
|
|
2549
|
+
features: resolvePersistedMetadataFeatures(this.getConfig("features")),
|
|
2550
|
+
strict: true
|
|
2551
|
+
});
|
|
2552
|
+
if (Object.keys(persistedEnums).length === 0) return structure;
|
|
2553
|
+
return {
|
|
2554
|
+
...structure,
|
|
2555
|
+
fields: structure.fields.map((field) => {
|
|
2556
|
+
const enumValues = persistedEnums[field.name];
|
|
2557
|
+
if (!enumValues || enumValues.length === 0) return field;
|
|
2558
|
+
const enumType = getPersistedEnumTsType(enumValues);
|
|
2559
|
+
const isArray = /^Array<.+>$/.test(field.type);
|
|
2560
|
+
return {
|
|
2561
|
+
...field,
|
|
2562
|
+
type: isArray ? `Array<${enumType}>` : enumType
|
|
2563
|
+
};
|
|
2564
|
+
})
|
|
2565
|
+
};
|
|
2566
|
+
}
|
|
2138
2567
|
/**
|
|
2139
2568
|
* Parse Prisma enum definitions from a schema and return their member names.
|
|
2140
2569
|
*
|
|
@@ -2293,7 +2722,10 @@ var CliApp = class {
|
|
|
2293
2722
|
return all;
|
|
2294
2723
|
}, /* @__PURE__ */ new Map());
|
|
2295
2724
|
const discovered = await adapter.introspectModels({ tables: [...new Set([...sources.values()].map((source) => source.table))] });
|
|
2296
|
-
const structuresByTable = new Map(discovered.map((model) =>
|
|
2725
|
+
const structuresByTable = new Map(discovered.map((model) => {
|
|
2726
|
+
const enriched = this.applyPersistedEnumMetadata(model);
|
|
2727
|
+
return [enriched.table, enriched];
|
|
2728
|
+
}));
|
|
2297
2729
|
const result = this.syncModelFiles(modelFiles, (filePath) => {
|
|
2298
2730
|
const parsed = sources.get(filePath);
|
|
2299
2731
|
return parsed ? structuresByTable.get(parsed.table) : void 0;
|
|
@@ -2507,112 +2939,6 @@ var MakeSeederCommand = class extends Command {
|
|
|
2507
2939
|
}
|
|
2508
2940
|
};
|
|
2509
2941
|
|
|
2510
|
-
//#endregion
|
|
2511
|
-
//#region src/helpers/migration-history.ts
|
|
2512
|
-
const DEFAULT_STATE = {
|
|
2513
|
-
version: 1,
|
|
2514
|
-
migrations: [],
|
|
2515
|
-
runs: []
|
|
2516
|
-
};
|
|
2517
|
-
const resolveMigrationStateFilePath = (cwd, configuredPath) => {
|
|
2518
|
-
if (configuredPath && configuredPath.trim().length > 0) return resolve(configuredPath);
|
|
2519
|
-
return join(cwd, ".arkormx", "migrations.applied.json");
|
|
2520
|
-
};
|
|
2521
|
-
const buildMigrationIdentity = (filePath, className) => {
|
|
2522
|
-
const fileName = filePath.split("/").pop()?.split("\\").pop() ?? filePath;
|
|
2523
|
-
return `${fileName.slice(0, fileName.length - extname(fileName).length)}:${className}`;
|
|
2524
|
-
};
|
|
2525
|
-
const computeMigrationChecksum = (filePath) => {
|
|
2526
|
-
const source = readFileSync(filePath, "utf-8");
|
|
2527
|
-
return createHash("sha256").update(source).digest("hex");
|
|
2528
|
-
};
|
|
2529
|
-
const readAppliedMigrationsState = (stateFilePath) => {
|
|
2530
|
-
if (!existsSync(stateFilePath)) return { ...DEFAULT_STATE };
|
|
2531
|
-
try {
|
|
2532
|
-
const parsed = JSON.parse(readFileSync(stateFilePath, "utf-8"));
|
|
2533
|
-
if (!Array.isArray(parsed.migrations)) return { ...DEFAULT_STATE };
|
|
2534
|
-
return {
|
|
2535
|
-
version: 1,
|
|
2536
|
-
migrations: parsed.migrations.filter((migration) => {
|
|
2537
|
-
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");
|
|
2538
|
-
}),
|
|
2539
|
-
runs: Array.isArray(parsed.runs) ? parsed.runs.filter((run) => {
|
|
2540
|
-
return typeof run?.id === "string" && typeof run?.appliedAt === "string" && Array.isArray(run?.migrationIds) && run.migrationIds.every((item) => typeof item === "string");
|
|
2541
|
-
}) : []
|
|
2542
|
-
};
|
|
2543
|
-
} catch {
|
|
2544
|
-
return { ...DEFAULT_STATE };
|
|
2545
|
-
}
|
|
2546
|
-
};
|
|
2547
|
-
const writeAppliedMigrationsState = (stateFilePath, state) => {
|
|
2548
|
-
const directory = dirname(stateFilePath);
|
|
2549
|
-
if (!existsSync(directory)) mkdirSync(directory, { recursive: true });
|
|
2550
|
-
writeFileSync(stateFilePath, JSON.stringify(state, null, 2));
|
|
2551
|
-
};
|
|
2552
|
-
const isMigrationApplied = (state, identity, checksum) => {
|
|
2553
|
-
const matched = state.migrations.find((migration) => migration.id === identity);
|
|
2554
|
-
if (!matched) return false;
|
|
2555
|
-
if (checksum && matched.checksum) return matched.checksum === checksum;
|
|
2556
|
-
if (checksum && !matched.checksum) return false;
|
|
2557
|
-
return true;
|
|
2558
|
-
};
|
|
2559
|
-
const findAppliedMigration = (state, identity) => {
|
|
2560
|
-
return state.migrations.find((migration) => migration.id === identity);
|
|
2561
|
-
};
|
|
2562
|
-
const markMigrationApplied = (state, entry) => {
|
|
2563
|
-
const next = state.migrations.filter((migration) => migration.id !== entry.id);
|
|
2564
|
-
next.push(entry);
|
|
2565
|
-
return {
|
|
2566
|
-
version: 1,
|
|
2567
|
-
migrations: next,
|
|
2568
|
-
runs: state.runs ?? []
|
|
2569
|
-
};
|
|
2570
|
-
};
|
|
2571
|
-
const removeAppliedMigration = (state, identity) => {
|
|
2572
|
-
return {
|
|
2573
|
-
version: 1,
|
|
2574
|
-
migrations: state.migrations.filter((migration) => migration.id !== identity),
|
|
2575
|
-
runs: (state.runs ?? []).map((run) => ({
|
|
2576
|
-
...run,
|
|
2577
|
-
migrationIds: run.migrationIds.filter((id) => id !== identity)
|
|
2578
|
-
})).filter((run) => run.migrationIds.length > 0)
|
|
2579
|
-
};
|
|
2580
|
-
};
|
|
2581
|
-
const buildMigrationRunId = () => {
|
|
2582
|
-
return `run_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
2583
|
-
};
|
|
2584
|
-
const markMigrationRun = (state, run) => {
|
|
2585
|
-
const nextRuns = (state.runs ?? []).filter((existing) => existing.id !== run.id);
|
|
2586
|
-
nextRuns.push(run);
|
|
2587
|
-
return {
|
|
2588
|
-
version: 1,
|
|
2589
|
-
migrations: state.migrations,
|
|
2590
|
-
runs: nextRuns
|
|
2591
|
-
};
|
|
2592
|
-
};
|
|
2593
|
-
const getLastMigrationRun = (state) => {
|
|
2594
|
-
const runs = state.runs ?? [];
|
|
2595
|
-
if (runs.length === 0) return void 0;
|
|
2596
|
-
return runs.map((run, index) => ({
|
|
2597
|
-
run,
|
|
2598
|
-
index
|
|
2599
|
-
})).sort((left, right) => {
|
|
2600
|
-
const appliedAtOrder = right.run.appliedAt.localeCompare(left.run.appliedAt);
|
|
2601
|
-
if (appliedAtOrder !== 0) return appliedAtOrder;
|
|
2602
|
-
return right.index - left.index;
|
|
2603
|
-
})[0]?.run;
|
|
2604
|
-
};
|
|
2605
|
-
const getLatestAppliedMigrations = (state, steps) => {
|
|
2606
|
-
return state.migrations.map((migration, index) => ({
|
|
2607
|
-
migration,
|
|
2608
|
-
index
|
|
2609
|
-
})).sort((left, right) => {
|
|
2610
|
-
const appliedAtOrder = right.migration.appliedAt.localeCompare(left.migration.appliedAt);
|
|
2611
|
-
if (appliedAtOrder !== 0) return appliedAtOrder;
|
|
2612
|
-
return right.index - left.index;
|
|
2613
|
-
}).slice(0, Math.max(0, steps)).map((entry) => entry.migration);
|
|
2614
|
-
};
|
|
2615
|
-
|
|
2616
2942
|
//#endregion
|
|
2617
2943
|
//#region src/database/Migration.ts
|
|
2618
2944
|
const MIGRATION_BRAND = Symbol.for("arkormx.migration");
|
|
@@ -2667,7 +2993,10 @@ var MigrateCommand = class extends Command {
|
|
|
2667
2993
|
const classes = this.option("all") || !this.argument("name") ? await this.loadAllMigrations(migrationsDir) : (await this.loadNamedMigration(migrationsDir, this.argument("name"))).filter(([cls]) => cls !== void 0);
|
|
2668
2994
|
if (classes.length === 0) return void this.error("Error: No migration classes found to run.");
|
|
2669
2995
|
const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
|
|
2670
|
-
let appliedState =
|
|
2996
|
+
let appliedState = await readAppliedMigrationsStateFromStore(this.app.getConfig("adapter"), stateFilePath);
|
|
2997
|
+
const adapter = this.app.getConfig("adapter");
|
|
2998
|
+
const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
|
|
2999
|
+
const persistedFeatures = resolvePersistedMetadataFeatures(this.app.getConfig("features"));
|
|
2671
3000
|
const skipped = [];
|
|
2672
3001
|
const changed = [];
|
|
2673
3002
|
const pending = classes.filter(([migrationClass, file]) => {
|
|
@@ -2686,13 +3015,31 @@ var MigrateCommand = class extends Command {
|
|
|
2686
3015
|
this.success(this.app.splitLogger("Changed", `${file} (${migrationClass.name})`));
|
|
2687
3016
|
});
|
|
2688
3017
|
if (pending.length === 0) {
|
|
3018
|
+
if (appliedState) try {
|
|
3019
|
+
await syncPersistedColumnMappingsFromState(process.cwd(), appliedState, await this.loadAllMigrations(migrationsDir), persistedFeatures);
|
|
3020
|
+
} catch (error) {
|
|
3021
|
+
this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
3022
|
+
return;
|
|
3023
|
+
}
|
|
2689
3024
|
this.success("No pending migration classes to apply.");
|
|
2690
3025
|
return;
|
|
2691
3026
|
}
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
3027
|
+
if (useDatabaseMigrations) try {
|
|
3028
|
+
await validatePersistedMetadataFeaturesForMigrations(pending, persistedFeatures);
|
|
3029
|
+
} catch (error) {
|
|
3030
|
+
this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
3031
|
+
return;
|
|
3032
|
+
}
|
|
3033
|
+
for (const [MigrationClassItem] of pending) {
|
|
3034
|
+
if (useDatabaseMigrations) {
|
|
3035
|
+
await applyMigrationToDatabase(adapter, MigrationClassItem);
|
|
3036
|
+
continue;
|
|
3037
|
+
}
|
|
3038
|
+
await applyMigrationToPrismaSchema(MigrationClassItem, {
|
|
3039
|
+
schemaPath,
|
|
3040
|
+
write: true
|
|
3041
|
+
});
|
|
3042
|
+
}
|
|
2696
3043
|
if (appliedState) {
|
|
2697
3044
|
const runAppliedIds = [];
|
|
2698
3045
|
for (const [migrationClass, file] of pending) {
|
|
@@ -2711,10 +3058,16 @@ var MigrateCommand = class extends Command {
|
|
|
2711
3058
|
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2712
3059
|
migrationIds: runAppliedIds
|
|
2713
3060
|
});
|
|
2714
|
-
|
|
3061
|
+
await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
|
|
3062
|
+
try {
|
|
3063
|
+
await syncPersistedColumnMappingsFromState(process.cwd(), appliedState, await this.loadAllMigrations(migrationsDir), persistedFeatures);
|
|
3064
|
+
} catch (error) {
|
|
3065
|
+
this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
3066
|
+
return;
|
|
3067
|
+
}
|
|
2715
3068
|
}
|
|
2716
|
-
if (!this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
|
|
2717
|
-
if (!this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
|
|
3069
|
+
if (!useDatabaseMigrations && !this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
|
|
3070
|
+
if (!useDatabaseMigrations && !this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
|
|
2718
3071
|
else runPrismaCommand([
|
|
2719
3072
|
"migrate",
|
|
2720
3073
|
"dev",
|
|
@@ -2774,6 +3127,103 @@ var MigrateCommand = class extends Command {
|
|
|
2774
3127
|
}
|
|
2775
3128
|
};
|
|
2776
3129
|
|
|
3130
|
+
//#endregion
|
|
3131
|
+
//#region src/cli/commands/MigrateFreshCommand.ts
|
|
3132
|
+
var MigrateFreshCommand = class extends Command {
|
|
3133
|
+
signature = `migrate:fresh
|
|
3134
|
+
{--skip-generate : Skip prisma generate}
|
|
3135
|
+
{--skip-migrate : Skip prisma database sync}
|
|
3136
|
+
{--state-file= : Path to applied migration state file}
|
|
3137
|
+
{--schema= : Explicit prisma schema path}
|
|
3138
|
+
`;
|
|
3139
|
+
description = "Reset the database and rerun all migration classes";
|
|
3140
|
+
async handle() {
|
|
3141
|
+
this.app.command = this;
|
|
3142
|
+
const configuredMigrationsDir = this.app.getConfig("paths")?.migrations ?? join(process.cwd(), "database", "migrations");
|
|
3143
|
+
const migrationsDir = this.app.resolveRuntimeDirectoryPath(configuredMigrationsDir);
|
|
3144
|
+
if (!existsSync(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
|
|
3145
|
+
const adapter = this.app.getConfig("adapter");
|
|
3146
|
+
const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
|
|
3147
|
+
const persistedFeatures = resolvePersistedMetadataFeatures(this.app.getConfig("features"));
|
|
3148
|
+
const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join(process.cwd(), "prisma", "schema.prisma");
|
|
3149
|
+
const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
|
|
3150
|
+
const migrations = await this.loadAllMigrations(migrationsDir);
|
|
3151
|
+
if (migrations.length === 0) return void this.error("Error: No migration classes found to run.");
|
|
3152
|
+
if (useDatabaseMigrations) try {
|
|
3153
|
+
await validatePersistedMetadataFeaturesForMigrations(migrations, persistedFeatures);
|
|
3154
|
+
} catch (error) {
|
|
3155
|
+
this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
3156
|
+
return;
|
|
3157
|
+
}
|
|
3158
|
+
if (useDatabaseMigrations) {
|
|
3159
|
+
if (!supportsDatabaseReset(adapter)) {
|
|
3160
|
+
this.error("Error: Your current database adapter does not support database reset.");
|
|
3161
|
+
return;
|
|
3162
|
+
}
|
|
3163
|
+
await adapter.resetDatabase();
|
|
3164
|
+
} else {
|
|
3165
|
+
if (!existsSync(schemaPath)) return void this.error(`Error: Prisma schema file not found: ${this.app.formatPathForLog(schemaPath)}`);
|
|
3166
|
+
writeFileSync(schemaPath, stripPrismaSchemaModelsAndEnums(readFileSync(schemaPath, "utf-8")));
|
|
3167
|
+
}
|
|
3168
|
+
let appliedState = createEmptyAppliedMigrationsState();
|
|
3169
|
+
await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
|
|
3170
|
+
for (const [MigrationClassItem] of migrations) {
|
|
3171
|
+
if (useDatabaseMigrations) {
|
|
3172
|
+
await applyMigrationToDatabase(adapter, MigrationClassItem);
|
|
3173
|
+
continue;
|
|
3174
|
+
}
|
|
3175
|
+
await applyMigrationToPrismaSchema(MigrationClassItem, {
|
|
3176
|
+
schemaPath,
|
|
3177
|
+
write: true
|
|
3178
|
+
});
|
|
3179
|
+
}
|
|
3180
|
+
for (const [migrationClass, file] of migrations) appliedState = markMigrationApplied(appliedState, {
|
|
3181
|
+
id: buildMigrationIdentity(file, migrationClass.name),
|
|
3182
|
+
file,
|
|
3183
|
+
className: migrationClass.name,
|
|
3184
|
+
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3185
|
+
checksum: computeMigrationChecksum(file)
|
|
3186
|
+
});
|
|
3187
|
+
appliedState = markMigrationRun(appliedState, {
|
|
3188
|
+
id: buildMigrationRunId(),
|
|
3189
|
+
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3190
|
+
migrationIds: appliedState.migrations.map((migration) => migration.id)
|
|
3191
|
+
});
|
|
3192
|
+
await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
|
|
3193
|
+
try {
|
|
3194
|
+
await syncPersistedColumnMappingsFromState(process.cwd(), appliedState, migrations, persistedFeatures);
|
|
3195
|
+
} catch (error) {
|
|
3196
|
+
this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
3197
|
+
return;
|
|
3198
|
+
}
|
|
3199
|
+
if (!useDatabaseMigrations) {
|
|
3200
|
+
const schemaArgs = this.option("schema") ? ["--schema", schemaPath] : [];
|
|
3201
|
+
if (!this.option("skip-generate")) runPrismaCommand(["generate", ...schemaArgs], process.cwd());
|
|
3202
|
+
if (!this.option("skip-migrate")) runPrismaCommand([
|
|
3203
|
+
"db",
|
|
3204
|
+
"push",
|
|
3205
|
+
"--force-reset",
|
|
3206
|
+
...schemaArgs
|
|
3207
|
+
], process.cwd());
|
|
3208
|
+
}
|
|
3209
|
+
this.success(`Refreshed database with ${migrations.length} migration(s).`);
|
|
3210
|
+
migrations.forEach(([_, file]) => this.success(this.app.splitLogger("Migrated", file)));
|
|
3211
|
+
}
|
|
3212
|
+
async loadAllMigrations(migrationsDir) {
|
|
3213
|
+
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)));
|
|
3214
|
+
return (await Promise.all(files.map(async (file) => (await this.loadMigrationClassesFromFile(file)).map((cls) => [cls, file])))).flat();
|
|
3215
|
+
}
|
|
3216
|
+
async loadMigrationClassesFromFile(filePath) {
|
|
3217
|
+
const imported = await RuntimeModuleLoader.load(filePath);
|
|
3218
|
+
return Object.values(imported).filter((value) => {
|
|
3219
|
+
if (typeof value !== "function") return false;
|
|
3220
|
+
const candidate = value;
|
|
3221
|
+
const prototype = candidate.prototype;
|
|
3222
|
+
return candidate[MIGRATION_BRAND] === true || typeof prototype?.up === "function" && typeof prototype?.down === "function";
|
|
3223
|
+
});
|
|
3224
|
+
}
|
|
3225
|
+
};
|
|
3226
|
+
|
|
2777
3227
|
//#endregion
|
|
2778
3228
|
//#region src/cli/commands/MigrateRollbackCommand.ts
|
|
2779
3229
|
/**
|
|
@@ -2802,7 +3252,10 @@ var MigrateRollbackCommand = class extends Command {
|
|
|
2802
3252
|
if (!existsSync(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
|
|
2803
3253
|
const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join(process.cwd(), "prisma", "schema.prisma");
|
|
2804
3254
|
const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
|
|
2805
|
-
|
|
3255
|
+
const adapter = this.app.getConfig("adapter");
|
|
3256
|
+
const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
|
|
3257
|
+
const persistedFeatures = resolvePersistedMetadataFeatures(this.app.getConfig("features"));
|
|
3258
|
+
let appliedState = await readAppliedMigrationsStateFromStore(adapter, stateFilePath);
|
|
2806
3259
|
const stepOption = this.option("step");
|
|
2807
3260
|
const stepCount = stepOption == null ? void 0 : Number(stepOption);
|
|
2808
3261
|
if (stepCount != null && (!Number.isFinite(stepCount) || stepCount <= 0 || !Number.isInteger(stepCount))) return void this.error("Error: --step must be a positive integer.");
|
|
@@ -2824,17 +3277,29 @@ var MigrateRollbackCommand = class extends Command {
|
|
|
2824
3277
|
rollbackClasses.forEach(([_, file]) => this.success(this.app.splitLogger("WouldRollback", file)));
|
|
2825
3278
|
return;
|
|
2826
3279
|
}
|
|
2827
|
-
for (const [MigrationClassItem] of rollbackClasses)
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
3280
|
+
for (const [MigrationClassItem] of rollbackClasses) {
|
|
3281
|
+
if (useDatabaseMigrations) {
|
|
3282
|
+
await applyMigrationRollbackToDatabase(adapter, MigrationClassItem);
|
|
3283
|
+
continue;
|
|
3284
|
+
}
|
|
3285
|
+
await applyMigrationRollbackToPrismaSchema(MigrationClassItem, {
|
|
3286
|
+
schemaPath,
|
|
3287
|
+
write: true
|
|
3288
|
+
});
|
|
3289
|
+
}
|
|
2831
3290
|
for (const [migrationClass, file] of rollbackClasses) {
|
|
2832
3291
|
const identity = buildMigrationIdentity(file, migrationClass.name);
|
|
2833
3292
|
appliedState = removeAppliedMigration(appliedState, identity);
|
|
2834
3293
|
}
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
3294
|
+
await writeAppliedMigrationsStateToStore(adapter, stateFilePath, appliedState);
|
|
3295
|
+
try {
|
|
3296
|
+
await syncPersistedColumnMappingsFromState(process.cwd(), appliedState, available, persistedFeatures);
|
|
3297
|
+
} catch (error) {
|
|
3298
|
+
this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
3299
|
+
return;
|
|
3300
|
+
}
|
|
3301
|
+
if (!useDatabaseMigrations && !this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
|
|
3302
|
+
if (!useDatabaseMigrations && !this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
|
|
2838
3303
|
else runPrismaCommand([
|
|
2839
3304
|
"migrate",
|
|
2840
3305
|
"dev",
|
|
@@ -2878,32 +3343,39 @@ var MigrationHistoryCommand = class extends Command {
|
|
|
2878
3343
|
async handle() {
|
|
2879
3344
|
this.app.command = this;
|
|
2880
3345
|
const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
|
|
3346
|
+
const adapter = this.app.getConfig("adapter");
|
|
3347
|
+
const usesDatabaseState = supportsDatabaseMigrationState(adapter);
|
|
2881
3348
|
if (this.option("delete")) {
|
|
3349
|
+
if (usesDatabaseState) {
|
|
3350
|
+
await adapter.writeAppliedMigrationsState(createEmptyAppliedMigrationsState());
|
|
3351
|
+
deletePersistedColumnMappingsState(resolveColumnMappingsFilePath(process.cwd()));
|
|
3352
|
+
this.success("Deleted tracked migration state from database.");
|
|
3353
|
+
return;
|
|
3354
|
+
}
|
|
2882
3355
|
if (!existsSync(stateFilePath)) {
|
|
2883
3356
|
this.success(`No migration state file found at ${this.app.formatPathForLog(stateFilePath)}`);
|
|
2884
3357
|
return;
|
|
2885
3358
|
}
|
|
2886
3359
|
rmSync(stateFilePath);
|
|
3360
|
+
deletePersistedColumnMappingsState(resolveColumnMappingsFilePath(process.cwd()));
|
|
2887
3361
|
this.success(`Deleted migration state file: ${this.app.formatPathForLog(stateFilePath)}`);
|
|
2888
3362
|
return;
|
|
2889
3363
|
}
|
|
2890
3364
|
if (this.option("reset")) {
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
});
|
|
2895
|
-
this.success(`Reset migration state: ${this.app.formatPathForLog(stateFilePath)}`);
|
|
3365
|
+
await writeAppliedMigrationsStateToStore(adapter, stateFilePath, createEmptyAppliedMigrationsState());
|
|
3366
|
+
deletePersistedColumnMappingsState(resolveColumnMappingsFilePath(process.cwd()));
|
|
3367
|
+
this.success(usesDatabaseState ? "Reset migration state in database." : `Reset migration state: ${this.app.formatPathForLog(stateFilePath)}`);
|
|
2896
3368
|
return;
|
|
2897
3369
|
}
|
|
2898
|
-
const state =
|
|
3370
|
+
const state = await readAppliedMigrationsStateFromStore(adapter, stateFilePath);
|
|
2899
3371
|
if (this.option("json")) {
|
|
2900
3372
|
this.success(JSON.stringify({
|
|
2901
|
-
path: stateFilePath,
|
|
3373
|
+
path: usesDatabaseState ? "database" : stateFilePath,
|
|
2902
3374
|
...state
|
|
2903
3375
|
}, null, 2));
|
|
2904
3376
|
return;
|
|
2905
3377
|
}
|
|
2906
|
-
this.success(this.app.splitLogger("State", stateFilePath));
|
|
3378
|
+
this.success(this.app.splitLogger("State", usesDatabaseState ? "database" : stateFilePath));
|
|
2907
3379
|
this.success(this.app.splitLogger("Tracked", String(state.migrations.length)));
|
|
2908
3380
|
if (state.migrations.length === 0) {
|
|
2909
3381
|
this.success("No tracked migrations found.");
|
|
@@ -2925,10 +3397,16 @@ var ModelsSyncCommand = class extends Command {
|
|
|
2925
3397
|
description = "Sync model declare attributes from the active adapter when supported, otherwise fall back to the Prisma schema";
|
|
2926
3398
|
async handle() {
|
|
2927
3399
|
this.app.command = this;
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
3400
|
+
let result;
|
|
3401
|
+
try {
|
|
3402
|
+
result = await this.app.syncModels({
|
|
3403
|
+
schemaPath: this.option("schema") ? resolve(String(this.option("schema"))) : void 0,
|
|
3404
|
+
modelsDir: this.option("models") ? resolve(String(this.option("models"))) : void 0
|
|
3405
|
+
});
|
|
3406
|
+
} catch (error) {
|
|
3407
|
+
this.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
3408
|
+
return;
|
|
3409
|
+
}
|
|
2932
3410
|
const updatedLines = result.updated.length === 0 ? [this.app.splitLogger("Updated", "none")] : result.updated.map((path) => this.app.splitLogger("Updated", path));
|
|
2933
3411
|
this.success("SUCCESS: Model sync completed with the following results:");
|
|
2934
3412
|
[
|
|
@@ -3096,6 +3574,7 @@ await Kernel.init(app, {
|
|
|
3096
3574
|
ModelsSyncCommand,
|
|
3097
3575
|
SeedCommand,
|
|
3098
3576
|
MigrateCommand,
|
|
3577
|
+
MigrateFreshCommand,
|
|
3099
3578
|
MigrateRollbackCommand,
|
|
3100
3579
|
MigrationHistoryCommand
|
|
3101
3580
|
],
|