From cbbca8d0ddaad038f35b5fa64e6db1e1ccbaaa79 Mon Sep 17 00:00:00 2001 From: 3m1n3nc3 Date: Sun, 5 Jul 2026 08:17:09 +0100 Subject: [PATCH 1/3] fix(cli): load project config in fresh/rollback/seed/sync/history MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only migrate loaded the project config; the other database-touching commands read this.app.getConfig('adapter') directly. Under harnesses that hand the command a fresh CliApp without applying the config (e.g. the Arkstack CLI), the adapter was undefined — so migrate:fresh silently took the Prisma-schema path instead of resetting the configured (Kysely) database, i.e. it did not tear anything down. rollback/seed/models:sync/migrate:history had the same latent gap. Each now calls loadArkormConfig() first (idempotent; matches migrate). migrate:fresh itself is confirmed correct: adapter.resetDatabase() drops all base tables and enum types in the current schema, then every migration is re-run from a clean state. Test: migrate:fresh reaches the adapter reset path when the adapter is only reachable through arkormx.config (no configureArkormRuntime). --- src/cli/commands/MigrateFreshCommand.ts | 6 +++ src/cli/commands/MigrateRollbackCommand.ts | 4 ++ src/cli/commands/MigrationHistoryCommand.ts | 4 ++ src/cli/commands/ModelsSyncCommand.ts | 4 ++ src/cli/commands/SeedCommand.ts | 4 ++ tests/base/migrate-database.spec.ts | 50 +++++++++++++++++++++ 6 files changed, 72 insertions(+) diff --git a/src/cli/commands/MigrateFreshCommand.ts b/src/cli/commands/MigrateFreshCommand.ts index abfcbae..3f25cb7 100644 --- a/src/cli/commands/MigrateFreshCommand.ts +++ b/src/cli/commands/MigrateFreshCommand.ts @@ -31,6 +31,7 @@ import { CliApp } from '../CliApp' import { Command } from '@h3ravel/musket' import { MIGRATION_BRAND } from '../../database/Migration' import { RuntimeModuleLoader } from '../../helpers/runtime-module-loader' +import { loadArkormConfig } from '../../helpers/runtime-config' export class MigrateFreshCommand extends Command { protected signature = `migrate:fresh @@ -45,6 +46,11 @@ export class MigrateFreshCommand extends Command { async handle() { this.app.command = this + // Apply the project's arkormx.config (and its adapter) before resolving it: + // harnesses like the Arkstack CLI hand us a fresh CliApp that hasn't loaded + // the config, so without this the adapter is undefined and `migrate:fresh` + // silently falls back to the Prisma path instead of resetting the database. + await loadArkormConfig() const configuredMigrationsDir = this.app.getConfig('paths')?.migrations ?? join(process.cwd(), 'database', 'migrations') diff --git a/src/cli/commands/MigrateRollbackCommand.ts b/src/cli/commands/MigrateRollbackCommand.ts index 48cb620..33d118b 100644 --- a/src/cli/commands/MigrateRollbackCommand.ts +++ b/src/cli/commands/MigrateRollbackCommand.ts @@ -23,6 +23,7 @@ import { import { CliApp } from '../CliApp' import { Command } from '@h3ravel/musket' +import { loadArkormConfig } from '../../helpers/runtime-config' import { MIGRATION_BRAND } from '../../database/Migration' import { RuntimeModuleLoader } from '../../helpers/runtime-module-loader' @@ -49,6 +50,9 @@ export class MigrateRollbackCommand extends Command { async handle() { this.app.command = this + // Load the project config (and its adapter) before resolving it; harnesses + // may hand us a CliApp that hasn't applied the config yet. + await loadArkormConfig() const configuredMigrationsDir = this.app.getConfig('paths')?.migrations ?? join(process.cwd(), 'database', 'migrations') diff --git a/src/cli/commands/MigrationHistoryCommand.ts b/src/cli/commands/MigrationHistoryCommand.ts index 866771c..1a80b0f 100644 --- a/src/cli/commands/MigrationHistoryCommand.ts +++ b/src/cli/commands/MigrationHistoryCommand.ts @@ -13,6 +13,7 @@ import { import { CliApp } from '../CliApp' import { Command } from '@h3ravel/musket' +import { loadArkormConfig } from '../../helpers/runtime-config' /** * The MigrationHistoryCommand class manages tracked migration run history. @@ -32,6 +33,9 @@ export class MigrationHistoryCommand extends Command { async handle() { this.app.command = this + // Load the project config (and its adapter) before resolving it; harnesses + // may hand us a CliApp that hasn't applied the config yet. + await loadArkormConfig() const stateFilePath = resolveMigrationStateFilePath( process.cwd(), diff --git a/src/cli/commands/ModelsSyncCommand.ts b/src/cli/commands/ModelsSyncCommand.ts index f6d147b..511fba5 100644 --- a/src/cli/commands/ModelsSyncCommand.ts +++ b/src/cli/commands/ModelsSyncCommand.ts @@ -1,5 +1,6 @@ import { CliApp } from '../CliApp' import { Command } from '@h3ravel/musket' +import { loadArkormConfig } from '../../helpers/runtime-config' import { resolve } from 'node:path' export class ModelsSyncCommand extends Command { @@ -12,6 +13,9 @@ export class ModelsSyncCommand extends Command { async handle() { this.app.command = this + // Load the project config (and its adapter) before resolving it; harnesses + // may hand us a CliApp that hasn't applied the config yet. + await loadArkormConfig() let result diff --git a/src/cli/commands/SeedCommand.ts b/src/cli/commands/SeedCommand.ts index 6f87780..4fa054d 100644 --- a/src/cli/commands/SeedCommand.ts +++ b/src/cli/commands/SeedCommand.ts @@ -3,6 +3,7 @@ import { getRegisteredPaths, getRegisteredSeeders } from '../../helpers/runtime- import { CliApp } from '../CliApp' import { Command } from '@h3ravel/musket' +import { loadArkormConfig } from '../../helpers/runtime-config' import { RuntimeModuleLoader } from '../../helpers/runtime-module-loader' import { SEEDER_BRAND, Seeder } from '../../database/Seeder' import { join } from 'node:path' @@ -36,6 +37,9 @@ export class SeedCommand extends Command { */ async handle() { this.app.command = this + // Load the project config (and its adapter) before resolving paths/adapter; + // harnesses may hand us a CliApp that hasn't applied the config yet. + await loadArkormConfig() const configuredSeedersDir = this.app.getConfig('paths')?.seeders ?? join(process.cwd(), 'database', 'seeders') const seederDirs = this.resolveSeederDirectories(configuredSeedersDir) diff --git a/tests/base/migrate-database.spec.ts b/tests/base/migrate-database.spec.ts index 0b2e780..5a9720e 100644 --- a/tests/base/migrate-database.spec.ts +++ b/tests/base/migrate-database.spec.ts @@ -503,6 +503,56 @@ describe('database-backed migration command fallback', () => { expect(adapter.state.migrations[0]?.id).toContain('CreateUsersMigration') }) + it('migrate:fresh loads the project config to reach the adapter reset path (regression)', async () => { + const workspace = makeTempDir('arkormx-db-fresh-loadconfig-') + process.chdir(workspace) + + const migrationsDir = join(workspace, 'database', 'migrations') + mkdirSync(migrationsDir, { recursive: true }) + + const migrationBaseImport = `${originalCwd.replace(/\\/g, '/')}/src/database/Migration.ts` + writeFileSync( + join(migrationsDir, 'CreateThings.ts'), + [ + `import { Migration } from '${migrationBaseImport}'`, + 'export class CreateThings extends Migration {', + " async up (schema) { schema.createTable('things', (table) => { table.id() }) }", + " async down (schema) { schema.dropTable('things') }", + '}', + '', + ].join('\n'), + ) + + const adapter = createNoopAdapter() as DatabaseAdapter & { + resetCount: number + executed: SchemaOperation[][] + } + ;(globalThis as Record).__ARKORM_FRESH_TEST_ADAPTER__ = adapter + + // The adapter is reachable ONLY through the project config — the command must + // load it itself. Without loadArkormConfig() the adapter is undefined and + // fresh wrongly takes the Prisma path (no schema.prisma => error, no reset). + writeFileSync( + join(workspace, 'arkormx.config.cjs'), + `module.exports = { adapter: globalThis.__ARKORM_FRESH_TEST_ADAPTER__, paths: { migrations: ${JSON.stringify(migrationsDir)} } }\n`, + ) + + const app = new CliApp() + const fresh = new MigrateFreshCommand(app, new Kernel(app)) + ;(fresh as unknown as { app: CliApp }).app = app + const io = attachCommandIo(fresh as unknown as any, { + 'skip-generate': true, + 'skip-migrate': true, + }) + + await fresh.handle() + delete (globalThis as Record).__ARKORM_FRESH_TEST_ADAPTER__ + + expect(io.errorLines).toHaveLength(0) + expect(adapter.resetCount).toBe(1) + expect(adapter.executed[0]?.[0]).toMatchObject({ type: 'createTable', table: 'things' }) + }) + it('offers to create the configured database before adapter-backed fresh runs', async () => { const workspace = makeTempDir('arkormx-db-fresh-create-database-') process.chdir(workspace) From 427dcada3ee7e90dd38972efc505216b335eaddd Mon Sep 17 00:00:00 2001 From: 3m1n3nc3 Date: Sun, 5 Jul 2026 08:18:45 +0100 Subject: [PATCH 2/3] chore: bump package versions --- package.json | 4 ++-- packages/plugin-clear-router/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 2d69d02..77c4cfc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "arkormx", - "version": "2.11.2", + "version": "2.11.3", "description": "Modern TypeScript-first ORM for Node.js.", "keywords": [ "orm", @@ -114,4 +114,4 @@ "pg": "^8.19.0" }, "packageManager": "pnpm@10.0.0" -} +} \ No newline at end of file diff --git a/packages/plugin-clear-router/package.json b/packages/plugin-clear-router/package.json index 0d4a243..82ed14a 100644 --- a/packages/plugin-clear-router/package.json +++ b/packages/plugin-clear-router/package.json @@ -1,6 +1,6 @@ { "name": "@arkormx/plugin-clear-router", - "version": "0.1.50", + "version": "0.1.51", "description": "Clear Router plugin for resolving Arkormx models from route parameters.", "publishConfig": { "access": "public" From 7f61a8d713942100b7a2db8eba4953031f2cbb7c Mon Sep 17 00:00:00 2001 From: 3m1n3nc3 Date: Sun, 5 Jul 2026 08:41:27 +0100 Subject: [PATCH 3/3] chore: bump package versions --- packages/plugin-clear-router/package.json | 2 +- src/index.ts | 3 ++- src/types/index.ts | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/plugin-clear-router/package.json b/packages/plugin-clear-router/package.json index 82ed14a..2f5cdfe 100644 --- a/packages/plugin-clear-router/package.json +++ b/packages/plugin-clear-router/package.json @@ -1,6 +1,6 @@ { "name": "@arkormx/plugin-clear-router", - "version": "0.1.51", + "version": "0.1.52", "description": "Clear Router plugin for resolving Arkormx models from route parameters.", "publishConfig": { "access": "public" diff --git a/src/index.ts b/src/index.ts index baa8d6c..57a50cf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,7 +27,6 @@ export * from './database/SchemaBuilder' export * from './database/Seeder' export * from './database/TableBuilder' export * from './DB' -export * from './Expression' export * from './Exceptions/ArkormException' export * from './Exceptions/MissingDelegateException' export * from './Exceptions/ModelNotFoundException' @@ -37,6 +36,7 @@ export * from './Exceptions/RelationResolutionException' export * from './Exceptions/ScopeNotDefinedException' export * from './Exceptions/UniqueConstraintResolutionException' export * from './Exceptions/UnsupportedAdapterFeatureException' +export * from './Expression' export * from './helpers/column-mappings' export * from './helpers/generated-column' export * from './helpers/migration-history' @@ -47,6 +47,7 @@ export * from './helpers/runtime-compatibility' export * from './helpers/runtime-config' export * from './helpers/runtime-module-loader' export * from './helpers/runtime-registry' +export * from './JoinClause' export * from './Model' export * from './Paginator' export * from './PivotModel' diff --git a/src/types/index.ts b/src/types/index.ts index fefe96f..afae4a4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -7,4 +7,5 @@ export * from './metadata' export * from './migrations' export * from './model' export * from './ModelStatic' +export * from './query-builder' export * from './relationship'