@happyvertical/smrt-cli 0.42.6 → 0.43.0

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/AGENTS.md CHANGED
@@ -43,6 +43,18 @@ migrations are manifest-driven through registered objects and project manifests.
43
43
 
44
44
  ## `db:migrate` on PostgreSQL
45
45
 
46
+ New tables are created in deterministic foreign-key dependency order. Mutual
47
+ cycles are created without the cyclic clauses first, then receive named
48
+ constraints after both tables exist. When a same-package constraint is missing
49
+ on an existing table, `db:migrate` probes the exact child/parent columns for
50
+ orphans before `ADD ... NOT VALID` and `VALIDATE CONSTRAINT`; orphaned data or a
51
+ failed probe stays manual with detector/repair SQL.
52
+
53
+ `db:migrate --dry-run` and deprecated `db:setup --dry-run` print the same
54
+ engine-specific dependency plan used for execution, including exact table DDL
55
+ and deferred PostgreSQL cycle constraints; cached per-class DDL is not a valid
56
+ preview.
57
+
46
58
  `db:migrate` always bounds a PostgreSQL batch with `SET LOCAL lock_timeout` and
47
59
  `SET LOCAL statement_timeout` inside its transaction, from
48
60
  `migrations.postgres.lockTimeout` / `.statementTimeout` (defaults `30s` / `60s`;
@@ -105,6 +117,13 @@ not from the schema definition.
105
117
 
106
118
  ## `db:migrate` on SQLite: type changes rebuild the table
107
119
 
120
+ SQLite can enforce generated same-package constraints on new tables, including
121
+ cycles, but adding one to an existing table requires an explicit rebuild.
122
+ DuckDB reports unsupported constraint shapes or `ALTER ADD CONSTRAINT` as
123
+ manual/refused work, never as a successful no-op. Generated same-package
124
+ constraints retain `ON UPDATE CASCADE`, which DuckDB/JSON cannot enforce, so
125
+ those engines report an actionable refusal rather than silently stripping it.
126
+
108
127
  SQLite has no `ALTER COLUMN ... TYPE`, so a type-bucket change (the common one
109
128
  being a numeric default edited `0` → `0.0`) is applied as the documented table
110
129
  rebuild — stage, copy, drop, rename, replay indexes and triggers — planned by
@@ -5,7 +5,7 @@ import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, writeFile
5
5
  import * as path from "node:path";
6
6
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
7
7
  import { fileURLToPath, pathToFileURL } from "node:url";
8
- import { ObjectRegistry, SchemaComparer, checkLiveSchemaParity, createQualifiedName, ensureDeferredSystemTableCompatibility, generateDDLForEngine, getClassName, isQualifiedName, migratePostgresSystemTimestamps, parseQualifiedName, planPostgresSystemTimestampMigrations } from "@happyvertical/smrt-core";
8
+ import { ObjectRegistry, SchemaComparer, checkLiveSchemaParity, createQualifiedName, ensureDeferredSystemTableCompatibility, generateDDLForEngine, getClassName, isQualifiedName, migratePostgresSystemTimestamps, parseQualifiedName, planForeignKeyCreation, planPostgresSystemTimestampMigrations } from "@happyvertical/smrt-core";
9
9
  import { loadExternalManifestSync } from "@happyvertical/smrt-core/manifest";
10
10
  import { access, cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
11
11
  import { createLogger } from "@happyvertical/logger";
@@ -489,6 +489,10 @@ function getSyntheticMigrationNameForAction(action) {
489
489
  const fingerprint = sqlShapeFingerprint(action);
490
490
  return fingerprint ? `add_index_${action.index.name}_${fingerprint}` : `add_index_${action.index.name}`;
491
491
  }
492
+ case "add_foreign_key": {
493
+ const fingerprint = sqlShapeFingerprint(action);
494
+ return fingerprint ? `add_foreign_key_${action.tableName}_${fingerprint}` : null;
495
+ }
492
496
  case "drop_index": {
493
497
  if (!action.indexName) return null;
494
498
  const fingerprint = sqlShapeFingerprint(action);
@@ -517,6 +521,10 @@ function getSyntheticMigrationNameForChange(change) {
517
521
  });
518
522
  return fingerprint ? `add_index_${indexName}_${fingerprint}` : `add_index_${indexName}`;
519
523
  }
524
+ case "add_foreign_key": {
525
+ const fingerprint = sqlShapeFingerprint(change);
526
+ return fingerprint ? `add_foreign_key_${change.table}_${fingerprint}` : null;
527
+ }
520
528
  case "drop_index": {
521
529
  if (!change.name) return null;
522
530
  const fingerprint = sqlShapeFingerprint({
@@ -557,13 +565,13 @@ function shouldApplySchemaMigrations(state) {
557
565
  return !state.dryRun;
558
566
  }
559
567
  function classifyFailedMigration(migrationName, unresolvedSyntheticMigrationNames) {
560
- if (!migrationName.startsWith("add_column_") && !migrationName.startsWith("drop_column_") && !migrationName.startsWith("alter_column_") && !migrationName.startsWith("add_index_") && !migrationName.startsWith("drop_index_") && !migrationName.startsWith("type_upgrade_")) return "other";
568
+ if (!migrationName.startsWith("add_column_") && !migrationName.startsWith("drop_column_") && !migrationName.startsWith("alter_column_") && !migrationName.startsWith("add_foreign_key_") && !migrationName.startsWith("add_index_") && !migrationName.startsWith("drop_index_") && !migrationName.startsWith("type_upgrade_")) return "other";
561
569
  return unresolvedSyntheticMigrationNames.has(migrationName) ? "unresolved" : "superseded";
562
570
  }
563
571
  function getUnresolvedGeneratedMigrationNames(changes) {
564
572
  const names = /* @__PURE__ */ new Set();
565
573
  for (const change of changes) {
566
- if (change.type !== "add_column" && change.type !== "drop_column" && change.type !== "alter_column" && change.type !== "add_index" && change.type !== "drop_index" && change.type !== "type_upgrade") continue;
574
+ if (change.type !== "add_column" && change.type !== "drop_column" && change.type !== "alter_column" && change.type !== "add_foreign_key" && change.type !== "add_index" && change.type !== "drop_index" && change.type !== "type_upgrade") continue;
567
575
  if (change.type === "type_upgrade" && classifyTypeUpgradeSql(change.sql) === "noop") continue;
568
576
  if (change.type === "alter_column" && isAdvisoryOnlyChangeLike(change)) continue;
569
577
  for (const migrationName of getSyntheticMigrationNamesForChange(change)) names.add(migrationName);
@@ -706,6 +714,19 @@ function partitionSchemaChanges(changes, getClassForTable) {
706
714
  });
707
715
  break;
708
716
  }
717
+ case "add_foreign_key": {
718
+ const action = {
719
+ type: "add_foreign_key",
720
+ tableName: change.table,
721
+ className,
722
+ sql: change.sql,
723
+ ...change.sqlStatements ? { sqlStatements: change.sqlStatements } : {},
724
+ advisory: change.advisory
725
+ };
726
+ if (isAdvisoryOnlyChangeLike(change)) manualInterventions.push(action);
727
+ else migrations.push(action);
728
+ break;
729
+ }
709
730
  case "drop_index":
710
731
  if (!change.name) continue;
711
732
  migrations.push({
@@ -7752,6 +7773,15 @@ export default testManifest;
7752
7773
  schemaContract: config.schemaContract
7753
7774
  }));
7754
7775
  const initOrder = ObjectRegistry.getInitializationOrder();
7776
+ const engine = resolveDDLPreviewEngine(dbType);
7777
+ const schemas = Object.values(ObjectRegistry.getAllSchemasAsDefinitions());
7778
+ const preflightTables = new Set(schemas.map((schema) => schema.tableName));
7779
+ const missingPreflightTables = [...new Set(initOrder.flatMap((className) => {
7780
+ const tableName = ObjectRegistry.getTableName(className);
7781
+ return tableName && !preflightTables.has(tableName) ? [tableName] : [];
7782
+ }))].sort();
7783
+ if (missingPreflightTables.length > 0) throw new Error(`Cannot safely preflight database setup because structured schema definitions are missing for: ${missingPreflightTables.join(", ")}. Rebuild the package manifests before running db:setup.`);
7784
+ const plan = planForeignKeyCreation(schemas, engine);
7755
7785
  if (options.verbose) {
7756
7786
  console.log("📋 Initialization order (respecting dependencies):");
7757
7787
  for (let i = 0; i < initOrder.length; i++) {
@@ -7764,25 +7794,17 @@ export default testManifest;
7764
7794
  }
7765
7795
  if (options["dry-run"]) {
7766
7796
  console.log("📋 SQL Preview (not executed):\n");
7767
- const { generateSchema } = await import("@happyvertical/smrt-core/schema/utils");
7768
- for (const className of initOrder) {
7769
- const registered = ObjectRegistry.getClass(className);
7770
- if (!registered) continue;
7771
- const tableStrategy = ObjectRegistry.getTableStrategy(className);
7772
- const stiBase = ObjectRegistry.getSTIBase(className);
7773
- const qualifiedClassName = registered.qualifiedName ?? registered.name ?? className;
7774
- if (tableStrategy === "sti" && !!stiBase && stiBase !== qualifiedClassName && stiBase !== className) {
7775
- console.log(`-- Table: ${className} (Base: ${stiBase}, Strategy: STI)`);
7776
- console.log(`-- Shares table with ${stiBase} (STI child)\n`);
7777
- continue;
7778
- }
7779
- const schema = await generateSchema(registered.constructor, void 0, { engine: resolveDDLPreviewEngine(dbType) });
7780
- if (schema && schema.trim() !== "") {
7781
- const tableName = ObjectRegistry.getTableName(className);
7782
- console.log(`-- Table: ${tableName} (Class: ${className}, Strategy: ${tableStrategy === "sti" ? "STI" : "CTI"})`);
7783
- console.log(schema);
7784
- console.log();
7785
- }
7797
+ for (const schema of plan.schemas) {
7798
+ const ddl = generateDDLForEngine(schema, engine);
7799
+ console.log(`-- Table: ${schema.tableName}`);
7800
+ console.log(ddl.createTable);
7801
+ for (const statement of [...ddl.indexes, ...ddl.triggers]) console.log(statement);
7802
+ console.log();
7803
+ }
7804
+ if (plan.deferredStatements.length > 0) {
7805
+ console.log("-- Deferred foreign-key constraints");
7806
+ for (const statement of plan.deferredStatements) console.log(statement);
7807
+ console.log();
7786
7808
  }
7787
7809
  console.log("✅ Dry-run complete (no changes made)\n");
7788
7810
  return;
@@ -7814,23 +7836,42 @@ export default testManifest;
7814
7836
  }
7815
7837
  console.log("\n🗑️ Dropping existing tables...\n");
7816
7838
  const dropOrder = [...initOrder].reverse();
7817
- for (const className of dropOrder) {
7818
- const tableName = ObjectRegistry.getTableName(className);
7819
- if (!tableName) continue;
7820
- try {
7821
- await db.execute`DROP TABLE IF EXISTS ${tableName}`;
7822
- console.log(` ✓ Dropped ${tableName}`);
7823
- } catch (error) {
7824
- if (options.verbose) console.log(` ⚠️ Could not drop ${tableName}: ${error}`);
7839
+ const dropEngine = resolveDDLPreviewEngine(dbType);
7840
+ if (dropEngine === "sqlite") await db.query("PRAGMA foreign_keys = OFF");
7841
+ try {
7842
+ for (const className of dropOrder) {
7843
+ const tableName = ObjectRegistry.getTableName(className);
7844
+ if (!tableName) continue;
7845
+ try {
7846
+ const cascade = dropEngine === "postgres" ? " CASCADE" : "";
7847
+ await db.query(`DROP TABLE IF EXISTS ${quoteIdentifier(tableName)}${cascade}`);
7848
+ console.log(` ✓ Dropped ${tableName}`);
7849
+ } catch (error) {
7850
+ if (options.verbose) console.log(` ⚠️ Could not drop ${tableName}: ${error}`);
7851
+ throw new Error(`Refusing to continue after failing to drop ${tableName}; dependency or foreign-key constraints may still be active.`, { cause: error });
7852
+ }
7825
7853
  }
7854
+ } finally {
7855
+ if (dropEngine === "sqlite") await db.query("PRAGMA foreign_keys = ON");
7826
7856
  }
7827
7857
  console.log();
7828
7858
  }
7829
7859
  console.log("🔨 Creating tables...\n");
7830
- const { ensureSchema } = await import("@happyvertical/smrt-core/schema/utils");
7860
+ const { createSchemaManager } = await import("@happyvertical/smrt-core/schema");
7861
+ const schemaManager = createSchemaManager(db, {
7862
+ engine,
7863
+ skipTriggers: typeof db.exportTable === "function"
7864
+ });
7865
+ try {
7866
+ await schemaManager.ensureTables(schemas);
7867
+ } catch (error) {
7868
+ console.error(` ✗ Schema creation failed: ${error}`);
7869
+ if (options.verbose && error instanceof Error && error.stack) console.error(`\n${error.stack}\n`);
7870
+ throw new Error("Refusing to report database setup success after schema creation failed.", { cause: error });
7871
+ }
7831
7872
  let tablesCreated = 0;
7832
7873
  let tablesSkipped = 0;
7833
- for (const className of initOrder) try {
7874
+ for (const className of initOrder) {
7834
7875
  const tableStrategy = ObjectRegistry.getTableStrategy(className);
7835
7876
  const stiBase = ObjectRegistry.getSTIBase(className);
7836
7877
  const registered = ObjectRegistry.getClass(className);
@@ -7840,14 +7881,10 @@ export default testManifest;
7840
7881
  if (options.verbose) console.log(` ⊙ ${className} (shares table with ${stiBase})`);
7841
7882
  continue;
7842
7883
  }
7843
- await ensureSchema(db, className);
7844
7884
  const tableName = ObjectRegistry.getTableName(className);
7845
7885
  const fieldCount = ObjectRegistry.getFields(className)?.size || 0;
7846
7886
  console.log(` ✓ ${tableName} (${fieldCount} columns)`);
7847
7887
  tablesCreated++;
7848
- } catch (error) {
7849
- console.error(` ✗ ${className}: ${error}`);
7850
- if (options.verbose && error instanceof Error && error.stack) console.error(`\n${error.stack}\n`);
7851
7888
  }
7852
7889
  console.log();
7853
7890
  if (tablesSkipped > 0) {
@@ -8230,16 +8267,29 @@ export default testManifest;
8230
8267
  relaxColumns: Boolean(options["relax-columns"]),
8231
8268
  postgresTimestampMigration
8232
8269
  }).compare(manifestSchemas);
8270
+ const engine = tracker.getEngine();
8271
+ const tablePlan = planForeignKeyCreation(diff.added_tables, engine);
8272
+ const plannedTableDDL = new Map(tablePlan.schemas.map((schema) => [schema.tableName, generateDDLForEngine(schema, engine)]));
8273
+ const deferredForeignKeyMigrations = tablePlan.deferredStatements.map((statement) => {
8274
+ const parsed = statement.match(/^ALTER TABLE\s+"((?:[^"]|"")+)"\s+ADD\s+CONSTRAINT\s+"((?:[^"]|"")+)"/i);
8275
+ const unquote = (value, fallback) => value ? value.replace(/""/g, "\"") : fallback;
8276
+ return {
8277
+ type: "add_foreign_key",
8278
+ tableName: unquote(parsed?.[1], "deferred_cycle"),
8279
+ className: unquote(parsed?.[2], "deferred cycle constraint"),
8280
+ sql: statement
8281
+ };
8282
+ });
8233
8283
  const getClassForTable = (tableName) => {
8234
8284
  for (const className of initOrder) if (ObjectRegistry.getTableName(className) === tableName) return className;
8235
8285
  return tableName;
8236
8286
  };
8237
8287
  if (diff.added_tables.length > 0 && isDryRun) {
8238
- for (const schema of diff.added_tables) {
8288
+ for (const schema of tablePlan.schemas) {
8239
8289
  const className = getClassForTable(schema.tableName);
8240
8290
  const fields = Object.keys(schema.columns).length;
8241
8291
  console.log(` 📦 ${schema.tableName} (${className}): Would create table (${fields} columns)`);
8242
- if (options.verbose && schema.ddl) console.log(` ${schema.ddl}`);
8292
+ if (options.verbose) console.log(` ${plannedTableDDL.get(schema.tableName)?.createTable}`);
8243
8293
  }
8244
8294
  console.log();
8245
8295
  }
@@ -8247,14 +8297,26 @@ export default testManifest;
8247
8297
  migrations.push(...partitionedChanges.migrations);
8248
8298
  manualInterventions.push(...partitionedChanges.manualInterventions);
8249
8299
  const advisories = partitionedChanges.advisories;
8250
- assertForceMigrationTargetsExist(forceSelection.forceMigrations, [...diff.added_tables.map((schema) => `create_table_${schema.tableName}`), ...migrations.flatMap((migration) => {
8251
- const migrationName = getSyntheticMigrationNameForAction(migration);
8252
- return migrationName ? [migrationName] : [];
8253
- })]);
8300
+ assertForceMigrationTargetsExist(forceSelection.forceMigrations, [
8301
+ ...diff.added_tables.map((schema) => `create_table_${schema.tableName}`),
8302
+ ...migrations.flatMap((migration) => {
8303
+ const migrationName = getSyntheticMigrationNameForAction(migration);
8304
+ return migrationName ? [migrationName] : [];
8305
+ }),
8306
+ ...deferredForeignKeyMigrations.flatMap((migration) => {
8307
+ const migrationName = getSyntheticMigrationNameForAction(migration);
8308
+ return migrationName ? [migrationName] : [];
8309
+ })
8310
+ ]);
8254
8311
  console.log();
8255
8312
  if (manualInterventions.length > 0) {
8256
8313
  console.log("⚠️ Schema drift detected that requires manual intervention:\n");
8257
8314
  for (const change of manualInterventions) {
8315
+ if (change.type === "add_foreign_key") {
8316
+ console.log(` ${change.tableName}: ${change.advisory?.message ?? change.sql?.replace(/^--\s*/, "") ?? "foreign-key constraint requires manual repair"}`);
8317
+ for (const sql of change.advisory?.suggestedSql ?? []) console.log(` ${sql}`);
8318
+ continue;
8319
+ }
8258
8320
  if (!change.mismatch) continue;
8259
8321
  const detail = change.type === "type_upgrade" ? `${change.tableName}.${change.mismatch.column}: expected ${change.mismatch.expected}, found ${change.mismatch.actual} (cannot auto-apply on this database engine)` : change.type === "alter_column" ? `${change.tableName}.${change.mismatch.column}: expected ${change.mismatch.expected}, found ${change.mismatch.actual} (${change.alteration ?? "alter_column"}; ${change.sql?.replace(/^--\s*/, "") ?? "cannot auto-apply on this database engine"})` : `${change.tableName}.${change.mismatch.column}: expected ${change.mismatch.expected}, found ${change.mismatch.actual}`;
8260
8322
  console.log(` ${detail}`);
@@ -8284,6 +8346,7 @@ export default testManifest;
8284
8346
  }
8285
8347
  const columnMigrations = migrations.filter((m) => m.type === "add_column");
8286
8348
  const indexMigrations = migrations.filter((m) => m.type === "add_index");
8349
+ const foreignKeyMigrations = migrations.filter((m) => m.type === "add_foreign_key");
8287
8350
  const indexDrops = migrations.filter((m) => m.type === "drop_index");
8288
8351
  const columnAlterations = migrations.filter((m) => m.type === "alter_column");
8289
8352
  const columnDrops = migrations.filter((m) => m.type === "drop_column");
@@ -8312,7 +8375,21 @@ export default testManifest;
8312
8375
  for (const m of indexMigrations) console.log(` ${m.index?.name} on ${m.tableName}`);
8313
8376
  console.log();
8314
8377
  }
8378
+ if (foreignKeyMigrations.length > 0) {
8379
+ console.log(` 🔗 Foreign-key constraints to add: ${foreignKeyMigrations.length}`);
8380
+ for (const m of foreignKeyMigrations) console.log(` ${m.tableName}`);
8381
+ console.log();
8382
+ }
8315
8383
  console.log(" SQL Statements:\n");
8384
+ for (const schema of tablePlan.schemas) {
8385
+ const ddl = plannedTableDDL.get(schema.tableName);
8386
+ if (!ddl) continue;
8387
+ for (const sql of [
8388
+ ddl.createTable,
8389
+ ...ddl.indexes,
8390
+ ...ddl.triggers
8391
+ ]) console.log(` ${sql}`);
8392
+ }
8316
8393
  for (const migration of systemTimestampPreview) {
8317
8394
  const terminator = migration.sql.trimEnd().endsWith(";") ? "" : ";";
8318
8395
  console.log(` ${migration.sql}${terminator}`);
@@ -8321,6 +8398,7 @@ export default testManifest;
8321
8398
  const sqlStatements = m.sqlStatements ?? (m.sql ? [m.sql] : []);
8322
8399
  for (const sql of sqlStatements) console.log(` ${sql};`);
8323
8400
  }
8401
+ for (const sql of tablePlan.deferredStatements) console.log(` ${sql}`);
8324
8402
  console.log();
8325
8403
  }
8326
8404
  if (!repairData) {
@@ -8333,13 +8411,13 @@ export default testManifest;
8333
8411
  let skippedCount = 0;
8334
8412
  let errorCount = 0;
8335
8413
  let stiErrorCount = 0;
8336
- const schemaChangeCount = diff.added_tables.length + migrations.length;
8414
+ const schemaChangeCount = diff.added_tables.length + migrations.length + deferredForeignKeyMigrations.length;
8337
8415
  if (applySchemaMigrations && schemaChangeCount > 0) {
8338
8416
  const migrationDefs = [];
8339
8417
  const migrationLogs = /* @__PURE__ */ new Map();
8340
- const engine = tracker.getEngine();
8341
- for (const schema of diff.added_tables) {
8342
- const ddl = generateDDLForEngine(schema, engine);
8418
+ for (const schema of tablePlan.schemas) {
8419
+ const ddl = plannedTableDDL.get(schema.tableName);
8420
+ if (!ddl) throw new Error(`Cannot create table ${schema.tableName}: planned DDL is unavailable.`);
8343
8421
  const createTableSql = ddl.createTable || schema.ddl;
8344
8422
  if (!createTableSql?.trim()) throw new Error(`Cannot create table ${schema.tableName}: schema definition has no generated DDL.`);
8345
8423
  const migrationName = `create_table_${schema.tableName}`;
@@ -8357,6 +8435,18 @@ export default testManifest;
8357
8435
  });
8358
8436
  migrationLogs.set(migrationName, { successMessage: `Created table ${schema.tableName} (${fields} columns)` });
8359
8437
  }
8438
+ for (const migration of deferredForeignKeyMigrations) {
8439
+ const migrationName = getSyntheticMigrationNameForAction(migration);
8440
+ if (!migrationName) continue;
8441
+ migrationDefs.push({
8442
+ id: migrationName,
8443
+ description: `Add deferred foreign-key constraint ${migration.className} on ${migration.tableName}`,
8444
+ version: "1.0.0",
8445
+ up: migration.sql ? [migration.sql] : [],
8446
+ down: []
8447
+ });
8448
+ migrationLogs.set(migrationName, { successMessage: `Added deferred foreign-key constraint ${migration.className} on ${migration.tableName}` });
8449
+ }
8360
8450
  for (const migration of migrations) {
8361
8451
  const migrationName = getSyntheticMigrationNameForAction(migration);
8362
8452
  if (!migrationName) continue;
@@ -8380,11 +8470,14 @@ export default testManifest;
8380
8470
  } else if (migration.type === "drop_index" && migration.indexName) {
8381
8471
  migrationSql = migration.sql || "";
8382
8472
  actionDesc = `Dropped index ${migration.indexName} on ${migration.tableName}`;
8473
+ } else if (migration.type === "add_foreign_key") {
8474
+ migrationSql = migration.sql || "";
8475
+ actionDesc = `Added foreign-key constraint on ${migration.tableName}`;
8383
8476
  } else continue;
8384
8477
  const migrationSqlStatements = migration.sqlStatements ?? (migrationSql ? [migrationSql] : []);
8385
8478
  migrationDefs.push({
8386
8479
  id: migrationName,
8387
- description: migration.type === "add_column" ? `Add column ${migration.column?.name} to ${migration.tableName}` : migration.type === "alter_column" ? `Alter column ${migration.columnName ?? migration.column?.name} on ${migration.tableName} (${migration.alteration ?? "alter"}: ${migration.mismatch?.actual ?? "?"} → ${migration.mismatch?.expected ?? "?"})` : migration.type === "drop_column" ? `Drop column ${migration.columnName} from ${migration.tableName}` : migration.type === "type_upgrade" ? `Upgrade column ${migration.column?.name} on ${migration.tableName} from ${migration.mismatch?.actual} to ${migration.mismatch?.expected}` : migration.type === "drop_index" ? `Drop index ${migration.indexName} on ${migration.tableName}` : `Add index ${migration.index?.name} on ${migration.tableName}`,
8480
+ description: migration.type === "add_column" ? `Add column ${migration.column?.name} to ${migration.tableName}` : migration.type === "alter_column" ? `Alter column ${migration.columnName ?? migration.column?.name} on ${migration.tableName} (${migration.alteration ?? "alter"}: ${migration.mismatch?.actual ?? "?"} → ${migration.mismatch?.expected ?? "?"})` : migration.type === "drop_column" ? `Drop column ${migration.columnName} from ${migration.tableName}` : migration.type === "type_upgrade" ? `Upgrade column ${migration.column?.name} on ${migration.tableName} from ${migration.mismatch?.actual} to ${migration.mismatch?.expected}` : migration.type === "drop_index" ? `Drop index ${migration.indexName} on ${migration.tableName}` : migration.type === "add_foreign_key" ? `Add foreign-key constraint on ${migration.tableName}` : `Add index ${migration.index?.name} on ${migration.tableName}`,
8388
8481
  version: "1.0.0",
8389
8482
  up: migrationSqlStatements,
8390
8483
  down: []
package/dist/index.js CHANGED
@@ -49,63 +49,63 @@ var _playgroundCommands = null;
49
49
  var _workbenchCommands = null;
50
50
  async function getGnodeCommands() {
51
51
  if (!_gnodeCommands) {
52
- const { gnodeCommands } = await import("./commands-BOw91sjd.js");
52
+ const { gnodeCommands } = await import("./commands-C1bK8bv9.js");
53
53
  _gnodeCommands = gnodeCommands;
54
54
  }
55
55
  return _gnodeCommands;
56
56
  }
57
57
  async function getGitCommands() {
58
58
  if (!_gitCommands) {
59
- const { gitCommands } = await import("./commands-BOw91sjd.js");
59
+ const { gitCommands } = await import("./commands-C1bK8bv9.js");
60
60
  _gitCommands = gitCommands;
61
61
  }
62
62
  return _gitCommands;
63
63
  }
64
64
  async function getGenerateCommands() {
65
65
  if (!_generateCommands) {
66
- const { generateCommands } = await import("./commands-BOw91sjd.js");
66
+ const { generateCommands } = await import("./commands-C1bK8bv9.js");
67
67
  _generateCommands = generateCommands;
68
68
  }
69
69
  return _generateCommands;
70
70
  }
71
71
  async function getInitCommands() {
72
72
  if (!_initCommands) {
73
- const { initCommands } = await import("./commands-BOw91sjd.js");
73
+ const { initCommands } = await import("./commands-C1bK8bv9.js");
74
74
  _initCommands = initCommands;
75
75
  }
76
76
  return _initCommands;
77
77
  }
78
78
  async function getUtilityCommands() {
79
79
  if (!_utilityCommands) {
80
- const { utilityCommands } = await import("./commands-BOw91sjd.js");
80
+ const { utilityCommands } = await import("./commands-C1bK8bv9.js");
81
81
  _utilityCommands = utilityCommands;
82
82
  }
83
83
  return _utilityCommands;
84
84
  }
85
85
  async function getDispatchCommands() {
86
86
  if (!_dispatchCommands) {
87
- const { dispatchCommands } = await import("./commands-BOw91sjd.js");
87
+ const { dispatchCommands } = await import("./commands-C1bK8bv9.js");
88
88
  _dispatchCommands = dispatchCommands;
89
89
  }
90
90
  return _dispatchCommands;
91
91
  }
92
92
  async function getDocsCommands() {
93
93
  if (!_docsCommands) {
94
- const { docsCommands } = await import("./commands-BOw91sjd.js");
94
+ const { docsCommands } = await import("./commands-C1bK8bv9.js");
95
95
  _docsCommands = docsCommands;
96
96
  }
97
97
  return _docsCommands;
98
98
  }
99
99
  async function getPlaygroundCommands() {
100
100
  if (!_playgroundCommands) {
101
- const { playgroundCommands } = await import("./commands-BOw91sjd.js");
101
+ const { playgroundCommands } = await import("./commands-C1bK8bv9.js");
102
102
  _playgroundCommands = playgroundCommands;
103
103
  }
104
104
  return _playgroundCommands;
105
105
  }
106
106
  async function getWorkbenchCommands() {
107
107
  if (!_workbenchCommands) {
108
- const { workbenchCommands } = await import("./commands-BOw91sjd.js");
108
+ const { workbenchCommands } = await import("./commands-C1bK8bv9.js");
109
109
  _workbenchCommands = workbenchCommands;
110
110
  }
111
111
  return _workbenchCommands;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-cli",
3
- "version": "0.42.6",
3
+ "version": "0.43.0",
4
4
  "description": "Developer CLI for SMRT framework - introspection, testing, and project management",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -32,12 +32,12 @@
32
32
  "acorn": "^8.17.0",
33
33
  "fast-glob": "3.3.3",
34
34
  "tar": "^7.5.19",
35
- "@happyvertical/smrt-config": "0.42.6",
36
- "@happyvertical/smrt-agents": "0.42.6",
37
- "@happyvertical/smrt-dev-mcp": "0.42.6",
38
- "@happyvertical/smrt-playground": "0.42.6",
39
- "@happyvertical/smrt-types": "0.42.6",
40
- "@happyvertical/smrt-core": "0.42.6"
35
+ "@happyvertical/smrt-agents": "0.43.0",
36
+ "@happyvertical/smrt-config": "0.43.0",
37
+ "@happyvertical/smrt-core": "0.43.0",
38
+ "@happyvertical/smrt-playground": "0.43.0",
39
+ "@happyvertical/smrt-types": "0.43.0",
40
+ "@happyvertical/smrt-dev-mcp": "0.43.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "24.13.2",