arkormx 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -2,13 +2,14 @@
2
2
  import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "fs";
3
3
  import path, { dirname, extname, join, relative } from "path";
4
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";
5
+ import { 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 "node:fs";
6
+ import { dirname as dirname$1, extname as extname$1, join as join$1, resolve } from "node:path";
7
7
  import { spawnSync } from "node:child_process";
8
8
  import { str } from "@h3ravel/support";
9
9
  import { fileURLToPath, pathToFileURL } from "url";
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
@@ -1823,6 +1824,63 @@ var MakeSeederCommand = class extends Command {
1823
1824
  }
1824
1825
  };
1825
1826
 
1827
+ //#endregion
1828
+ //#region src/helpers/migration-history.ts
1829
+ const DEFAULT_STATE = {
1830
+ version: 1,
1831
+ migrations: []
1832
+ };
1833
+ const resolveMigrationStateFilePath = (cwd, configuredPath) => {
1834
+ if (configuredPath && configuredPath.trim().length > 0) return resolve(configuredPath);
1835
+ return join$1(cwd, ".arkormx", "migrations.applied.json");
1836
+ };
1837
+ const buildMigrationIdentity = (filePath, className) => {
1838
+ const fileName = filePath.split("/").pop()?.split("\\").pop() ?? filePath;
1839
+ return `${fileName.slice(0, fileName.length - extname$1(fileName).length)}:${className}`;
1840
+ };
1841
+ const computeMigrationChecksum = (filePath) => {
1842
+ const source = readFileSync$1(filePath, "utf-8");
1843
+ return createHash("sha256").update(source).digest("hex");
1844
+ };
1845
+ const readAppliedMigrationsState = (stateFilePath) => {
1846
+ if (!existsSync$1(stateFilePath)) return { ...DEFAULT_STATE };
1847
+ try {
1848
+ const parsed = JSON.parse(readFileSync$1(stateFilePath, "utf-8"));
1849
+ if (!Array.isArray(parsed.migrations)) return { ...DEFAULT_STATE };
1850
+ return {
1851
+ version: 1,
1852
+ migrations: parsed.migrations.filter((migration) => {
1853
+ 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");
1854
+ })
1855
+ };
1856
+ } catch {
1857
+ return { ...DEFAULT_STATE };
1858
+ }
1859
+ };
1860
+ const writeAppliedMigrationsState = (stateFilePath, state) => {
1861
+ const directory = dirname$1(stateFilePath);
1862
+ if (!existsSync$1(directory)) mkdirSync$1(directory, { recursive: true });
1863
+ writeFileSync$1(stateFilePath, JSON.stringify(state, null, 2));
1864
+ };
1865
+ const isMigrationApplied = (state, identity, checksum) => {
1866
+ const matched = state.migrations.find((migration) => migration.id === identity);
1867
+ if (!matched) return false;
1868
+ if (checksum && matched.checksum) return matched.checksum === checksum;
1869
+ if (checksum && !matched.checksum) return false;
1870
+ return true;
1871
+ };
1872
+ const findAppliedMigration = (state, identity) => {
1873
+ return state.migrations.find((migration) => migration.id === identity);
1874
+ };
1875
+ const markMigrationApplied = (state, entry) => {
1876
+ const next = state.migrations.filter((migration) => migration.id !== entry.id);
1877
+ next.push(entry);
1878
+ return {
1879
+ version: 1,
1880
+ migrations: next
1881
+ };
1882
+ };
1883
+
1826
1884
  //#endregion
1827
1885
  //#region src/database/Migration.ts
1828
1886
  const MIGRATION_BRAND = Symbol.for("arkormx.migration");
@@ -1854,6 +1912,7 @@ var MigrateCommand = class extends Command {
1854
1912
  {--deploy : Use prisma migrate deploy instead of migrate dev}
1855
1913
  {--skip-generate : Skip prisma generate}
1856
1914
  {--skip-migrate : Skip prisma migrate command}
1915
+ {--state-file= : Path to applied migration state file}
1857
1916
  {--schema= : Explicit prisma schema path}
1858
1917
  {--migration-name= : Name for prisma migrate dev}
1859
1918
  `;
@@ -1875,10 +1934,47 @@ var MigrateCommand = class extends Command {
1875
1934
  const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join$1(process.cwd(), "prisma", "schema.prisma");
1876
1935
  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
1936
  if (classes.length === 0) return void this.error("Error: No migration classes found to run.");
1878
- for (const [MigrationClassItem] of classes) await applyMigrationToPrismaSchema(MigrationClassItem, {
1937
+ const shouldTrackApplied = Boolean(this.option("all") || !this.argument("name") || this.option("state-file"));
1938
+ const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
1939
+ let appliedState = shouldTrackApplied ? readAppliedMigrationsState(stateFilePath) : void 0;
1940
+ const skipped = [];
1941
+ const changed = [];
1942
+ const pending = classes.filter(([migrationClass, file]) => {
1943
+ if (!appliedState) return true;
1944
+ const identity = buildMigrationIdentity(file, migrationClass.name);
1945
+ const checksum = computeMigrationChecksum(file);
1946
+ const alreadyApplied = isMigrationApplied(appliedState, identity, checksum);
1947
+ if (alreadyApplied) skipped.push([migrationClass, file]);
1948
+ else if (findAppliedMigration(appliedState, identity)) changed.push([migrationClass, file]);
1949
+ return !alreadyApplied;
1950
+ });
1951
+ skipped.forEach(([migrationClass, file]) => {
1952
+ this.success(this.app.splitLogger("Skipped", `${file} (${migrationClass.name})`));
1953
+ });
1954
+ changed.forEach(([migrationClass, file]) => {
1955
+ this.success(this.app.splitLogger("Changed", `${file} (${migrationClass.name})`));
1956
+ });
1957
+ if (pending.length === 0) {
1958
+ this.success("No pending migration classes to apply.");
1959
+ return;
1960
+ }
1961
+ for (const [MigrationClassItem] of pending) await applyMigrationToPrismaSchema(MigrationClassItem, {
1879
1962
  schemaPath,
1880
1963
  write: true
1881
1964
  });
1965
+ if (appliedState) {
1966
+ for (const [migrationClass, file] of pending) {
1967
+ const identity = buildMigrationIdentity(file, migrationClass.name);
1968
+ appliedState = markMigrationApplied(appliedState, {
1969
+ id: identity,
1970
+ file,
1971
+ className: migrationClass.name,
1972
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
1973
+ checksum: computeMigrationChecksum(file)
1974
+ });
1975
+ }
1976
+ writeAppliedMigrationsState(stateFilePath, appliedState);
1977
+ }
1882
1978
  if (!this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
1883
1979
  if (!this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
1884
1980
  else runPrismaCommand([
@@ -1887,8 +1983,8 @@ var MigrateCommand = class extends Command {
1887
1983
  "--name",
1888
1984
  this.option("migration-name") ? String(this.option("migration-name")) : `arkorm_cli_${Date.now()}`
1889
1985
  ], process.cwd());
1890
- this.success(`Applied ${classes.length} migration(s).`);
1891
- classes.forEach(([_, file]) => this.success(this.app.splitLogger("Migrated", file)));
1986
+ this.success(`Applied ${pending.length} migration(s).`);
1987
+ pending.forEach(([_, file]) => this.success(this.app.splitLogger("Migrated", file)));
1892
1988
  }
1893
1989
  /**
1894
1990
  * Load all migration classes from the specified directory.
@@ -1940,6 +2036,62 @@ var MigrateCommand = class extends Command {
1940
2036
  }
1941
2037
  };
1942
2038
 
2039
+ //#endregion
2040
+ //#region src/cli/commands/MigrationHistoryCommand.ts
2041
+ /**
2042
+ * The MigrationHistoryCommand class manages tracked migration run history.
2043
+ *
2044
+ * @author Legacy (3m1n3nc3)
2045
+ * @since 0.2.4
2046
+ */
2047
+ var MigrationHistoryCommand = class extends Command {
2048
+ signature = `migrate:history
2049
+ {--state-file= : Path to applied migration state file}
2050
+ {--reset : Clear tracked migration history file}
2051
+ {--delete : Delete tracked migration history file}
2052
+ {--json : Print raw JSON output}
2053
+ `;
2054
+ description = "Inspect or reset tracked migration history";
2055
+ async handle() {
2056
+ this.app.command = this;
2057
+ const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
2058
+ if (this.option("delete")) {
2059
+ if (!existsSync$1(stateFilePath)) {
2060
+ this.success(`No migration state file found at ${this.app.formatPathForLog(stateFilePath)}`);
2061
+ return;
2062
+ }
2063
+ rmSync$1(stateFilePath);
2064
+ this.success(`Deleted migration state file: ${this.app.formatPathForLog(stateFilePath)}`);
2065
+ return;
2066
+ }
2067
+ if (this.option("reset")) {
2068
+ writeAppliedMigrationsState(stateFilePath, {
2069
+ version: 1,
2070
+ migrations: []
2071
+ });
2072
+ this.success(`Reset migration state: ${this.app.formatPathForLog(stateFilePath)}`);
2073
+ return;
2074
+ }
2075
+ const state = readAppliedMigrationsState(stateFilePath);
2076
+ if (this.option("json")) {
2077
+ this.success(JSON.stringify({
2078
+ path: stateFilePath,
2079
+ ...state
2080
+ }, null, 2));
2081
+ return;
2082
+ }
2083
+ this.success(this.app.splitLogger("State", stateFilePath));
2084
+ this.success(this.app.splitLogger("Tracked", String(state.migrations.length)));
2085
+ if (state.migrations.length === 0) {
2086
+ this.success("No tracked migrations found.");
2087
+ return;
2088
+ }
2089
+ state.migrations.sort((left, right) => left.appliedAt.localeCompare(right.appliedAt)).forEach((migration) => {
2090
+ this.success(this.app.splitLogger("Applied", `${migration.id} @ ${migration.appliedAt}`));
2091
+ });
2092
+ }
2093
+ };
2094
+
1943
2095
  //#endregion
1944
2096
  //#region src/cli/commands/ModelsSyncCommand.ts
1945
2097
  var ModelsSyncCommand = class extends Command {
@@ -2119,7 +2271,8 @@ await Kernel.init(app, {
2119
2271
  MakeMigrationCommand,
2120
2272
  ModelsSyncCommand,
2121
2273
  SeedCommand,
2122
- MigrateCommand
2274
+ MigrateCommand,
2275
+ MigrationHistoryCommand
2123
2276
  ],
2124
2277
  exceptionHandler(exception) {
2125
2278
  throw exception;
package/dist/index.cjs CHANGED
@@ -37,6 +37,7 @@ let _h3ravel_support = require("@h3ravel/support");
37
37
  let url = require("url");
38
38
  let _h3ravel_shared = require("@h3ravel/shared");
39
39
  let _h3ravel_musket = require("@h3ravel/musket");
40
+ let node_crypto = require("node:crypto");
40
41
  let node_url = require("node:url");
41
42
  let _h3ravel_collect_js = require("@h3ravel/collect.js");
42
43
 
@@ -2031,6 +2032,63 @@ var MakeSeederCommand = class extends _h3ravel_musket.Command {
2031
2032
  }
2032
2033
  };
2033
2034
 
2035
+ //#endregion
2036
+ //#region src/helpers/migration-history.ts
2037
+ const DEFAULT_STATE = {
2038
+ version: 1,
2039
+ migrations: []
2040
+ };
2041
+ const resolveMigrationStateFilePath = (cwd, configuredPath) => {
2042
+ if (configuredPath && configuredPath.trim().length > 0) return (0, node_path.resolve)(configuredPath);
2043
+ return (0, node_path.join)(cwd, ".arkormx", "migrations.applied.json");
2044
+ };
2045
+ const buildMigrationIdentity = (filePath, className) => {
2046
+ const fileName = filePath.split("/").pop()?.split("\\").pop() ?? filePath;
2047
+ return `${fileName.slice(0, fileName.length - (0, node_path.extname)(fileName).length)}:${className}`;
2048
+ };
2049
+ const computeMigrationChecksum = (filePath) => {
2050
+ const source = (0, node_fs.readFileSync)(filePath, "utf-8");
2051
+ return (0, node_crypto.createHash)("sha256").update(source).digest("hex");
2052
+ };
2053
+ const readAppliedMigrationsState = (stateFilePath) => {
2054
+ if (!(0, node_fs.existsSync)(stateFilePath)) return { ...DEFAULT_STATE };
2055
+ try {
2056
+ const parsed = JSON.parse((0, node_fs.readFileSync)(stateFilePath, "utf-8"));
2057
+ if (!Array.isArray(parsed.migrations)) return { ...DEFAULT_STATE };
2058
+ return {
2059
+ version: 1,
2060
+ migrations: parsed.migrations.filter((migration) => {
2061
+ 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");
2062
+ })
2063
+ };
2064
+ } catch {
2065
+ return { ...DEFAULT_STATE };
2066
+ }
2067
+ };
2068
+ const writeAppliedMigrationsState = (stateFilePath, state) => {
2069
+ const directory = (0, node_path.dirname)(stateFilePath);
2070
+ if (!(0, node_fs.existsSync)(directory)) (0, node_fs.mkdirSync)(directory, { recursive: true });
2071
+ (0, node_fs.writeFileSync)(stateFilePath, JSON.stringify(state, null, 2));
2072
+ };
2073
+ const isMigrationApplied = (state, identity, checksum) => {
2074
+ const matched = state.migrations.find((migration) => migration.id === identity);
2075
+ if (!matched) return false;
2076
+ if (checksum && matched.checksum) return matched.checksum === checksum;
2077
+ if (checksum && !matched.checksum) return false;
2078
+ return true;
2079
+ };
2080
+ const findAppliedMigration = (state, identity) => {
2081
+ return state.migrations.find((migration) => migration.id === identity);
2082
+ };
2083
+ const markMigrationApplied = (state, entry) => {
2084
+ const next = state.migrations.filter((migration) => migration.id !== entry.id);
2085
+ next.push(entry);
2086
+ return {
2087
+ version: 1,
2088
+ migrations: next
2089
+ };
2090
+ };
2091
+
2034
2092
  //#endregion
2035
2093
  //#region src/database/Migration.ts
2036
2094
  const MIGRATION_BRAND = Symbol.for("arkormx.migration");
@@ -2062,6 +2120,7 @@ var MigrateCommand = class extends _h3ravel_musket.Command {
2062
2120
  {--deploy : Use prisma migrate deploy instead of migrate dev}
2063
2121
  {--skip-generate : Skip prisma generate}
2064
2122
  {--skip-migrate : Skip prisma migrate command}
2123
+ {--state-file= : Path to applied migration state file}
2065
2124
  {--schema= : Explicit prisma schema path}
2066
2125
  {--migration-name= : Name for prisma migrate dev}
2067
2126
  `;
@@ -2083,10 +2142,47 @@ var MigrateCommand = class extends _h3ravel_musket.Command {
2083
2142
  const schemaPath = this.option("schema") ? (0, node_path.resolve)(String(this.option("schema"))) : (0, node_path.join)(process.cwd(), "prisma", "schema.prisma");
2084
2143
  const classes = this.option("all") || !this.argument("name") ? await this.loadAllMigrations(migrationsDir) : (await this.loadNamedMigration(migrationsDir, this.argument("name"))).filter(([cls]) => cls !== void 0);
2085
2144
  if (classes.length === 0) return void this.error("Error: No migration classes found to run.");
2086
- for (const [MigrationClassItem] of classes) await applyMigrationToPrismaSchema(MigrationClassItem, {
2145
+ const shouldTrackApplied = Boolean(this.option("all") || !this.argument("name") || this.option("state-file"));
2146
+ const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
2147
+ let appliedState = shouldTrackApplied ? readAppliedMigrationsState(stateFilePath) : void 0;
2148
+ const skipped = [];
2149
+ const changed = [];
2150
+ const pending = classes.filter(([migrationClass, file]) => {
2151
+ if (!appliedState) return true;
2152
+ const identity = buildMigrationIdentity(file, migrationClass.name);
2153
+ const checksum = computeMigrationChecksum(file);
2154
+ const alreadyApplied = isMigrationApplied(appliedState, identity, checksum);
2155
+ if (alreadyApplied) skipped.push([migrationClass, file]);
2156
+ else if (findAppliedMigration(appliedState, identity)) changed.push([migrationClass, file]);
2157
+ return !alreadyApplied;
2158
+ });
2159
+ skipped.forEach(([migrationClass, file]) => {
2160
+ this.success(this.app.splitLogger("Skipped", `${file} (${migrationClass.name})`));
2161
+ });
2162
+ changed.forEach(([migrationClass, file]) => {
2163
+ this.success(this.app.splitLogger("Changed", `${file} (${migrationClass.name})`));
2164
+ });
2165
+ if (pending.length === 0) {
2166
+ this.success("No pending migration classes to apply.");
2167
+ return;
2168
+ }
2169
+ for (const [MigrationClassItem] of pending) await applyMigrationToPrismaSchema(MigrationClassItem, {
2087
2170
  schemaPath,
2088
2171
  write: true
2089
2172
  });
2173
+ if (appliedState) {
2174
+ for (const [migrationClass, file] of pending) {
2175
+ const identity = buildMigrationIdentity(file, migrationClass.name);
2176
+ appliedState = markMigrationApplied(appliedState, {
2177
+ id: identity,
2178
+ file,
2179
+ className: migrationClass.name,
2180
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
2181
+ checksum: computeMigrationChecksum(file)
2182
+ });
2183
+ }
2184
+ writeAppliedMigrationsState(stateFilePath, appliedState);
2185
+ }
2090
2186
  if (!this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
2091
2187
  if (!this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
2092
2188
  else runPrismaCommand([
@@ -2095,8 +2191,8 @@ var MigrateCommand = class extends _h3ravel_musket.Command {
2095
2191
  "--name",
2096
2192
  this.option("migration-name") ? String(this.option("migration-name")) : `arkorm_cli_${Date.now()}`
2097
2193
  ], process.cwd());
2098
- this.success(`Applied ${classes.length} migration(s).`);
2099
- classes.forEach(([_, file]) => this.success(this.app.splitLogger("Migrated", file)));
2194
+ this.success(`Applied ${pending.length} migration(s).`);
2195
+ pending.forEach(([_, file]) => this.success(this.app.splitLogger("Migrated", file)));
2100
2196
  }
2101
2197
  /**
2102
2198
  * Load all migration classes from the specified directory.
@@ -2148,6 +2244,62 @@ var MigrateCommand = class extends _h3ravel_musket.Command {
2148
2244
  }
2149
2245
  };
2150
2246
 
2247
+ //#endregion
2248
+ //#region src/cli/commands/MigrationHistoryCommand.ts
2249
+ /**
2250
+ * The MigrationHistoryCommand class manages tracked migration run history.
2251
+ *
2252
+ * @author Legacy (3m1n3nc3)
2253
+ * @since 0.2.4
2254
+ */
2255
+ var MigrationHistoryCommand = class extends _h3ravel_musket.Command {
2256
+ signature = `migrate:history
2257
+ {--state-file= : Path to applied migration state file}
2258
+ {--reset : Clear tracked migration history file}
2259
+ {--delete : Delete tracked migration history file}
2260
+ {--json : Print raw JSON output}
2261
+ `;
2262
+ description = "Inspect or reset tracked migration history";
2263
+ async handle() {
2264
+ this.app.command = this;
2265
+ const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
2266
+ if (this.option("delete")) {
2267
+ if (!(0, node_fs.existsSync)(stateFilePath)) {
2268
+ this.success(`No migration state file found at ${this.app.formatPathForLog(stateFilePath)}`);
2269
+ return;
2270
+ }
2271
+ (0, node_fs.rmSync)(stateFilePath);
2272
+ this.success(`Deleted migration state file: ${this.app.formatPathForLog(stateFilePath)}`);
2273
+ return;
2274
+ }
2275
+ if (this.option("reset")) {
2276
+ writeAppliedMigrationsState(stateFilePath, {
2277
+ version: 1,
2278
+ migrations: []
2279
+ });
2280
+ this.success(`Reset migration state: ${this.app.formatPathForLog(stateFilePath)}`);
2281
+ return;
2282
+ }
2283
+ const state = readAppliedMigrationsState(stateFilePath);
2284
+ if (this.option("json")) {
2285
+ this.success(JSON.stringify({
2286
+ path: stateFilePath,
2287
+ ...state
2288
+ }, null, 2));
2289
+ return;
2290
+ }
2291
+ this.success(this.app.splitLogger("State", stateFilePath));
2292
+ this.success(this.app.splitLogger("Tracked", String(state.migrations.length)));
2293
+ if (state.migrations.length === 0) {
2294
+ this.success("No tracked migrations found.");
2295
+ return;
2296
+ }
2297
+ state.migrations.sort((left, right) => left.appliedAt.localeCompare(right.appliedAt)).forEach((migration) => {
2298
+ this.success(this.app.splitLogger("Applied", `${migration.id} @ ${migration.appliedAt}`));
2299
+ });
2300
+ }
2301
+ };
2302
+
2151
2303
  //#endregion
2152
2304
  //#region src/cli/commands/ModelsSyncCommand.ts
2153
2305
  var ModelsSyncCommand = class extends _h3ravel_musket.Command {
@@ -5130,6 +5282,7 @@ exports.MakeModelCommand = MakeModelCommand;
5130
5282
  exports.MakeSeederCommand = MakeSeederCommand;
5131
5283
  exports.MigrateCommand = MigrateCommand;
5132
5284
  exports.Migration = Migration;
5285
+ exports.MigrationHistoryCommand = MigrationHistoryCommand;
5133
5286
  exports.Model = Model;
5134
5287
  exports.ModelFactory = ModelFactory;
5135
5288
  exports.ModelNotFoundException = ModelNotFoundException;
@@ -5151,9 +5304,11 @@ exports.applyOperationsToPrismaSchema = applyOperationsToPrismaSchema;
5151
5304
  exports.buildFieldLine = buildFieldLine;
5152
5305
  exports.buildIndexLine = buildIndexLine;
5153
5306
  exports.buildInverseRelationLine = buildInverseRelationLine;
5307
+ exports.buildMigrationIdentity = buildMigrationIdentity;
5154
5308
  exports.buildMigrationSource = buildMigrationSource;
5155
5309
  exports.buildModelBlock = buildModelBlock;
5156
5310
  exports.buildRelationLine = buildRelationLine;
5311
+ exports.computeMigrationChecksum = computeMigrationChecksum;
5157
5312
  exports.configureArkormRuntime = configureArkormRuntime;
5158
5313
  exports.createMigrationTimestamp = createMigrationTimestamp;
5159
5314
  exports.createPrismaAdapter = createPrismaAdapter;
@@ -5165,6 +5320,7 @@ exports.deriveInverseRelationAlias = deriveInverseRelationAlias;
5165
5320
  exports.deriveRelationFieldName = deriveRelationFieldName;
5166
5321
  exports.ensureArkormConfigLoading = ensureArkormConfigLoading;
5167
5322
  exports.escapeRegex = escapeRegex;
5323
+ exports.findAppliedMigration = findAppliedMigration;
5168
5324
  exports.findModelBlock = findModelBlock;
5169
5325
  exports.formatDefaultValue = formatDefaultValue;
5170
5326
  exports.formatRelationAction = formatRelationAction;
@@ -5176,13 +5332,18 @@ exports.getRuntimePrismaClient = getRuntimePrismaClient;
5176
5332
  exports.getUserConfig = getUserConfig;
5177
5333
  exports.inferDelegateName = inferDelegateName;
5178
5334
  exports.isDelegateLike = isDelegateLike;
5335
+ exports.isMigrationApplied = isMigrationApplied;
5179
5336
  exports.loadArkormConfig = loadArkormConfig;
5337
+ exports.markMigrationApplied = markMigrationApplied;
5180
5338
  exports.pad = pad;
5339
+ exports.readAppliedMigrationsState = readAppliedMigrationsState;
5181
5340
  exports.resetArkormRuntimeForTests = resetArkormRuntimeForTests;
5182
5341
  exports.resolveCast = resolveCast;
5183
5342
  exports.resolveMigrationClassName = resolveMigrationClassName;
5343
+ exports.resolveMigrationStateFilePath = resolveMigrationStateFilePath;
5184
5344
  exports.resolvePrismaType = resolvePrismaType;
5185
5345
  exports.runMigrationWithPrisma = runMigrationWithPrisma;
5186
5346
  exports.runPrismaCommand = runPrismaCommand;
5187
5347
  exports.toMigrationFileSlug = toMigrationFileSlug;
5188
- exports.toModelName = toModelName;
5348
+ exports.toModelName = toModelName;
5349
+ exports.writeAppliedMigrationsState = writeAppliedMigrationsState;
package/dist/index.d.cts CHANGED
@@ -79,6 +79,17 @@ interface PrismaMigrationWorkflowOptions extends PrismaSchemaSyncOptions {
79
79
  migrateMode?: 'dev' | 'deploy';
80
80
  migrationName?: string;
81
81
  }
82
+ interface AppliedMigrationEntry {
83
+ id: string;
84
+ file: string;
85
+ className: string;
86
+ appliedAt: string;
87
+ checksum?: string;
88
+ }
89
+ interface AppliedMigrationsState {
90
+ version: 1;
91
+ migrations: AppliedMigrationEntry[];
92
+ }
82
93
  //#endregion
83
94
  //#region src/Collection.d.ts
84
95
  declare class ArkormCollection<T = any, X = T[]> extends Collection<T, X> {}
@@ -2251,6 +2262,19 @@ declare class MigrateCommand extends Command<CliApp> {
2251
2262
  private loadMigrationClassesFromFile;
2252
2263
  }
2253
2264
  //#endregion
2265
+ //#region src/cli/commands/MigrationHistoryCommand.d.ts
2266
+ /**
2267
+ * The MigrationHistoryCommand class manages tracked migration run history.
2268
+ *
2269
+ * @author Legacy (3m1n3nc3)
2270
+ * @since 0.2.4
2271
+ */
2272
+ declare class MigrationHistoryCommand extends Command<CliApp> {
2273
+ protected signature: string;
2274
+ protected description: string;
2275
+ handle(): Promise<void>;
2276
+ }
2277
+ //#endregion
2254
2278
  //#region src/cli/commands/ModelsSyncCommand.d.ts
2255
2279
  declare class ModelsSyncCommand extends Command<CliApp> {
2256
2280
  protected signature: string;
@@ -2731,6 +2755,16 @@ declare class ModelNotFoundException extends ArkormException {
2731
2755
  getModelName(): string;
2732
2756
  }
2733
2757
  //#endregion
2758
+ //#region src/helpers/migration-history.d.ts
2759
+ declare const resolveMigrationStateFilePath: (cwd: string, configuredPath?: string) => string;
2760
+ declare const buildMigrationIdentity: (filePath: string, className: string) => string;
2761
+ declare const computeMigrationChecksum: (filePath: string) => string;
2762
+ declare const readAppliedMigrationsState: (stateFilePath: string) => AppliedMigrationsState;
2763
+ declare const writeAppliedMigrationsState: (stateFilePath: string, state: AppliedMigrationsState) => void;
2764
+ declare const isMigrationApplied: (state: AppliedMigrationsState, identity: string, checksum?: string) => boolean;
2765
+ declare const findAppliedMigration: (state: AppliedMigrationsState, identity: string) => AppliedMigrationEntry | undefined;
2766
+ declare const markMigrationApplied: (state: AppliedMigrationsState, entry: AppliedMigrationEntry) => AppliedMigrationsState;
2767
+ //#endregion
2734
2768
  //#region src/helpers/migrations.d.ts
2735
2769
  declare const PRISMA_MODEL_REGEX: RegExp;
2736
2770
  /**
@@ -3080,4 +3114,4 @@ declare class URLDriver {
3080
3114
  url(page: number): string;
3081
3115
  }
3082
3116
  //#endregion
3083
- export { ArkormCollection, ArkormException, CliApp, ForeignKeyBuilder, InitCommand, InlineFactory, LengthAwarePaginator, MIGRATION_BRAND, MakeFactoryCommand, MakeMigrationCommand, MakeModelCommand, MakeSeederCommand, MigrateCommand, Migration, Model, ModelFactory, ModelNotFoundException, ModelsSyncCommand, PRISMA_MODEL_REGEX, Paginator, PrismaDelegateMap, QueryBuilder, SEEDER_BRAND, SchemaBuilder, SeedCommand, Seeder, SeederCallArgument, SeederConstructor, SeederInput, TableBuilder, URLDriver, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationToPrismaSchema, applyOperationsToPrismaSchema, buildFieldLine, buildIndexLine, buildInverseRelationLine, buildMigrationSource, buildModelBlock, buildRelationLine, configureArkormRuntime, createMigrationTimestamp, createPrismaAdapter, createPrismaDelegateMap, defineConfig, defineFactory, deriveCollectionFieldName, deriveInverseRelationAlias, deriveRelationFieldName, ensureArkormConfigLoading, escapeRegex, findModelBlock, formatDefaultValue, formatRelationAction, generateMigrationFile, getDefaultStubsPath, getMigrationPlan, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, loadArkormConfig, pad, resetArkormRuntimeForTests, resolveCast, resolveMigrationClassName, resolvePrismaType, runMigrationWithPrisma, runPrismaCommand, toMigrationFileSlug, toModelName };
3117
+ export { ArkormCollection, ArkormException, CliApp, ForeignKeyBuilder, InitCommand, InlineFactory, LengthAwarePaginator, MIGRATION_BRAND, MakeFactoryCommand, MakeMigrationCommand, MakeModelCommand, MakeSeederCommand, MigrateCommand, Migration, MigrationHistoryCommand, Model, ModelFactory, ModelNotFoundException, ModelsSyncCommand, PRISMA_MODEL_REGEX, Paginator, PrismaDelegateMap, QueryBuilder, SEEDER_BRAND, SchemaBuilder, SeedCommand, Seeder, SeederCallArgument, SeederConstructor, SeederInput, TableBuilder, URLDriver, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationToPrismaSchema, applyOperationsToPrismaSchema, buildFieldLine, buildIndexLine, buildInverseRelationLine, buildMigrationIdentity, buildMigrationSource, buildModelBlock, buildRelationLine, computeMigrationChecksum, configureArkormRuntime, createMigrationTimestamp, createPrismaAdapter, createPrismaDelegateMap, defineConfig, defineFactory, deriveCollectionFieldName, deriveInverseRelationAlias, deriveRelationFieldName, ensureArkormConfigLoading, escapeRegex, findAppliedMigration, findModelBlock, formatDefaultValue, formatRelationAction, generateMigrationFile, getDefaultStubsPath, getMigrationPlan, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, isMigrationApplied, loadArkormConfig, markMigrationApplied, pad, readAppliedMigrationsState, resetArkormRuntimeForTests, resolveCast, resolveMigrationClassName, resolveMigrationStateFilePath, resolvePrismaType, runMigrationWithPrisma, runPrismaCommand, toMigrationFileSlug, toModelName, writeAppliedMigrationsState };
package/dist/index.d.mts CHANGED
@@ -79,6 +79,17 @@ interface PrismaMigrationWorkflowOptions extends PrismaSchemaSyncOptions {
79
79
  migrateMode?: 'dev' | 'deploy';
80
80
  migrationName?: string;
81
81
  }
82
+ interface AppliedMigrationEntry {
83
+ id: string;
84
+ file: string;
85
+ className: string;
86
+ appliedAt: string;
87
+ checksum?: string;
88
+ }
89
+ interface AppliedMigrationsState {
90
+ version: 1;
91
+ migrations: AppliedMigrationEntry[];
92
+ }
82
93
  //#endregion
83
94
  //#region src/Collection.d.ts
84
95
  declare class ArkormCollection<T = any, X = T[]> extends Collection<T, X> {}
@@ -2251,6 +2262,19 @@ declare class MigrateCommand extends Command<CliApp> {
2251
2262
  private loadMigrationClassesFromFile;
2252
2263
  }
2253
2264
  //#endregion
2265
+ //#region src/cli/commands/MigrationHistoryCommand.d.ts
2266
+ /**
2267
+ * The MigrationHistoryCommand class manages tracked migration run history.
2268
+ *
2269
+ * @author Legacy (3m1n3nc3)
2270
+ * @since 0.2.4
2271
+ */
2272
+ declare class MigrationHistoryCommand extends Command<CliApp> {
2273
+ protected signature: string;
2274
+ protected description: string;
2275
+ handle(): Promise<void>;
2276
+ }
2277
+ //#endregion
2254
2278
  //#region src/cli/commands/ModelsSyncCommand.d.ts
2255
2279
  declare class ModelsSyncCommand extends Command<CliApp> {
2256
2280
  protected signature: string;
@@ -2731,6 +2755,16 @@ declare class ModelNotFoundException extends ArkormException {
2731
2755
  getModelName(): string;
2732
2756
  }
2733
2757
  //#endregion
2758
+ //#region src/helpers/migration-history.d.ts
2759
+ declare const resolveMigrationStateFilePath: (cwd: string, configuredPath?: string) => string;
2760
+ declare const buildMigrationIdentity: (filePath: string, className: string) => string;
2761
+ declare const computeMigrationChecksum: (filePath: string) => string;
2762
+ declare const readAppliedMigrationsState: (stateFilePath: string) => AppliedMigrationsState;
2763
+ declare const writeAppliedMigrationsState: (stateFilePath: string, state: AppliedMigrationsState) => void;
2764
+ declare const isMigrationApplied: (state: AppliedMigrationsState, identity: string, checksum?: string) => boolean;
2765
+ declare const findAppliedMigration: (state: AppliedMigrationsState, identity: string) => AppliedMigrationEntry | undefined;
2766
+ declare const markMigrationApplied: (state: AppliedMigrationsState, entry: AppliedMigrationEntry) => AppliedMigrationsState;
2767
+ //#endregion
2734
2768
  //#region src/helpers/migrations.d.ts
2735
2769
  declare const PRISMA_MODEL_REGEX: RegExp;
2736
2770
  /**
@@ -3080,4 +3114,4 @@ declare class URLDriver {
3080
3114
  url(page: number): string;
3081
3115
  }
3082
3116
  //#endregion
3083
- export { ArkormCollection, ArkormException, CliApp, ForeignKeyBuilder, InitCommand, InlineFactory, LengthAwarePaginator, MIGRATION_BRAND, MakeFactoryCommand, MakeMigrationCommand, MakeModelCommand, MakeSeederCommand, MigrateCommand, Migration, Model, ModelFactory, ModelNotFoundException, ModelsSyncCommand, PRISMA_MODEL_REGEX, Paginator, PrismaDelegateMap, QueryBuilder, SEEDER_BRAND, SchemaBuilder, SeedCommand, Seeder, SeederCallArgument, SeederConstructor, SeederInput, TableBuilder, URLDriver, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationToPrismaSchema, applyOperationsToPrismaSchema, buildFieldLine, buildIndexLine, buildInverseRelationLine, buildMigrationSource, buildModelBlock, buildRelationLine, configureArkormRuntime, createMigrationTimestamp, createPrismaAdapter, createPrismaDelegateMap, defineConfig, defineFactory, deriveCollectionFieldName, deriveInverseRelationAlias, deriveRelationFieldName, ensureArkormConfigLoading, escapeRegex, findModelBlock, formatDefaultValue, formatRelationAction, generateMigrationFile, getDefaultStubsPath, getMigrationPlan, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, loadArkormConfig, pad, resetArkormRuntimeForTests, resolveCast, resolveMigrationClassName, resolvePrismaType, runMigrationWithPrisma, runPrismaCommand, toMigrationFileSlug, toModelName };
3117
+ export { ArkormCollection, ArkormException, CliApp, ForeignKeyBuilder, InitCommand, InlineFactory, LengthAwarePaginator, MIGRATION_BRAND, MakeFactoryCommand, MakeMigrationCommand, MakeModelCommand, MakeSeederCommand, MigrateCommand, Migration, MigrationHistoryCommand, Model, ModelFactory, ModelNotFoundException, ModelsSyncCommand, PRISMA_MODEL_REGEX, Paginator, PrismaDelegateMap, QueryBuilder, SEEDER_BRAND, SchemaBuilder, SeedCommand, Seeder, SeederCallArgument, SeederConstructor, SeederInput, TableBuilder, URLDriver, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationToPrismaSchema, applyOperationsToPrismaSchema, buildFieldLine, buildIndexLine, buildInverseRelationLine, buildMigrationIdentity, buildMigrationSource, buildModelBlock, buildRelationLine, computeMigrationChecksum, configureArkormRuntime, createMigrationTimestamp, createPrismaAdapter, createPrismaDelegateMap, defineConfig, defineFactory, deriveCollectionFieldName, deriveInverseRelationAlias, deriveRelationFieldName, ensureArkormConfigLoading, escapeRegex, findAppliedMigration, findModelBlock, formatDefaultValue, formatRelationAction, generateMigrationFile, getDefaultStubsPath, getMigrationPlan, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, isMigrationApplied, loadArkormConfig, markMigrationApplied, pad, readAppliedMigrationsState, resetArkormRuntimeForTests, resolveCast, resolveMigrationClassName, resolveMigrationStateFilePath, resolvePrismaType, runMigrationWithPrisma, runPrismaCommand, toMigrationFileSlug, toModelName, writeAppliedMigrationsState };
package/dist/index.mjs CHANGED
@@ -1,13 +1,14 @@
1
1
  import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "fs";
2
2
  import path, { dirname, extname, join, relative } from "path";
3
3
  import { createRequire } from "module";
4
- import { existsSync as existsSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, writeFileSync as writeFileSync$1 } from "node:fs";
5
- import { join as join$1, resolve } from "node:path";
4
+ import { 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 "node:fs";
5
+ import { dirname as dirname$1, extname as extname$1, join as join$1, resolve } from "node:path";
6
6
  import { spawnSync } from "node:child_process";
7
7
  import { str } from "@h3ravel/support";
8
8
  import { fileURLToPath, pathToFileURL } from "url";
9
9
  import { Logger } from "@h3ravel/shared";
10
10
  import { Command } from "@h3ravel/musket";
11
+ import { createHash } from "node:crypto";
11
12
  import { pathToFileURL as pathToFileURL$1 } from "node:url";
12
13
  import { Collection } from "@h3ravel/collect.js";
13
14
 
@@ -2002,6 +2003,63 @@ var MakeSeederCommand = class extends Command {
2002
2003
  }
2003
2004
  };
2004
2005
 
2006
+ //#endregion
2007
+ //#region src/helpers/migration-history.ts
2008
+ const DEFAULT_STATE = {
2009
+ version: 1,
2010
+ migrations: []
2011
+ };
2012
+ const resolveMigrationStateFilePath = (cwd, configuredPath) => {
2013
+ if (configuredPath && configuredPath.trim().length > 0) return resolve(configuredPath);
2014
+ return join$1(cwd, ".arkormx", "migrations.applied.json");
2015
+ };
2016
+ const buildMigrationIdentity = (filePath, className) => {
2017
+ const fileName = filePath.split("/").pop()?.split("\\").pop() ?? filePath;
2018
+ return `${fileName.slice(0, fileName.length - extname$1(fileName).length)}:${className}`;
2019
+ };
2020
+ const computeMigrationChecksum = (filePath) => {
2021
+ const source = readFileSync$1(filePath, "utf-8");
2022
+ return createHash("sha256").update(source).digest("hex");
2023
+ };
2024
+ const readAppliedMigrationsState = (stateFilePath) => {
2025
+ if (!existsSync$1(stateFilePath)) return { ...DEFAULT_STATE };
2026
+ try {
2027
+ const parsed = JSON.parse(readFileSync$1(stateFilePath, "utf-8"));
2028
+ if (!Array.isArray(parsed.migrations)) return { ...DEFAULT_STATE };
2029
+ return {
2030
+ version: 1,
2031
+ migrations: parsed.migrations.filter((migration) => {
2032
+ 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");
2033
+ })
2034
+ };
2035
+ } catch {
2036
+ return { ...DEFAULT_STATE };
2037
+ }
2038
+ };
2039
+ const writeAppliedMigrationsState = (stateFilePath, state) => {
2040
+ const directory = dirname$1(stateFilePath);
2041
+ if (!existsSync$1(directory)) mkdirSync$1(directory, { recursive: true });
2042
+ writeFileSync$1(stateFilePath, JSON.stringify(state, null, 2));
2043
+ };
2044
+ const isMigrationApplied = (state, identity, checksum) => {
2045
+ const matched = state.migrations.find((migration) => migration.id === identity);
2046
+ if (!matched) return false;
2047
+ if (checksum && matched.checksum) return matched.checksum === checksum;
2048
+ if (checksum && !matched.checksum) return false;
2049
+ return true;
2050
+ };
2051
+ const findAppliedMigration = (state, identity) => {
2052
+ return state.migrations.find((migration) => migration.id === identity);
2053
+ };
2054
+ const markMigrationApplied = (state, entry) => {
2055
+ const next = state.migrations.filter((migration) => migration.id !== entry.id);
2056
+ next.push(entry);
2057
+ return {
2058
+ version: 1,
2059
+ migrations: next
2060
+ };
2061
+ };
2062
+
2005
2063
  //#endregion
2006
2064
  //#region src/database/Migration.ts
2007
2065
  const MIGRATION_BRAND = Symbol.for("arkormx.migration");
@@ -2033,6 +2091,7 @@ var MigrateCommand = class extends Command {
2033
2091
  {--deploy : Use prisma migrate deploy instead of migrate dev}
2034
2092
  {--skip-generate : Skip prisma generate}
2035
2093
  {--skip-migrate : Skip prisma migrate command}
2094
+ {--state-file= : Path to applied migration state file}
2036
2095
  {--schema= : Explicit prisma schema path}
2037
2096
  {--migration-name= : Name for prisma migrate dev}
2038
2097
  `;
@@ -2054,10 +2113,47 @@ var MigrateCommand = class extends Command {
2054
2113
  const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join$1(process.cwd(), "prisma", "schema.prisma");
2055
2114
  const classes = this.option("all") || !this.argument("name") ? await this.loadAllMigrations(migrationsDir) : (await this.loadNamedMigration(migrationsDir, this.argument("name"))).filter(([cls]) => cls !== void 0);
2056
2115
  if (classes.length === 0) return void this.error("Error: No migration classes found to run.");
2057
- for (const [MigrationClassItem] of classes) await applyMigrationToPrismaSchema(MigrationClassItem, {
2116
+ const shouldTrackApplied = Boolean(this.option("all") || !this.argument("name") || this.option("state-file"));
2117
+ const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
2118
+ let appliedState = shouldTrackApplied ? readAppliedMigrationsState(stateFilePath) : void 0;
2119
+ const skipped = [];
2120
+ const changed = [];
2121
+ const pending = classes.filter(([migrationClass, file]) => {
2122
+ if (!appliedState) return true;
2123
+ const identity = buildMigrationIdentity(file, migrationClass.name);
2124
+ const checksum = computeMigrationChecksum(file);
2125
+ const alreadyApplied = isMigrationApplied(appliedState, identity, checksum);
2126
+ if (alreadyApplied) skipped.push([migrationClass, file]);
2127
+ else if (findAppliedMigration(appliedState, identity)) changed.push([migrationClass, file]);
2128
+ return !alreadyApplied;
2129
+ });
2130
+ skipped.forEach(([migrationClass, file]) => {
2131
+ this.success(this.app.splitLogger("Skipped", `${file} (${migrationClass.name})`));
2132
+ });
2133
+ changed.forEach(([migrationClass, file]) => {
2134
+ this.success(this.app.splitLogger("Changed", `${file} (${migrationClass.name})`));
2135
+ });
2136
+ if (pending.length === 0) {
2137
+ this.success("No pending migration classes to apply.");
2138
+ return;
2139
+ }
2140
+ for (const [MigrationClassItem] of pending) await applyMigrationToPrismaSchema(MigrationClassItem, {
2058
2141
  schemaPath,
2059
2142
  write: true
2060
2143
  });
2144
+ if (appliedState) {
2145
+ for (const [migrationClass, file] of pending) {
2146
+ const identity = buildMigrationIdentity(file, migrationClass.name);
2147
+ appliedState = markMigrationApplied(appliedState, {
2148
+ id: identity,
2149
+ file,
2150
+ className: migrationClass.name,
2151
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
2152
+ checksum: computeMigrationChecksum(file)
2153
+ });
2154
+ }
2155
+ writeAppliedMigrationsState(stateFilePath, appliedState);
2156
+ }
2061
2157
  if (!this.option("skip-generate")) runPrismaCommand(["generate"], process.cwd());
2062
2158
  if (!this.option("skip-migrate")) if (this.option("deploy")) runPrismaCommand(["migrate", "deploy"], process.cwd());
2063
2159
  else runPrismaCommand([
@@ -2066,8 +2162,8 @@ var MigrateCommand = class extends Command {
2066
2162
  "--name",
2067
2163
  this.option("migration-name") ? String(this.option("migration-name")) : `arkorm_cli_${Date.now()}`
2068
2164
  ], process.cwd());
2069
- this.success(`Applied ${classes.length} migration(s).`);
2070
- classes.forEach(([_, file]) => this.success(this.app.splitLogger("Migrated", file)));
2165
+ this.success(`Applied ${pending.length} migration(s).`);
2166
+ pending.forEach(([_, file]) => this.success(this.app.splitLogger("Migrated", file)));
2071
2167
  }
2072
2168
  /**
2073
2169
  * Load all migration classes from the specified directory.
@@ -2119,6 +2215,62 @@ var MigrateCommand = class extends Command {
2119
2215
  }
2120
2216
  };
2121
2217
 
2218
+ //#endregion
2219
+ //#region src/cli/commands/MigrationHistoryCommand.ts
2220
+ /**
2221
+ * The MigrationHistoryCommand class manages tracked migration run history.
2222
+ *
2223
+ * @author Legacy (3m1n3nc3)
2224
+ * @since 0.2.4
2225
+ */
2226
+ var MigrationHistoryCommand = class extends Command {
2227
+ signature = `migrate:history
2228
+ {--state-file= : Path to applied migration state file}
2229
+ {--reset : Clear tracked migration history file}
2230
+ {--delete : Delete tracked migration history file}
2231
+ {--json : Print raw JSON output}
2232
+ `;
2233
+ description = "Inspect or reset tracked migration history";
2234
+ async handle() {
2235
+ this.app.command = this;
2236
+ const stateFilePath = resolveMigrationStateFilePath(process.cwd(), this.option("state-file") ? String(this.option("state-file")) : void 0);
2237
+ if (this.option("delete")) {
2238
+ if (!existsSync$1(stateFilePath)) {
2239
+ this.success(`No migration state file found at ${this.app.formatPathForLog(stateFilePath)}`);
2240
+ return;
2241
+ }
2242
+ rmSync$1(stateFilePath);
2243
+ this.success(`Deleted migration state file: ${this.app.formatPathForLog(stateFilePath)}`);
2244
+ return;
2245
+ }
2246
+ if (this.option("reset")) {
2247
+ writeAppliedMigrationsState(stateFilePath, {
2248
+ version: 1,
2249
+ migrations: []
2250
+ });
2251
+ this.success(`Reset migration state: ${this.app.formatPathForLog(stateFilePath)}`);
2252
+ return;
2253
+ }
2254
+ const state = readAppliedMigrationsState(stateFilePath);
2255
+ if (this.option("json")) {
2256
+ this.success(JSON.stringify({
2257
+ path: stateFilePath,
2258
+ ...state
2259
+ }, null, 2));
2260
+ return;
2261
+ }
2262
+ this.success(this.app.splitLogger("State", stateFilePath));
2263
+ this.success(this.app.splitLogger("Tracked", String(state.migrations.length)));
2264
+ if (state.migrations.length === 0) {
2265
+ this.success("No tracked migrations found.");
2266
+ return;
2267
+ }
2268
+ state.migrations.sort((left, right) => left.appliedAt.localeCompare(right.appliedAt)).forEach((migration) => {
2269
+ this.success(this.app.splitLogger("Applied", `${migration.id} @ ${migration.appliedAt}`));
2270
+ });
2271
+ }
2272
+ };
2273
+
2122
2274
  //#endregion
2123
2275
  //#region src/cli/commands/ModelsSyncCommand.ts
2124
2276
  var ModelsSyncCommand = class extends Command {
@@ -5087,4 +5239,4 @@ var Model = class Model {
5087
5239
  };
5088
5240
 
5089
5241
  //#endregion
5090
- export { ArkormCollection, ArkormException, CliApp, ForeignKeyBuilder, InitCommand, InlineFactory, LengthAwarePaginator, MIGRATION_BRAND, MakeFactoryCommand, MakeMigrationCommand, MakeModelCommand, MakeSeederCommand, MigrateCommand, Migration, Model, ModelFactory, ModelNotFoundException, ModelsSyncCommand, PRISMA_MODEL_REGEX, Paginator, QueryBuilder, SEEDER_BRAND, SchemaBuilder, SeedCommand, Seeder, TableBuilder, URLDriver, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationToPrismaSchema, applyOperationsToPrismaSchema, buildFieldLine, buildIndexLine, buildInverseRelationLine, buildMigrationSource, buildModelBlock, buildRelationLine, configureArkormRuntime, createMigrationTimestamp, createPrismaAdapter, createPrismaDelegateMap, defineConfig, defineFactory, deriveCollectionFieldName, deriveInverseRelationAlias, deriveRelationFieldName, ensureArkormConfigLoading, escapeRegex, findModelBlock, formatDefaultValue, formatRelationAction, generateMigrationFile, getDefaultStubsPath, getMigrationPlan, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, loadArkormConfig, pad, resetArkormRuntimeForTests, resolveCast, resolveMigrationClassName, resolvePrismaType, runMigrationWithPrisma, runPrismaCommand, toMigrationFileSlug, toModelName };
5242
+ export { ArkormCollection, ArkormException, CliApp, ForeignKeyBuilder, InitCommand, InlineFactory, LengthAwarePaginator, MIGRATION_BRAND, MakeFactoryCommand, MakeMigrationCommand, MakeModelCommand, MakeSeederCommand, MigrateCommand, Migration, MigrationHistoryCommand, Model, ModelFactory, ModelNotFoundException, ModelsSyncCommand, PRISMA_MODEL_REGEX, Paginator, QueryBuilder, SEEDER_BRAND, SchemaBuilder, SeedCommand, Seeder, TableBuilder, URLDriver, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationToPrismaSchema, applyOperationsToPrismaSchema, buildFieldLine, buildIndexLine, buildInverseRelationLine, buildMigrationIdentity, buildMigrationSource, buildModelBlock, buildRelationLine, computeMigrationChecksum, configureArkormRuntime, createMigrationTimestamp, createPrismaAdapter, createPrismaDelegateMap, defineConfig, defineFactory, deriveCollectionFieldName, deriveInverseRelationAlias, deriveRelationFieldName, ensureArkormConfigLoading, escapeRegex, findAppliedMigration, findModelBlock, formatDefaultValue, formatRelationAction, generateMigrationFile, getDefaultStubsPath, getMigrationPlan, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, isMigrationApplied, loadArkormConfig, markMigrationApplied, pad, readAppliedMigrationsState, resetArkormRuntimeForTests, resolveCast, resolveMigrationClassName, resolveMigrationStateFilePath, resolvePrismaType, runMigrationWithPrisma, runPrismaCommand, toMigrationFileSlug, toModelName, writeAppliedMigrationsState };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkormx",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Modern TypeScript-first ORM for Node.js.",
5
5
  "keywords": [
6
6
  "orm",