@happyvertical/smrt-cli 0.42.7 → 0.43.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.
- package/AGENTS.md +19 -0
- package/dist/{commands-BOw91sjd.js → commands-BAKZil1v.js} +182 -49
- package/dist/index.js +9 -9
- package/package.json +7 -7
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,14 @@ 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
|
+
}
|
|
496
|
+
case "drop_foreign_key": {
|
|
497
|
+
const fingerprint = sqlShapeFingerprint(action);
|
|
498
|
+
return fingerprint ? `drop_foreign_key_${action.tableName}_${fingerprint}` : null;
|
|
499
|
+
}
|
|
492
500
|
case "drop_index": {
|
|
493
501
|
if (!action.indexName) return null;
|
|
494
502
|
const fingerprint = sqlShapeFingerprint(action);
|
|
@@ -517,6 +525,14 @@ function getSyntheticMigrationNameForChange(change) {
|
|
|
517
525
|
});
|
|
518
526
|
return fingerprint ? `add_index_${indexName}_${fingerprint}` : `add_index_${indexName}`;
|
|
519
527
|
}
|
|
528
|
+
case "add_foreign_key": {
|
|
529
|
+
const fingerprint = sqlShapeFingerprint(change);
|
|
530
|
+
return fingerprint ? `add_foreign_key_${change.table}_${fingerprint}` : null;
|
|
531
|
+
}
|
|
532
|
+
case "drop_foreign_key": {
|
|
533
|
+
const fingerprint = sqlShapeFingerprint(change);
|
|
534
|
+
return fingerprint ? `drop_foreign_key_${change.table}_${fingerprint}` : null;
|
|
535
|
+
}
|
|
520
536
|
case "drop_index": {
|
|
521
537
|
if (!change.name) return null;
|
|
522
538
|
const fingerprint = sqlShapeFingerprint({
|
|
@@ -557,13 +573,13 @@ function shouldApplySchemaMigrations(state) {
|
|
|
557
573
|
return !state.dryRun;
|
|
558
574
|
}
|
|
559
575
|
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";
|
|
576
|
+
if (!migrationName.startsWith("add_column_") && !migrationName.startsWith("drop_column_") && !migrationName.startsWith("alter_column_") && !migrationName.startsWith("add_foreign_key_") && !migrationName.startsWith("drop_foreign_key_") && !migrationName.startsWith("add_index_") && !migrationName.startsWith("drop_index_") && !migrationName.startsWith("type_upgrade_")) return "other";
|
|
561
577
|
return unresolvedSyntheticMigrationNames.has(migrationName) ? "unresolved" : "superseded";
|
|
562
578
|
}
|
|
563
579
|
function getUnresolvedGeneratedMigrationNames(changes) {
|
|
564
580
|
const names = /* @__PURE__ */ new Set();
|
|
565
581
|
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;
|
|
582
|
+
if (change.type !== "add_column" && change.type !== "drop_column" && change.type !== "alter_column" && change.type !== "add_foreign_key" && change.type !== "drop_foreign_key" && change.type !== "add_index" && change.type !== "drop_index" && change.type !== "type_upgrade") continue;
|
|
567
583
|
if (change.type === "type_upgrade" && classifyTypeUpgradeSql(change.sql) === "noop") continue;
|
|
568
584
|
if (change.type === "alter_column" && isAdvisoryOnlyChangeLike(change)) continue;
|
|
569
585
|
for (const migrationName of getSyntheticMigrationNamesForChange(change)) names.add(migrationName);
|
|
@@ -706,6 +722,20 @@ function partitionSchemaChanges(changes, getClassForTable) {
|
|
|
706
722
|
});
|
|
707
723
|
break;
|
|
708
724
|
}
|
|
725
|
+
case "add_foreign_key":
|
|
726
|
+
case "drop_foreign_key": {
|
|
727
|
+
const action = {
|
|
728
|
+
type: change.type,
|
|
729
|
+
tableName: change.table,
|
|
730
|
+
className,
|
|
731
|
+
sql: change.sql,
|
|
732
|
+
...change.sqlStatements ? { sqlStatements: change.sqlStatements } : {},
|
|
733
|
+
advisory: change.advisory
|
|
734
|
+
};
|
|
735
|
+
if (isAdvisoryOnlyChangeLike(change)) manualInterventions.push(action);
|
|
736
|
+
else migrations.push(action);
|
|
737
|
+
break;
|
|
738
|
+
}
|
|
709
739
|
case "drop_index":
|
|
710
740
|
if (!change.name) continue;
|
|
711
741
|
migrations.push({
|
|
@@ -965,7 +995,8 @@ var dbDiffCommand = {
|
|
|
965
995
|
}, null, 2));
|
|
966
996
|
return;
|
|
967
997
|
}
|
|
968
|
-
const { advisories } = partitionSchemaChanges(diff.changes, (tableName) => tableName);
|
|
998
|
+
const { advisories, manualInterventions } = partitionSchemaChanges(diff.changes, (tableName) => tableName);
|
|
999
|
+
const manualForeignKeyChanges = manualInterventions.filter((change) => change.type === "add_foreign_key" || change.type === "drop_foreign_key");
|
|
969
1000
|
if (!diff.has_changes) {
|
|
970
1001
|
console.log("✅ Database schema is up to date - no changes detected\n");
|
|
971
1002
|
printSchemaAdvisories(advisories, {
|
|
@@ -974,7 +1005,7 @@ var dbDiffCommand = {
|
|
|
974
1005
|
});
|
|
975
1006
|
return;
|
|
976
1007
|
}
|
|
977
|
-
if (diff.changes.filter((c) => !(c.advisory && !c.sql && !c.sqlStatements)).length === 0 && diff.added_tables.length === 0 && diff.dropped_tables.length === 0) {
|
|
1008
|
+
if (diff.changes.filter((c) => !(c.advisory && !c.sql && !c.sqlStatements)).length === 0 && manualForeignKeyChanges.length === 0 && diff.added_tables.length === 0 && diff.dropped_tables.length === 0) {
|
|
978
1009
|
console.log("✅ Database schema is up to date - no migrations needed (see notes below)\n");
|
|
979
1010
|
printSchemaAdvisories(advisories, {
|
|
980
1011
|
orphanTables: diff.orphan_tables,
|
|
@@ -1055,6 +1086,16 @@ var dbDiffCommand = {
|
|
|
1055
1086
|
for (const change of typeMismatches) console.log(` ! ${change.table}.${change.name}: ${change.mismatch?.expected} vs ${change.mismatch?.actual}`);
|
|
1056
1087
|
console.log(" (Type changes require manual migration)\n");
|
|
1057
1088
|
}
|
|
1089
|
+
if (manualForeignKeyChanges.length > 0) {
|
|
1090
|
+
console.log(` ⚠️ Foreign-key drift needing a manual step (${manualForeignKeyChanges.length}):`);
|
|
1091
|
+
for (const change of manualForeignKeyChanges) {
|
|
1092
|
+
const operation = change.type === "drop_foreign_key" ? "remove" : "add";
|
|
1093
|
+
console.log(` ! ${change.tableName}: ${operation} constraint`);
|
|
1094
|
+
console.log(` ${change.advisory?.message ?? "foreign-key constraint requires manual repair"}`);
|
|
1095
|
+
for (const sql of change.advisory?.suggestedSql ?? []) console.log(` ↳ ${sql}`);
|
|
1096
|
+
}
|
|
1097
|
+
console.log();
|
|
1098
|
+
}
|
|
1058
1099
|
printSchemaAdvisories(advisories, {
|
|
1059
1100
|
orphanTables: diff.orphan_tables,
|
|
1060
1101
|
verbose: options.verbose
|
|
@@ -2361,6 +2402,17 @@ function summarizeSchemaDiff(diff) {
|
|
|
2361
2402
|
recommendation: "Run `smrt db:migrate` to add the missing index and reconcile the live schema."
|
|
2362
2403
|
});
|
|
2363
2404
|
break;
|
|
2405
|
+
case "add_foreign_key":
|
|
2406
|
+
case "drop_foreign_key": {
|
|
2407
|
+
const relationship = change.foreignKey ? `${change.table}.${change.foreignKey.column} -> ${change.foreignKey.referencesTable}.${change.foreignKey.referencesColumn}` : `${change.table}.${change.name ?? "(unknown)"}`;
|
|
2408
|
+
const isAddition = change.type === "add_foreign_key";
|
|
2409
|
+
drift.push({
|
|
2410
|
+
name: relationship,
|
|
2411
|
+
type: isAddition ? "missing_foreign_key" : "disabled_foreign_key",
|
|
2412
|
+
recommendation: change.advisory?.message ?? (isAddition ? "Run `smrt db:migrate` to add the missing foreign key and reconcile the live schema." : "Run `smrt db:migrate` to remove the disabled foreign key and reconcile the live schema.")
|
|
2413
|
+
});
|
|
2414
|
+
break;
|
|
2415
|
+
}
|
|
2364
2416
|
case "type_upgrade":
|
|
2365
2417
|
switch (classifyTypeUpgradeSql(change.sql)) {
|
|
2366
2418
|
case "executable":
|
|
@@ -7752,6 +7804,15 @@ export default testManifest;
|
|
|
7752
7804
|
schemaContract: config.schemaContract
|
|
7753
7805
|
}));
|
|
7754
7806
|
const initOrder = ObjectRegistry.getInitializationOrder();
|
|
7807
|
+
const engine = resolveDDLPreviewEngine(dbType);
|
|
7808
|
+
const schemas = Object.values(ObjectRegistry.getAllSchemasAsDefinitions());
|
|
7809
|
+
const preflightTables = new Set(schemas.map((schema) => schema.tableName));
|
|
7810
|
+
const missingPreflightTables = [...new Set(initOrder.flatMap((className) => {
|
|
7811
|
+
const tableName = ObjectRegistry.getTableName(className);
|
|
7812
|
+
return tableName && !preflightTables.has(tableName) ? [tableName] : [];
|
|
7813
|
+
}))].sort();
|
|
7814
|
+
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.`);
|
|
7815
|
+
const plan = planForeignKeyCreation(schemas, engine);
|
|
7755
7816
|
if (options.verbose) {
|
|
7756
7817
|
console.log("📋 Initialization order (respecting dependencies):");
|
|
7757
7818
|
for (let i = 0; i < initOrder.length; i++) {
|
|
@@ -7764,25 +7825,17 @@ export default testManifest;
|
|
|
7764
7825
|
}
|
|
7765
7826
|
if (options["dry-run"]) {
|
|
7766
7827
|
console.log("📋 SQL Preview (not executed):\n");
|
|
7767
|
-
const
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
const
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
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
|
-
}
|
|
7828
|
+
for (const schema of plan.schemas) {
|
|
7829
|
+
const ddl = generateDDLForEngine(schema, engine);
|
|
7830
|
+
console.log(`-- Table: ${schema.tableName}`);
|
|
7831
|
+
console.log(ddl.createTable);
|
|
7832
|
+
for (const statement of [...ddl.indexes, ...ddl.triggers]) console.log(statement);
|
|
7833
|
+
console.log();
|
|
7834
|
+
}
|
|
7835
|
+
if (plan.deferredStatements.length > 0) {
|
|
7836
|
+
console.log("-- Deferred foreign-key constraints");
|
|
7837
|
+
for (const statement of plan.deferredStatements) console.log(statement);
|
|
7838
|
+
console.log();
|
|
7786
7839
|
}
|
|
7787
7840
|
console.log("✅ Dry-run complete (no changes made)\n");
|
|
7788
7841
|
return;
|
|
@@ -7814,23 +7867,42 @@ export default testManifest;
|
|
|
7814
7867
|
}
|
|
7815
7868
|
console.log("\n🗑️ Dropping existing tables...\n");
|
|
7816
7869
|
const dropOrder = [...initOrder].reverse();
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7870
|
+
const dropEngine = resolveDDLPreviewEngine(dbType);
|
|
7871
|
+
if (dropEngine === "sqlite") await db.query("PRAGMA foreign_keys = OFF");
|
|
7872
|
+
try {
|
|
7873
|
+
for (const className of dropOrder) {
|
|
7874
|
+
const tableName = ObjectRegistry.getTableName(className);
|
|
7875
|
+
if (!tableName) continue;
|
|
7876
|
+
try {
|
|
7877
|
+
const cascade = dropEngine === "postgres" ? " CASCADE" : "";
|
|
7878
|
+
await db.query(`DROP TABLE IF EXISTS ${quoteIdentifier(tableName)}${cascade}`);
|
|
7879
|
+
console.log(` ✓ Dropped ${tableName}`);
|
|
7880
|
+
} catch (error) {
|
|
7881
|
+
if (options.verbose) console.log(` ⚠️ Could not drop ${tableName}: ${error}`);
|
|
7882
|
+
throw new Error(`Refusing to continue after failing to drop ${tableName}; dependency or foreign-key constraints may still be active.`, { cause: error });
|
|
7883
|
+
}
|
|
7825
7884
|
}
|
|
7885
|
+
} finally {
|
|
7886
|
+
if (dropEngine === "sqlite") await db.query("PRAGMA foreign_keys = ON");
|
|
7826
7887
|
}
|
|
7827
7888
|
console.log();
|
|
7828
7889
|
}
|
|
7829
7890
|
console.log("🔨 Creating tables...\n");
|
|
7830
|
-
const {
|
|
7891
|
+
const { createSchemaManager } = await import("@happyvertical/smrt-core/schema");
|
|
7892
|
+
const schemaManager = createSchemaManager(db, {
|
|
7893
|
+
engine,
|
|
7894
|
+
skipTriggers: typeof db.exportTable === "function"
|
|
7895
|
+
});
|
|
7896
|
+
try {
|
|
7897
|
+
await schemaManager.ensureTables(schemas);
|
|
7898
|
+
} catch (error) {
|
|
7899
|
+
console.error(` ✗ Schema creation failed: ${error}`);
|
|
7900
|
+
if (options.verbose && error instanceof Error && error.stack) console.error(`\n${error.stack}\n`);
|
|
7901
|
+
throw new Error("Refusing to report database setup success after schema creation failed.", { cause: error });
|
|
7902
|
+
}
|
|
7831
7903
|
let tablesCreated = 0;
|
|
7832
7904
|
let tablesSkipped = 0;
|
|
7833
|
-
for (const className of initOrder)
|
|
7905
|
+
for (const className of initOrder) {
|
|
7834
7906
|
const tableStrategy = ObjectRegistry.getTableStrategy(className);
|
|
7835
7907
|
const stiBase = ObjectRegistry.getSTIBase(className);
|
|
7836
7908
|
const registered = ObjectRegistry.getClass(className);
|
|
@@ -7840,14 +7912,10 @@ export default testManifest;
|
|
|
7840
7912
|
if (options.verbose) console.log(` ⊙ ${className} (shares table with ${stiBase})`);
|
|
7841
7913
|
continue;
|
|
7842
7914
|
}
|
|
7843
|
-
await ensureSchema(db, className);
|
|
7844
7915
|
const tableName = ObjectRegistry.getTableName(className);
|
|
7845
7916
|
const fieldCount = ObjectRegistry.getFields(className)?.size || 0;
|
|
7846
7917
|
console.log(` ✓ ${tableName} (${fieldCount} columns)`);
|
|
7847
7918
|
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
7919
|
}
|
|
7852
7920
|
console.log();
|
|
7853
7921
|
if (tablesSkipped > 0) {
|
|
@@ -8230,16 +8298,29 @@ export default testManifest;
|
|
|
8230
8298
|
relaxColumns: Boolean(options["relax-columns"]),
|
|
8231
8299
|
postgresTimestampMigration
|
|
8232
8300
|
}).compare(manifestSchemas);
|
|
8301
|
+
const engine = tracker.getEngine();
|
|
8302
|
+
const tablePlan = planForeignKeyCreation(diff.added_tables, engine);
|
|
8303
|
+
const plannedTableDDL = new Map(tablePlan.schemas.map((schema) => [schema.tableName, generateDDLForEngine(schema, engine)]));
|
|
8304
|
+
const deferredForeignKeyMigrations = tablePlan.deferredStatements.map((statement) => {
|
|
8305
|
+
const parsed = statement.match(/^ALTER TABLE\s+"((?:[^"]|"")+)"\s+ADD\s+CONSTRAINT\s+"((?:[^"]|"")+)"/i);
|
|
8306
|
+
const unquote = (value, fallback) => value ? value.replace(/""/g, "\"") : fallback;
|
|
8307
|
+
return {
|
|
8308
|
+
type: "add_foreign_key",
|
|
8309
|
+
tableName: unquote(parsed?.[1], "deferred_cycle"),
|
|
8310
|
+
className: unquote(parsed?.[2], "deferred cycle constraint"),
|
|
8311
|
+
sql: statement
|
|
8312
|
+
};
|
|
8313
|
+
});
|
|
8233
8314
|
const getClassForTable = (tableName) => {
|
|
8234
8315
|
for (const className of initOrder) if (ObjectRegistry.getTableName(className) === tableName) return className;
|
|
8235
8316
|
return tableName;
|
|
8236
8317
|
};
|
|
8237
8318
|
if (diff.added_tables.length > 0 && isDryRun) {
|
|
8238
|
-
for (const schema of
|
|
8319
|
+
for (const schema of tablePlan.schemas) {
|
|
8239
8320
|
const className = getClassForTable(schema.tableName);
|
|
8240
8321
|
const fields = Object.keys(schema.columns).length;
|
|
8241
8322
|
console.log(` 📦 ${schema.tableName} (${className}): Would create table (${fields} columns)`);
|
|
8242
|
-
if (options.verbose
|
|
8323
|
+
if (options.verbose) console.log(` ${plannedTableDDL.get(schema.tableName)?.createTable}`);
|
|
8243
8324
|
}
|
|
8244
8325
|
console.log();
|
|
8245
8326
|
}
|
|
@@ -8247,14 +8328,26 @@ export default testManifest;
|
|
|
8247
8328
|
migrations.push(...partitionedChanges.migrations);
|
|
8248
8329
|
manualInterventions.push(...partitionedChanges.manualInterventions);
|
|
8249
8330
|
const advisories = partitionedChanges.advisories;
|
|
8250
|
-
assertForceMigrationTargetsExist(forceSelection.forceMigrations, [
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
8331
|
+
assertForceMigrationTargetsExist(forceSelection.forceMigrations, [
|
|
8332
|
+
...diff.added_tables.map((schema) => `create_table_${schema.tableName}`),
|
|
8333
|
+
...migrations.flatMap((migration) => {
|
|
8334
|
+
const migrationName = getSyntheticMigrationNameForAction(migration);
|
|
8335
|
+
return migrationName ? [migrationName] : [];
|
|
8336
|
+
}),
|
|
8337
|
+
...deferredForeignKeyMigrations.flatMap((migration) => {
|
|
8338
|
+
const migrationName = getSyntheticMigrationNameForAction(migration);
|
|
8339
|
+
return migrationName ? [migrationName] : [];
|
|
8340
|
+
})
|
|
8341
|
+
]);
|
|
8254
8342
|
console.log();
|
|
8255
8343
|
if (manualInterventions.length > 0) {
|
|
8256
8344
|
console.log("⚠️ Schema drift detected that requires manual intervention:\n");
|
|
8257
8345
|
for (const change of manualInterventions) {
|
|
8346
|
+
if (change.type === "add_foreign_key" || change.type === "drop_foreign_key") {
|
|
8347
|
+
console.log(` ${change.tableName}: ${change.advisory?.message ?? change.sql?.replace(/^--\s*/, "") ?? "foreign-key constraint requires manual repair"}`);
|
|
8348
|
+
for (const sql of change.advisory?.suggestedSql ?? []) console.log(` ${sql}`);
|
|
8349
|
+
continue;
|
|
8350
|
+
}
|
|
8258
8351
|
if (!change.mismatch) continue;
|
|
8259
8352
|
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
8353
|
console.log(` ${detail}`);
|
|
@@ -8284,6 +8377,8 @@ export default testManifest;
|
|
|
8284
8377
|
}
|
|
8285
8378
|
const columnMigrations = migrations.filter((m) => m.type === "add_column");
|
|
8286
8379
|
const indexMigrations = migrations.filter((m) => m.type === "add_index");
|
|
8380
|
+
const foreignKeyMigrations = migrations.filter((m) => m.type === "add_foreign_key");
|
|
8381
|
+
const foreignKeyDrops = migrations.filter((m) => m.type === "drop_foreign_key");
|
|
8287
8382
|
const indexDrops = migrations.filter((m) => m.type === "drop_index");
|
|
8288
8383
|
const columnAlterations = migrations.filter((m) => m.type === "alter_column");
|
|
8289
8384
|
const columnDrops = migrations.filter((m) => m.type === "drop_column");
|
|
@@ -8312,7 +8407,26 @@ export default testManifest;
|
|
|
8312
8407
|
for (const m of indexMigrations) console.log(` ${m.index?.name} on ${m.tableName}`);
|
|
8313
8408
|
console.log();
|
|
8314
8409
|
}
|
|
8410
|
+
if (foreignKeyMigrations.length > 0) {
|
|
8411
|
+
console.log(` 🔗 Foreign-key constraints to add: ${foreignKeyMigrations.length}`);
|
|
8412
|
+
for (const m of foreignKeyMigrations) console.log(` ${m.tableName}`);
|
|
8413
|
+
console.log();
|
|
8414
|
+
}
|
|
8415
|
+
if (foreignKeyDrops.length > 0) {
|
|
8416
|
+
console.log(` 🔗 Foreign-key constraints to drop: ${foreignKeyDrops.length}`);
|
|
8417
|
+
for (const m of foreignKeyDrops) console.log(` ${m.tableName}`);
|
|
8418
|
+
console.log();
|
|
8419
|
+
}
|
|
8315
8420
|
console.log(" SQL Statements:\n");
|
|
8421
|
+
for (const schema of tablePlan.schemas) {
|
|
8422
|
+
const ddl = plannedTableDDL.get(schema.tableName);
|
|
8423
|
+
if (!ddl) continue;
|
|
8424
|
+
for (const sql of [
|
|
8425
|
+
ddl.createTable,
|
|
8426
|
+
...ddl.indexes,
|
|
8427
|
+
...ddl.triggers
|
|
8428
|
+
]) console.log(` ${sql}`);
|
|
8429
|
+
}
|
|
8316
8430
|
for (const migration of systemTimestampPreview) {
|
|
8317
8431
|
const terminator = migration.sql.trimEnd().endsWith(";") ? "" : ";";
|
|
8318
8432
|
console.log(` ${migration.sql}${terminator}`);
|
|
@@ -8321,6 +8435,7 @@ export default testManifest;
|
|
|
8321
8435
|
const sqlStatements = m.sqlStatements ?? (m.sql ? [m.sql] : []);
|
|
8322
8436
|
for (const sql of sqlStatements) console.log(` ${sql};`);
|
|
8323
8437
|
}
|
|
8438
|
+
for (const sql of tablePlan.deferredStatements) console.log(` ${sql}`);
|
|
8324
8439
|
console.log();
|
|
8325
8440
|
}
|
|
8326
8441
|
if (!repairData) {
|
|
@@ -8333,13 +8448,13 @@ export default testManifest;
|
|
|
8333
8448
|
let skippedCount = 0;
|
|
8334
8449
|
let errorCount = 0;
|
|
8335
8450
|
let stiErrorCount = 0;
|
|
8336
|
-
const schemaChangeCount = diff.added_tables.length + migrations.length;
|
|
8451
|
+
const schemaChangeCount = diff.added_tables.length + migrations.length + deferredForeignKeyMigrations.length;
|
|
8337
8452
|
if (applySchemaMigrations && schemaChangeCount > 0) {
|
|
8338
8453
|
const migrationDefs = [];
|
|
8339
8454
|
const migrationLogs = /* @__PURE__ */ new Map();
|
|
8340
|
-
const
|
|
8341
|
-
|
|
8342
|
-
|
|
8455
|
+
for (const schema of tablePlan.schemas) {
|
|
8456
|
+
const ddl = plannedTableDDL.get(schema.tableName);
|
|
8457
|
+
if (!ddl) throw new Error(`Cannot create table ${schema.tableName}: planned DDL is unavailable.`);
|
|
8343
8458
|
const createTableSql = ddl.createTable || schema.ddl;
|
|
8344
8459
|
if (!createTableSql?.trim()) throw new Error(`Cannot create table ${schema.tableName}: schema definition has no generated DDL.`);
|
|
8345
8460
|
const migrationName = `create_table_${schema.tableName}`;
|
|
@@ -8357,6 +8472,18 @@ export default testManifest;
|
|
|
8357
8472
|
});
|
|
8358
8473
|
migrationLogs.set(migrationName, { successMessage: `Created table ${schema.tableName} (${fields} columns)` });
|
|
8359
8474
|
}
|
|
8475
|
+
for (const migration of deferredForeignKeyMigrations) {
|
|
8476
|
+
const migrationName = getSyntheticMigrationNameForAction(migration);
|
|
8477
|
+
if (!migrationName) continue;
|
|
8478
|
+
migrationDefs.push({
|
|
8479
|
+
id: migrationName,
|
|
8480
|
+
description: `Add deferred foreign-key constraint ${migration.className} on ${migration.tableName}`,
|
|
8481
|
+
version: "1.0.0",
|
|
8482
|
+
up: migration.sql ? [migration.sql] : [],
|
|
8483
|
+
down: []
|
|
8484
|
+
});
|
|
8485
|
+
migrationLogs.set(migrationName, { successMessage: `Added deferred foreign-key constraint ${migration.className} on ${migration.tableName}` });
|
|
8486
|
+
}
|
|
8360
8487
|
for (const migration of migrations) {
|
|
8361
8488
|
const migrationName = getSyntheticMigrationNameForAction(migration);
|
|
8362
8489
|
if (!migrationName) continue;
|
|
@@ -8380,11 +8507,17 @@ export default testManifest;
|
|
|
8380
8507
|
} else if (migration.type === "drop_index" && migration.indexName) {
|
|
8381
8508
|
migrationSql = migration.sql || "";
|
|
8382
8509
|
actionDesc = `Dropped index ${migration.indexName} on ${migration.tableName}`;
|
|
8510
|
+
} else if (migration.type === "add_foreign_key") {
|
|
8511
|
+
migrationSql = migration.sql || "";
|
|
8512
|
+
actionDesc = `Added foreign-key constraint on ${migration.tableName}`;
|
|
8513
|
+
} else if (migration.type === "drop_foreign_key") {
|
|
8514
|
+
migrationSql = migration.sql || "";
|
|
8515
|
+
actionDesc = `Dropped foreign-key constraint on ${migration.tableName}`;
|
|
8383
8516
|
} else continue;
|
|
8384
8517
|
const migrationSqlStatements = migration.sqlStatements ?? (migrationSql ? [migrationSql] : []);
|
|
8385
8518
|
migrationDefs.push({
|
|
8386
8519
|
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}`,
|
|
8520
|
+
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}` : migration.type === "drop_foreign_key" ? `Drop foreign-key constraint on ${migration.tableName}` : `Add index ${migration.index?.name} on ${migration.tableName}`,
|
|
8388
8521
|
version: "1.0.0",
|
|
8389
8522
|
up: migrationSqlStatements,
|
|
8390
8523
|
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-
|
|
52
|
+
const { gnodeCommands } = await import("./commands-BAKZil1v.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-
|
|
59
|
+
const { gitCommands } = await import("./commands-BAKZil1v.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-
|
|
66
|
+
const { generateCommands } = await import("./commands-BAKZil1v.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-
|
|
73
|
+
const { initCommands } = await import("./commands-BAKZil1v.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-
|
|
80
|
+
const { utilityCommands } = await import("./commands-BAKZil1v.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-
|
|
87
|
+
const { dispatchCommands } = await import("./commands-BAKZil1v.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-
|
|
94
|
+
const { docsCommands } = await import("./commands-BAKZil1v.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-
|
|
101
|
+
const { playgroundCommands } = await import("./commands-BAKZil1v.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-
|
|
108
|
+
const { workbenchCommands } = await import("./commands-BAKZil1v.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.
|
|
3
|
+
"version": "0.43.1",
|
|
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-
|
|
36
|
-
"@happyvertical/smrt-core": "0.
|
|
37
|
-
"@happyvertical/smrt-
|
|
38
|
-
"@happyvertical/smrt-
|
|
39
|
-
"@happyvertical/smrt-playground": "0.
|
|
40
|
-
"@happyvertical/smrt-dev-mcp": "0.
|
|
35
|
+
"@happyvertical/smrt-agents": "0.43.1",
|
|
36
|
+
"@happyvertical/smrt-core": "0.43.1",
|
|
37
|
+
"@happyvertical/smrt-config": "0.43.1",
|
|
38
|
+
"@happyvertical/smrt-types": "0.43.1",
|
|
39
|
+
"@happyvertical/smrt-playground": "0.43.1",
|
|
40
|
+
"@happyvertical/smrt-dev-mcp": "0.43.1"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "24.13.2",
|