@danceroutine/tango-migrations 1.12.0 → 1.12.1

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.
@@ -129,6 +129,13 @@ async function connectDbClient(db, dialect) {
129
129
  }
130
130
  throw new Error(`Unsupported dialect: ${dialect}`);
131
131
  }
132
+ async function closeCliDbClientSafely(dbClient) {
133
+ try {
134
+ await dbClient.close();
135
+ } catch (closeError) {
136
+ logger.warn(`Unable to close database client: ${closeError instanceof Error ? closeError.message : String(closeError)}`);
137
+ }
138
+ }
132
139
  async function ensureSqliteParentDirectory(filename) {
133
140
  if (filename === ":memory:" || filename === "file::memory:") return;
134
141
  const directory = dirname(filename);
@@ -181,9 +188,16 @@ function registerMigrationsCommands(yargsBuilder) {
181
188
  }
182
189
  if (!resolved.db) throw new Error("No database target provided. Pass --db or define db settings in tango.config.ts.");
183
190
  const dbClient = await connectDbClient(resolved.db, resolved.dialect);
184
- await new MigrationRunner(dbClient, resolved.dialect, resolved.dir).apply(argv.to);
185
- await dbClient.close();
186
- logger.info("Migrations applied successfully");
191
+ let error;
192
+ try {
193
+ await new MigrationRunner(dbClient, resolved.dialect, resolved.dir).apply(argv.to);
194
+ logger.info("Migrations applied successfully");
195
+ } catch (e) {
196
+ error = e;
197
+ } finally {
198
+ await closeCliDbClientSafely(dbClient);
199
+ }
200
+ if (error) throw error;
187
201
  }).command("make:migrations", "Generate migration file by comparing models to database", (builder) => builder.option("dir", {
188
202
  type: "string",
189
203
  describe: "Migrations directory"
@@ -312,16 +326,23 @@ function registerMigrationsCommands(yargsBuilder) {
312
326
  });
313
327
  if (!resolved.db) throw new Error("No database target provided. Pass --db or define db settings in tango.config.ts.");
314
328
  const dbClient = await connectDbClient(resolved.db, resolved.dialect);
315
- const statuses = await new MigrationRunner(dbClient, resolved.dialect, resolved.dir).status();
316
- if (statuses.length === 0) logger.info("No migrations found");
317
- else statuses.forEach((statusItem) => {
318
- const marker = statusItem.applied ? "[x]" : "[ ]";
319
- logger.info(` ${marker} ${statusItem.id}`);
320
- });
321
- await dbClient.close();
329
+ let error;
330
+ try {
331
+ const statuses = await new MigrationRunner(dbClient, resolved.dialect, resolved.dir).status();
332
+ if (statuses.length === 0) logger.info("No migrations found");
333
+ else statuses.forEach((statusItem) => {
334
+ const marker = statusItem.applied ? "[x]" : "[ ]";
335
+ logger.info(` ${marker} ${statusItem.id}`);
336
+ });
337
+ } catch (e) {
338
+ error = e;
339
+ } finally {
340
+ await closeCliDbClientSafely(dbClient);
341
+ }
342
+ if (error) throw error;
322
343
  });
323
344
  }
324
345
  //#endregion
325
346
  export { registerMigrationsCommands as t };
326
347
 
327
- //# sourceMappingURL=cli-Dhp2BZsB.js.map
348
+ //# sourceMappingURL=cli-CB5Z7Ptr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-CB5Z7Ptr.js","names":["fsConstants"],"sources":["../src/commands/cli.ts"],"sourcesContent":["import { access, mkdir } from 'node:fs/promises';\nimport { constants as fsConstants } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport type { Argv } from 'yargs';\nimport { MigrationRunner } from '../runner/MigrationRunner';\nimport { MigrationGenerator } from '../generator/MigrationGenerator';\nimport { buildMigrationModelMetadataProjection, diffSchema } from '../diff/index';\nimport type { DbSchema } from '../introspect/PostgresIntrospector';\nimport type { Dialect } from '../domain/Dialect';\nimport type { ColumnType } from '../builder/contracts/ColumnType';\nimport type { DeleteReferentialAction } from '../builder/contracts/DeleteReferentialAction';\nimport type { UpdateReferentialAction } from '../builder/contracts/UpdateReferentialAction';\nimport { createDefaultIntrospectorStrategy } from '../strategies/IntrospectorStrategy';\nimport { InternalDialect } from '../domain/internal/InternalDialect';\nimport { loadConfig } from '@danceroutine/tango-config';\nimport { loadProjectModule } from '@danceroutine/tango-codegen/commands';\nimport { writeRelationRegistryArtifacts } from '@danceroutine/tango-codegen/generators';\nimport { getLogger } from '@danceroutine/tango-core';\nimport { GENERATED_RELATION_REGISTRY_DIRNAME, ModelRegistry } from '@danceroutine/tango-schema';\nimport { loadModule } from '../runtime/loadModule';\n\nconst logger = getLogger('tango.migrations');\n\ntype ConfigEnvironment = 'development' | 'test' | 'production';\n\ntype OptionalMigrationDefaults = {\n dialect?: Dialect;\n db?: string;\n dir?: string;\n autoApply?: boolean;\n};\n\ntype ModelMetadataLike = {\n table: string;\n fields: Array<{\n name: string;\n type: ColumnType;\n notNull?: boolean;\n default?: string | { now: true } | null;\n primaryKey?: boolean;\n unique?: boolean;\n references?: {\n table: string;\n column: string;\n onDelete?: DeleteReferentialAction;\n onUpdate?: UpdateReferentialAction;\n };\n }>;\n indexes?: Array<{ name: string; on: string[]; unique?: boolean }>;\n};\n\ntype CliDbClient = {\n query<T = unknown>(sql: string, params?: readonly unknown[]): Promise<{ rows: T[] }>;\n close(): Promise<void>;\n};\n\ntype LoadedModelsResult = {\n models: ModelMetadataLike[];\n registry: ModelRegistry;\n modelTypeAccessors: Record<string, string>;\n};\n\ntype ModelContainerLike = {\n metadata: ModelMetadataLike;\n};\n\nasync function importModule(modulePath: string): Promise<Record<string, unknown>> {\n return loadModule(modulePath, { projectRoot: process.cwd() });\n}\n\nasync function tryLoadMigrationDefaults(\n configPathArg: string | undefined,\n configEnvArg: ConfigEnvironment | undefined\n): Promise<OptionalMigrationDefaults> {\n const explicitConfigPath = typeof configPathArg === 'string' && configPathArg.trim().length > 0;\n const resolvedPath = resolve(process.cwd(), configPathArg?.trim() || './tango.config.ts');\n\n try {\n await access(resolvedPath, fsConstants.F_OK);\n } catch (error) {\n if (explicitConfigPath) {\n throw new Error(`Config file not found: ${resolvedPath}`, { cause: error });\n }\n return {};\n }\n\n const module = await importModule(resolvedPath);\n const fileConfig = (module.default ?? module) as { current?: ConfigEnvironment } & Record<string, unknown>;\n\n const loaded = loadConfig(() => ({\n ...fileConfig,\n ...(configEnvArg ? { current: configEnvArg } : {}),\n }));\n\n const { db, migrations } = loaded.current;\n const inferredDialect = db.adapter as Dialect;\n const inferredDb = resolveDbTarget(db);\n\n return {\n dialect: inferredDialect,\n db: inferredDb,\n dir: migrations.dir,\n autoApply: (migrations as { autoApply?: boolean }).autoApply,\n };\n}\n\nfunction resolveDbTarget(db: {\n adapter: string;\n url?: string;\n filename?: string;\n host?: string;\n port?: number;\n database?: string;\n user?: string;\n password?: string;\n}): string | undefined {\n if (db.adapter === InternalDialect.SQLITE) {\n return db.filename ?? db.url;\n }\n\n if (db.url) {\n return db.url;\n }\n\n if (!db.database) {\n return undefined;\n }\n\n const host = db.host ?? 'localhost';\n const port = db.port ?? 5432;\n const encodedUser = db.user ? encodeURIComponent(db.user) : '';\n const encodedPassword = db.password ? encodeURIComponent(db.password) : '';\n const userInfo =\n encodedUser.length > 0\n ? encodedPassword.length > 0\n ? `${encodedUser}:${encodedPassword}@`\n : `${encodedUser}@`\n : '';\n\n return `postgres://${userInfo}${host}:${String(port)}/${db.database}`;\n}\n\nasync function resolveCommandInputs(argv: {\n dialect?: string;\n dir?: string;\n db?: string;\n config?: string;\n env?: ConfigEnvironment;\n}): Promise<{ dialect: Dialect; dir: string; db?: string; autoApply: boolean }> {\n const defaults = await tryLoadMigrationDefaults(argv.config, argv.env);\n\n const resolvedDialect = (argv.dialect as Dialect | undefined) ?? defaults.dialect ?? InternalDialect.POSTGRES;\n const resolvedDir = argv.dir ?? defaults.dir ?? 'migrations';\n const resolvedDb = argv.db ?? defaults.db;\n\n return {\n dialect: resolvedDialect,\n dir: resolvedDir,\n db: resolvedDb,\n autoApply: defaults.autoApply ?? true,\n };\n}\n\nfunction isModelContainerLike(value: unknown): value is ModelContainerLike {\n return typeof value === 'object' && value !== null && 'metadata' in value;\n}\n\nfunction collectExportedModels(moduleValue: unknown): ModelMetadataLike[] {\n if (!moduleValue || typeof moduleValue !== 'object') {\n return [];\n }\n\n const models: ModelMetadataLike[] = [];\n for (const value of Object.values(moduleValue as Record<string, unknown>)) {\n if (isModelContainerLike(value)) {\n models.push(value.metadata);\n continue;\n }\n\n if (!value || typeof value !== 'object') {\n continue;\n }\n\n for (const nestedValue of Object.values(value as Record<string, unknown>)) {\n if (isModelContainerLike(nestedValue)) {\n models.push(nestedValue.metadata);\n }\n }\n }\n\n return models;\n}\n\nasync function loadModels(modelsPath: string): Promise<LoadedModelsResult> {\n const {\n loaded: mod,\n registry,\n modelTypeAccessors,\n } = await loadProjectModule(modelsPath, {\n projectRoot: process.cwd(),\n outputDir: resolve(process.cwd(), GENERATED_RELATION_REGISTRY_DIRNAME),\n });\n const moduleValue = (mod.default ?? mod) as unknown;\n\n const models = isModelContainerLike(moduleValue)\n ? [moduleValue.metadata, ...collectExportedModels(moduleValue)]\n : collectExportedModels(moduleValue);\n\n if (models.length === 0) {\n throw new Error(`No models found in '${modelsPath}'. Ensure the module exports Model() definitions.`);\n }\n\n return {\n models,\n registry,\n modelTypeAccessors,\n };\n}\n\nasync function connectAndIntrospect(dbUrl: string, dialect: string): Promise<DbSchema> {\n const dbClient = await connectDbClient(dbUrl, dialect as Dialect);\n\n try {\n const strategy = createDefaultIntrospectorStrategy();\n return await strategy.introspect(dialect as Dialect, dbClient);\n } finally {\n await dbClient.close();\n }\n}\n\nasync function connectDbClient(db: string, dialect: Dialect): Promise<CliDbClient> {\n if (dialect === InternalDialect.POSTGRES) {\n const pg = await import('pg');\n const client = new pg.default.Client({ connectionString: db });\n await client.connect();\n\n return {\n async query<T = unknown>(sql: string, params?: readonly unknown[]): Promise<{ rows: T[] }> {\n const result = await client.query(sql, params as unknown[] | undefined);\n return { rows: result.rows as T[] };\n },\n async close(): Promise<void> {\n await client.end();\n },\n };\n }\n\n if (dialect === InternalDialect.SQLITE) {\n const sqlite = await import('better-sqlite3');\n const DatabaseCtor = (sqlite.default ?? sqlite) as new (filename: string) => {\n prepare(sql: string): {\n all(...params: unknown[]): unknown[];\n run(...params: unknown[]): unknown;\n };\n close(): void;\n };\n\n const filename = normalizeSqliteFilename(db);\n await ensureSqliteParentDirectory(filename);\n const connection = new DatabaseCtor(filename);\n\n return {\n async query<T = unknown>(sql: string, params?: readonly unknown[]): Promise<{ rows: T[] }> {\n const statement = connection.prepare(sql);\n const values = [...(params ?? [])];\n const isSelectLike = /^\\s*(SELECT|PRAGMA|WITH)\\b/i.test(sql);\n if (isSelectLike) {\n return { rows: statement.all(...values) as T[] };\n }\n\n statement.run(...values);\n return { rows: [] as T[] };\n },\n async close(): Promise<void> {\n connection.close();\n },\n };\n }\n\n throw new Error(`Unsupported dialect: ${dialect}`);\n}\n\nasync function closeCliDbClientSafely(dbClient: CliDbClient): Promise<void> {\n try {\n await dbClient.close();\n } catch (closeError) {\n logger.warn(\n `Unable to close database client: ${closeError instanceof Error ? closeError.message : String(closeError)}`\n );\n }\n}\n\nasync function ensureSqliteParentDirectory(filename: string): Promise<void> {\n if (filename === ':memory:' || filename === 'file::memory:') {\n return;\n }\n const directory = dirname(filename);\n if (directory === '.' || directory.length === 0) {\n return;\n }\n await mkdir(directory, { recursive: true });\n}\n\nfunction normalizeSqliteFilename(db: string): string {\n if (db.startsWith('sqlite://')) {\n return db.slice('sqlite://'.length);\n }\n return db;\n}\n\n/**\n * Register Tango's migration commands on an existing yargs parser.\n */\nexport function registerMigrationsCommands(yargsBuilder: Argv): Argv {\n return yargsBuilder\n .command(\n 'migrate',\n 'Apply pending migrations to the database',\n (builder) =>\n builder\n .option('dir', {\n type: 'string',\n describe: 'Migrations directory',\n })\n .option('db', {\n type: 'string',\n describe: 'Database connection URL',\n })\n .option('dialect', {\n type: 'string',\n choices: ['postgres', 'sqlite'] as const,\n describe: 'Database dialect',\n })\n .option('config', {\n type: 'string',\n describe: 'Path to tango.config.ts (auto-loads ./tango.config.ts when present)',\n })\n .option('env', {\n type: 'string',\n choices: ['development', 'test', 'production'] as const,\n describe: 'Config environment override',\n })\n .option('to', {\n type: 'string',\n describe: 'Target migration ID (apply up to this migration)',\n }),\n async (argv) => {\n const resolved = await resolveCommandInputs({\n dialect: argv.dialect as string | undefined,\n dir: argv.dir as string | undefined,\n db: argv.db as string | undefined,\n config: argv.config as string | undefined,\n env: argv.env as ConfigEnvironment | undefined,\n });\n\n if (!resolved.autoApply) {\n logger.info('Auto-migration disabled (autoApply: false). Skipping.');\n return;\n }\n\n if (!resolved.db) {\n throw new Error('No database target provided. Pass --db or define db settings in tango.config.ts.');\n }\n\n const dbClient = await connectDbClient(resolved.db, resolved.dialect);\n\n let error: unknown;\n try {\n const runner = new MigrationRunner(dbClient, resolved.dialect, resolved.dir);\n await runner.apply(argv.to);\n logger.info('Migrations applied successfully');\n } catch (e) {\n error = e;\n } finally {\n await closeCliDbClientSafely(dbClient);\n }\n\n if (error) {\n throw error;\n }\n }\n )\n .command(\n 'make:migrations',\n 'Generate migration file by comparing models to database',\n (builder) =>\n builder\n .option('dir', {\n type: 'string',\n describe: 'Migrations directory',\n })\n .option('name', {\n type: 'string',\n demandOption: true,\n describe: 'Migration name (e.g. \"create_users\")',\n })\n .option('models', {\n type: 'string',\n demandOption: true,\n describe: 'Path to module exporting Model definitions (e.g. \"./src/models.ts\")',\n })\n .option('db', {\n type: 'string',\n describe: 'Database connection URL for introspection (omit for initial migration)',\n })\n .option('dialect', {\n type: 'string',\n choices: ['postgres', 'sqlite'] as const,\n describe: 'Database dialect',\n })\n .option('config', {\n type: 'string',\n describe: 'Path to tango.config.ts (auto-loads ./tango.config.ts when present)',\n })\n .option('env', {\n type: 'string',\n choices: ['development', 'test', 'production'] as const,\n describe: 'Config environment override',\n }),\n async (argv) => {\n const resolved = await resolveCommandInputs({\n dialect: argv.dialect as string | undefined,\n dir: argv.dir as string | undefined,\n db: argv.db as string | undefined,\n config: argv.config as string | undefined,\n env: argv.env as ConfigEnvironment | undefined,\n });\n const loaded = await loadModels(argv.models);\n const projectedModels = buildMigrationModelMetadataProjection(loaded.registry, {\n dialect: resolved.dialect,\n });\n logger.info(\n `Found ${loaded.models.length} exported model(s); ${projectedModels.length} model(s) after projection: ${projectedModels.map((m) => m.table).join(', ')}`\n );\n try {\n await writeRelationRegistryArtifacts({\n registry: loaded.registry,\n modelTypeAccessors: loaded.modelTypeAccessors,\n outputDir: resolve(process.cwd(), GENERATED_RELATION_REGISTRY_DIRNAME),\n });\n } catch (error) {\n logger.warn(\n `Unable to refresh generated relation registry during make:migrations. Continuing without updated relation artifacts: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n\n let dbState: DbSchema;\n if (resolved.db) {\n logger.info('Introspecting database...');\n dbState = await connectAndIntrospect(resolved.db, resolved.dialect);\n } else {\n dbState = { tables: {} };\n }\n\n const operations = diffSchema(dbState, projectedModels);\n\n if (operations.length === 0) {\n logger.info('No changes detected — models and database are in sync');\n return;\n }\n\n const generator = new MigrationGenerator();\n const filepath = await generator.generate({\n name: argv.name,\n operations,\n directory: resolved.dir,\n });\n\n logger.info(`Generated migration: ${filepath}`);\n logger.info(` ${operations.length} operation(s)`);\n }\n )\n .command(\n 'plan',\n 'Print migration SQL without applying',\n (builder) =>\n builder\n .option('dir', {\n type: 'string',\n describe: 'Migrations directory',\n })\n .option('dialect', {\n type: 'string',\n choices: ['postgres', 'sqlite'] as const,\n describe: 'Database dialect',\n })\n .option('config', {\n type: 'string',\n describe: 'Path to tango.config.ts (auto-loads ./tango.config.ts when present)',\n })\n .option('env', {\n type: 'string',\n choices: ['development', 'test', 'production'] as const,\n describe: 'Config environment override',\n }),\n async (argv) => {\n const resolved = await resolveCommandInputs({\n dialect: argv.dialect as string | undefined,\n dir: argv.dir as string | undefined,\n db: argv.db as string | undefined,\n config: argv.config as string | undefined,\n env: argv.env as ConfigEnvironment | undefined,\n });\n const runner = new MigrationRunner(\n { query: async () => ({ rows: [] }), close: async () => {} },\n resolved.dialect,\n resolved.dir\n );\n const output = await runner.plan();\n logger.info(output);\n }\n )\n .command(\n 'status',\n 'Show applied/pending status of all migrations',\n (builder) =>\n builder\n .option('dir', {\n type: 'string',\n describe: 'Migrations directory',\n })\n .option('db', {\n type: 'string',\n describe: 'Database connection URL',\n })\n .option('dialect', {\n type: 'string',\n choices: ['postgres', 'sqlite'] as const,\n describe: 'Database dialect',\n })\n .option('config', {\n type: 'string',\n describe: 'Path to tango.config.ts (auto-loads ./tango.config.ts when present)',\n })\n .option('env', {\n type: 'string',\n choices: ['development', 'test', 'production'] as const,\n describe: 'Config environment override',\n }),\n async (argv) => {\n const resolved = await resolveCommandInputs({\n dialect: argv.dialect as string | undefined,\n dir: argv.dir as string | undefined,\n db: argv.db as string | undefined,\n config: argv.config as string | undefined,\n env: argv.env as ConfigEnvironment | undefined,\n });\n if (!resolved.db) {\n throw new Error('No database target provided. Pass --db or define db settings in tango.config.ts.');\n }\n\n const dbClient = await connectDbClient(resolved.db, resolved.dialect);\n\n let error: unknown;\n try {\n const runner = new MigrationRunner(dbClient, resolved.dialect, resolved.dir);\n const statuses = await runner.status();\n\n if (statuses.length === 0) {\n logger.info('No migrations found');\n } else {\n statuses.forEach((statusItem) => {\n const marker = statusItem.applied ? '[x]' : '[ ]';\n logger.info(` ${marker} ${statusItem.id}`);\n });\n }\n } catch (e) {\n error = e;\n } finally {\n await closeCliDbClientSafely(dbClient);\n }\n\n if (error) {\n throw error;\n }\n }\n );\n}\n"],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as registerMigrationsCommands } from "./cli-Dhp2BZsB.js";
2
+ import { t as registerMigrationsCommands } from "./cli-CB5Z7Ptr.js";
3
3
  import yargs from "yargs";
4
4
  import { hideBin } from "yargs/helpers";
5
5
  //#region src/cli.ts
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "../chunk-D7D4PA-g.js";
2
- import { t as registerMigrationsCommands } from "../cli-Dhp2BZsB.js";
2
+ import { t as registerMigrationsCommands } from "../cli-CB5Z7Ptr.js";
3
3
  //#region src/commands/index.ts
4
4
  var commands_exports = /* @__PURE__ */ __exportAll({ registerMigrationsCommands: () => registerMigrationsCommands });
5
5
  //#endregion
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ import { n as PostgresIntrospector, t as SqliteIntrospector } from "./SqliteIntr
14
14
  import { t as introspect_exports } from "./introspect/index.js";
15
15
  import { n as createDefaultIntrospectorStrategy, t as IntrospectorStrategy } from "./IntrospectorStrategy-D_9umttn.js";
16
16
  import { t as strategies_exports } from "./strategies/index.js";
17
- import { t as registerMigrationsCommands } from "./cli-Dhp2BZsB.js";
17
+ import { t as registerMigrationsCommands } from "./cli-CB5Z7Ptr.js";
18
18
  import { t as commands_exports } from "./commands/index.js";
19
19
  import { trustedSql } from "@danceroutine/tango-core";
20
20
  export { CollectingBuilder, CompilerStrategy, IntrospectorStrategy, Migration, MigrationGenerator, MigrationRunner, OpBuilder, OpBuilder as op, PostgresCompiler, PostgresIntrospector, SqliteCompiler, SqliteIntrospector, applyFieldType, builder_exports as builder, commands_exports as commands, compilers_exports as compilers, createDefaultCompilerStrategy, createDefaultIntrospectorStrategy, diff_exports as diff, diffSchema, domain_exports as domain, generator_exports as generator, introspect_exports as introspect, registerMigrationsCommands, runner_exports as runner, strategies_exports as strategies, trustedSql };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danceroutine/tango-migrations",
3
- "version": "1.12.0",
3
+ "version": "1.12.1",
4
4
  "description": "Auto-migration system with ops DSL for Tango",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -67,10 +67,10 @@
67
67
  "jiti": "^2.7.0",
68
68
  "kleur": "^4.1.5",
69
69
  "yargs": "^18.0.0",
70
- "@danceroutine/tango-config": "1.12.0",
71
- "@danceroutine/tango-codegen": "1.12.0",
72
- "@danceroutine/tango-core": "1.12.0",
73
- "@danceroutine/tango-schema": "1.12.0"
70
+ "@danceroutine/tango-config": "1.12.1",
71
+ "@danceroutine/tango-codegen": "1.12.1",
72
+ "@danceroutine/tango-core": "1.12.1",
73
+ "@danceroutine/tango-schema": "1.12.1"
74
74
  },
75
75
  "peerDependencies": {
76
76
  "pg": "^8.20.0",
@@ -95,8 +95,8 @@
95
95
  "typescript": "^6.0.3",
96
96
  "vitest": "^4.1.8",
97
97
  "zod": "^4.0.0",
98
- "@danceroutine/tango-testing": "1.12.0",
99
- "@danceroutine/tango-orm": "1.12.0"
98
+ "@danceroutine/tango-testing": "1.12.1",
99
+ "@danceroutine/tango-orm": "1.12.1"
100
100
  },
101
101
  "scripts": {
102
102
  "build": "tsdown",
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli-Dhp2BZsB.js","names":["fsConstants"],"sources":["../src/commands/cli.ts"],"sourcesContent":["import { access, mkdir } from 'node:fs/promises';\nimport { constants as fsConstants } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport type { Argv } from 'yargs';\nimport { MigrationRunner } from '../runner/MigrationRunner';\nimport { MigrationGenerator } from '../generator/MigrationGenerator';\nimport { buildMigrationModelMetadataProjection, diffSchema } from '../diff/index';\nimport type { DbSchema } from '../introspect/PostgresIntrospector';\nimport type { Dialect } from '../domain/Dialect';\nimport type { ColumnType } from '../builder/contracts/ColumnType';\nimport type { DeleteReferentialAction } from '../builder/contracts/DeleteReferentialAction';\nimport type { UpdateReferentialAction } from '../builder/contracts/UpdateReferentialAction';\nimport { createDefaultIntrospectorStrategy } from '../strategies/IntrospectorStrategy';\nimport { InternalDialect } from '../domain/internal/InternalDialect';\nimport { loadConfig } from '@danceroutine/tango-config';\nimport { loadProjectModule } from '@danceroutine/tango-codegen/commands';\nimport { writeRelationRegistryArtifacts } from '@danceroutine/tango-codegen/generators';\nimport { getLogger } from '@danceroutine/tango-core';\nimport { GENERATED_RELATION_REGISTRY_DIRNAME, ModelRegistry } from '@danceroutine/tango-schema';\nimport { loadModule } from '../runtime/loadModule';\n\nconst logger = getLogger('tango.migrations');\n\ntype ConfigEnvironment = 'development' | 'test' | 'production';\n\ntype OptionalMigrationDefaults = {\n dialect?: Dialect;\n db?: string;\n dir?: string;\n autoApply?: boolean;\n};\n\ntype ModelMetadataLike = {\n table: string;\n fields: Array<{\n name: string;\n type: ColumnType;\n notNull?: boolean;\n default?: string | { now: true } | null;\n primaryKey?: boolean;\n unique?: boolean;\n references?: {\n table: string;\n column: string;\n onDelete?: DeleteReferentialAction;\n onUpdate?: UpdateReferentialAction;\n };\n }>;\n indexes?: Array<{ name: string; on: string[]; unique?: boolean }>;\n};\n\ntype CliDbClient = {\n query<T = unknown>(sql: string, params?: readonly unknown[]): Promise<{ rows: T[] }>;\n close(): Promise<void>;\n};\n\ntype LoadedModelsResult = {\n models: ModelMetadataLike[];\n registry: ModelRegistry;\n modelTypeAccessors: Record<string, string>;\n};\n\ntype ModelContainerLike = {\n metadata: ModelMetadataLike;\n};\n\nasync function importModule(modulePath: string): Promise<Record<string, unknown>> {\n return loadModule(modulePath, { projectRoot: process.cwd() });\n}\n\nasync function tryLoadMigrationDefaults(\n configPathArg: string | undefined,\n configEnvArg: ConfigEnvironment | undefined\n): Promise<OptionalMigrationDefaults> {\n const explicitConfigPath = typeof configPathArg === 'string' && configPathArg.trim().length > 0;\n const resolvedPath = resolve(process.cwd(), configPathArg?.trim() || './tango.config.ts');\n\n try {\n await access(resolvedPath, fsConstants.F_OK);\n } catch (error) {\n if (explicitConfigPath) {\n throw new Error(`Config file not found: ${resolvedPath}`, { cause: error });\n }\n return {};\n }\n\n const module = await importModule(resolvedPath);\n const fileConfig = (module.default ?? module) as { current?: ConfigEnvironment } & Record<string, unknown>;\n\n const loaded = loadConfig(() => ({\n ...fileConfig,\n ...(configEnvArg ? { current: configEnvArg } : {}),\n }));\n\n const { db, migrations } = loaded.current;\n const inferredDialect = db.adapter as Dialect;\n const inferredDb = resolveDbTarget(db);\n\n return {\n dialect: inferredDialect,\n db: inferredDb,\n dir: migrations.dir,\n autoApply: (migrations as { autoApply?: boolean }).autoApply,\n };\n}\n\nfunction resolveDbTarget(db: {\n adapter: string;\n url?: string;\n filename?: string;\n host?: string;\n port?: number;\n database?: string;\n user?: string;\n password?: string;\n}): string | undefined {\n if (db.adapter === InternalDialect.SQLITE) {\n return db.filename ?? db.url;\n }\n\n if (db.url) {\n return db.url;\n }\n\n if (!db.database) {\n return undefined;\n }\n\n const host = db.host ?? 'localhost';\n const port = db.port ?? 5432;\n const encodedUser = db.user ? encodeURIComponent(db.user) : '';\n const encodedPassword = db.password ? encodeURIComponent(db.password) : '';\n const userInfo =\n encodedUser.length > 0\n ? encodedPassword.length > 0\n ? `${encodedUser}:${encodedPassword}@`\n : `${encodedUser}@`\n : '';\n\n return `postgres://${userInfo}${host}:${String(port)}/${db.database}`;\n}\n\nasync function resolveCommandInputs(argv: {\n dialect?: string;\n dir?: string;\n db?: string;\n config?: string;\n env?: ConfigEnvironment;\n}): Promise<{ dialect: Dialect; dir: string; db?: string; autoApply: boolean }> {\n const defaults = await tryLoadMigrationDefaults(argv.config, argv.env);\n\n const resolvedDialect = (argv.dialect as Dialect | undefined) ?? defaults.dialect ?? InternalDialect.POSTGRES;\n const resolvedDir = argv.dir ?? defaults.dir ?? 'migrations';\n const resolvedDb = argv.db ?? defaults.db;\n\n return {\n dialect: resolvedDialect,\n dir: resolvedDir,\n db: resolvedDb,\n autoApply: defaults.autoApply ?? true,\n };\n}\n\nfunction isModelContainerLike(value: unknown): value is ModelContainerLike {\n return typeof value === 'object' && value !== null && 'metadata' in value;\n}\n\nfunction collectExportedModels(moduleValue: unknown): ModelMetadataLike[] {\n if (!moduleValue || typeof moduleValue !== 'object') {\n return [];\n }\n\n const models: ModelMetadataLike[] = [];\n for (const value of Object.values(moduleValue as Record<string, unknown>)) {\n if (isModelContainerLike(value)) {\n models.push(value.metadata);\n continue;\n }\n\n if (!value || typeof value !== 'object') {\n continue;\n }\n\n for (const nestedValue of Object.values(value as Record<string, unknown>)) {\n if (isModelContainerLike(nestedValue)) {\n models.push(nestedValue.metadata);\n }\n }\n }\n\n return models;\n}\n\nasync function loadModels(modelsPath: string): Promise<LoadedModelsResult> {\n const {\n loaded: mod,\n registry,\n modelTypeAccessors,\n } = await loadProjectModule(modelsPath, {\n projectRoot: process.cwd(),\n outputDir: resolve(process.cwd(), GENERATED_RELATION_REGISTRY_DIRNAME),\n });\n const moduleValue = (mod.default ?? mod) as unknown;\n\n const models = isModelContainerLike(moduleValue)\n ? [moduleValue.metadata, ...collectExportedModels(moduleValue)]\n : collectExportedModels(moduleValue);\n\n if (models.length === 0) {\n throw new Error(`No models found in '${modelsPath}'. Ensure the module exports Model() definitions.`);\n }\n\n return {\n models,\n registry,\n modelTypeAccessors,\n };\n}\n\nasync function connectAndIntrospect(dbUrl: string, dialect: string): Promise<DbSchema> {\n const dbClient = await connectDbClient(dbUrl, dialect as Dialect);\n\n try {\n const strategy = createDefaultIntrospectorStrategy();\n return await strategy.introspect(dialect as Dialect, dbClient);\n } finally {\n await dbClient.close();\n }\n}\n\nasync function connectDbClient(db: string, dialect: Dialect): Promise<CliDbClient> {\n if (dialect === InternalDialect.POSTGRES) {\n const pg = await import('pg');\n const client = new pg.default.Client({ connectionString: db });\n await client.connect();\n\n return {\n async query<T = unknown>(sql: string, params?: readonly unknown[]): Promise<{ rows: T[] }> {\n const result = await client.query(sql, params as unknown[] | undefined);\n return { rows: result.rows as T[] };\n },\n async close(): Promise<void> {\n await client.end();\n },\n };\n }\n\n if (dialect === InternalDialect.SQLITE) {\n const sqlite = await import('better-sqlite3');\n const DatabaseCtor = (sqlite.default ?? sqlite) as new (filename: string) => {\n prepare(sql: string): {\n all(...params: unknown[]): unknown[];\n run(...params: unknown[]): unknown;\n };\n close(): void;\n };\n\n const filename = normalizeSqliteFilename(db);\n await ensureSqliteParentDirectory(filename);\n const connection = new DatabaseCtor(filename);\n\n return {\n async query<T = unknown>(sql: string, params?: readonly unknown[]): Promise<{ rows: T[] }> {\n const statement = connection.prepare(sql);\n const values = [...(params ?? [])];\n const isSelectLike = /^\\s*(SELECT|PRAGMA|WITH)\\b/i.test(sql);\n if (isSelectLike) {\n return { rows: statement.all(...values) as T[] };\n }\n\n statement.run(...values);\n return { rows: [] as T[] };\n },\n async close(): Promise<void> {\n connection.close();\n },\n };\n }\n\n throw new Error(`Unsupported dialect: ${dialect}`);\n}\n\nasync function ensureSqliteParentDirectory(filename: string): Promise<void> {\n if (filename === ':memory:' || filename === 'file::memory:') {\n return;\n }\n const directory = dirname(filename);\n if (directory === '.' || directory.length === 0) {\n return;\n }\n await mkdir(directory, { recursive: true });\n}\n\nfunction normalizeSqliteFilename(db: string): string {\n if (db.startsWith('sqlite://')) {\n return db.slice('sqlite://'.length);\n }\n return db;\n}\n\n/**\n * Register Tango's migration commands on an existing yargs parser.\n */\nexport function registerMigrationsCommands(yargsBuilder: Argv): Argv {\n return yargsBuilder\n .command(\n 'migrate',\n 'Apply pending migrations to the database',\n (builder) =>\n builder\n .option('dir', {\n type: 'string',\n describe: 'Migrations directory',\n })\n .option('db', {\n type: 'string',\n describe: 'Database connection URL',\n })\n .option('dialect', {\n type: 'string',\n choices: ['postgres', 'sqlite'] as const,\n describe: 'Database dialect',\n })\n .option('config', {\n type: 'string',\n describe: 'Path to tango.config.ts (auto-loads ./tango.config.ts when present)',\n })\n .option('env', {\n type: 'string',\n choices: ['development', 'test', 'production'] as const,\n describe: 'Config environment override',\n })\n .option('to', {\n type: 'string',\n describe: 'Target migration ID (apply up to this migration)',\n }),\n async (argv) => {\n const resolved = await resolveCommandInputs({\n dialect: argv.dialect as string | undefined,\n dir: argv.dir as string | undefined,\n db: argv.db as string | undefined,\n config: argv.config as string | undefined,\n env: argv.env as ConfigEnvironment | undefined,\n });\n\n if (!resolved.autoApply) {\n logger.info('Auto-migration disabled (autoApply: false). Skipping.');\n return;\n }\n\n if (!resolved.db) {\n throw new Error('No database target provided. Pass --db or define db settings in tango.config.ts.');\n }\n\n const dbClient = await connectDbClient(resolved.db, resolved.dialect);\n\n const runner = new MigrationRunner(dbClient, resolved.dialect, resolved.dir);\n await runner.apply(argv.to);\n\n await dbClient.close();\n logger.info('Migrations applied successfully');\n }\n )\n .command(\n 'make:migrations',\n 'Generate migration file by comparing models to database',\n (builder) =>\n builder\n .option('dir', {\n type: 'string',\n describe: 'Migrations directory',\n })\n .option('name', {\n type: 'string',\n demandOption: true,\n describe: 'Migration name (e.g. \"create_users\")',\n })\n .option('models', {\n type: 'string',\n demandOption: true,\n describe: 'Path to module exporting Model definitions (e.g. \"./src/models.ts\")',\n })\n .option('db', {\n type: 'string',\n describe: 'Database connection URL for introspection (omit for initial migration)',\n })\n .option('dialect', {\n type: 'string',\n choices: ['postgres', 'sqlite'] as const,\n describe: 'Database dialect',\n })\n .option('config', {\n type: 'string',\n describe: 'Path to tango.config.ts (auto-loads ./tango.config.ts when present)',\n })\n .option('env', {\n type: 'string',\n choices: ['development', 'test', 'production'] as const,\n describe: 'Config environment override',\n }),\n async (argv) => {\n const resolved = await resolveCommandInputs({\n dialect: argv.dialect as string | undefined,\n dir: argv.dir as string | undefined,\n db: argv.db as string | undefined,\n config: argv.config as string | undefined,\n env: argv.env as ConfigEnvironment | undefined,\n });\n const loaded = await loadModels(argv.models);\n const projectedModels = buildMigrationModelMetadataProjection(loaded.registry, {\n dialect: resolved.dialect,\n });\n logger.info(\n `Found ${loaded.models.length} exported model(s); ${projectedModels.length} model(s) after projection: ${projectedModels.map((m) => m.table).join(', ')}`\n );\n try {\n await writeRelationRegistryArtifacts({\n registry: loaded.registry,\n modelTypeAccessors: loaded.modelTypeAccessors,\n outputDir: resolve(process.cwd(), GENERATED_RELATION_REGISTRY_DIRNAME),\n });\n } catch (error) {\n logger.warn(\n `Unable to refresh generated relation registry during make:migrations. Continuing without updated relation artifacts: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n\n let dbState: DbSchema;\n if (resolved.db) {\n logger.info('Introspecting database...');\n dbState = await connectAndIntrospect(resolved.db, resolved.dialect);\n } else {\n dbState = { tables: {} };\n }\n\n const operations = diffSchema(dbState, projectedModels);\n\n if (operations.length === 0) {\n logger.info('No changes detected — models and database are in sync');\n return;\n }\n\n const generator = new MigrationGenerator();\n const filepath = await generator.generate({\n name: argv.name,\n operations,\n directory: resolved.dir,\n });\n\n logger.info(`Generated migration: ${filepath}`);\n logger.info(` ${operations.length} operation(s)`);\n }\n )\n .command(\n 'plan',\n 'Print migration SQL without applying',\n (builder) =>\n builder\n .option('dir', {\n type: 'string',\n describe: 'Migrations directory',\n })\n .option('dialect', {\n type: 'string',\n choices: ['postgres', 'sqlite'] as const,\n describe: 'Database dialect',\n })\n .option('config', {\n type: 'string',\n describe: 'Path to tango.config.ts (auto-loads ./tango.config.ts when present)',\n })\n .option('env', {\n type: 'string',\n choices: ['development', 'test', 'production'] as const,\n describe: 'Config environment override',\n }),\n async (argv) => {\n const resolved = await resolveCommandInputs({\n dialect: argv.dialect as string | undefined,\n dir: argv.dir as string | undefined,\n db: argv.db as string | undefined,\n config: argv.config as string | undefined,\n env: argv.env as ConfigEnvironment | undefined,\n });\n const runner = new MigrationRunner(\n { query: async () => ({ rows: [] }), close: async () => {} },\n resolved.dialect,\n resolved.dir\n );\n const output = await runner.plan();\n logger.info(output);\n }\n )\n .command(\n 'status',\n 'Show applied/pending status of all migrations',\n (builder) =>\n builder\n .option('dir', {\n type: 'string',\n describe: 'Migrations directory',\n })\n .option('db', {\n type: 'string',\n describe: 'Database connection URL',\n })\n .option('dialect', {\n type: 'string',\n choices: ['postgres', 'sqlite'] as const,\n describe: 'Database dialect',\n })\n .option('config', {\n type: 'string',\n describe: 'Path to tango.config.ts (auto-loads ./tango.config.ts when present)',\n })\n .option('env', {\n type: 'string',\n choices: ['development', 'test', 'production'] as const,\n describe: 'Config environment override',\n }),\n async (argv) => {\n const resolved = await resolveCommandInputs({\n dialect: argv.dialect as string | undefined,\n dir: argv.dir as string | undefined,\n db: argv.db as string | undefined,\n config: argv.config as string | undefined,\n env: argv.env as ConfigEnvironment | undefined,\n });\n if (!resolved.db) {\n throw new Error('No database target provided. Pass --db or define db settings in tango.config.ts.');\n }\n\n const dbClient = await connectDbClient(resolved.db, resolved.dialect);\n\n const runner = new MigrationRunner(dbClient, resolved.dialect, resolved.dir);\n const statuses = await runner.status();\n\n if (statuses.length === 0) {\n logger.info('No migrations found');\n } else {\n statuses.forEach((statusItem) => {\n const marker = statusItem.applied ? '[x]' : '[ ]';\n logger.info(` ${marker} ${statusItem.id}`);\n });\n }\n\n await dbClient.close();\n }\n );\n}\n"],"mappings":"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"}