@happyvertical/smrt-cli 0.40.70 → 0.42.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 +29 -0
- package/dist/{commands-Df9OZ6tG.js → commands-BOw91sjd.js} +314 -7
- package/dist/index.js +9 -9
- package/package.json +7 -7
package/AGENTS.md
CHANGED
|
@@ -14,9 +14,12 @@ smrt db:migrate # Apply migrations
|
|
|
14
14
|
smrt db:migrate --postgres-safe # PostgreSQL concurrent-index mode (see below)
|
|
15
15
|
smrt db:migrate --force-migration <exact-id> [--force-migration <exact-id>...] # Force exact generated migrations in one atomic batch
|
|
16
16
|
smrt db:migrate-uuid # Convert schema-declared UUID text columns after data remap
|
|
17
|
+
smrt db:migrate-int8 # Explicitly widen pre-#2373 int4 columns after preflight
|
|
17
18
|
smrt db:diff # Show schema differences without generating migration files
|
|
18
19
|
smrt db:rollback # Roll back migrations by executing their recorded DOWN
|
|
19
20
|
smrt db:rollback --mark-only # Record-only flip; schema deliberately untouched
|
|
21
|
+
smrt db:prune # Prune framework system tables to their retention windows
|
|
22
|
+
smrt db:prune --dry-run # Same predicates, counted rather than deleted
|
|
20
23
|
smrt docs:agents # Generate .agents/smrt-framework.md
|
|
21
24
|
smrt docs:claude # Deprecated alias writing .claude/smrt-framework.md
|
|
22
25
|
smrt dev:knowledge-* # Deterministic agent knowledge index/check/diff
|
|
@@ -93,6 +96,12 @@ not from the schema definition.
|
|
|
93
96
|
(`indexIntrospection: 'unavailable'`) rather than inventing missing indexes.
|
|
94
97
|
- The check is read-only and lives in a new core module; it does not share code
|
|
95
98
|
with `migrations/differ.ts`.
|
|
99
|
+
- PostgreSQL/DuckDB `int4` columns created before #2373 are advisory warnings,
|
|
100
|
+
not type drift: the differ intentionally treats int4/int8 as equivalent.
|
|
101
|
+
Run `smrt db:migrate-int8 --dry-run`, schedule the reported table rewrites,
|
|
102
|
+
then run `smrt db:migrate-int8`; SQLite is already 64-bit and is a no-op.
|
|
103
|
+
PostgreSQL uses the same bounded `migrations.postgres.lockTimeout` and
|
|
104
|
+
`statementTimeout` settings as ordinary schema migration.
|
|
96
105
|
|
|
97
106
|
## `db:migrate` on SQLite: type changes rebuild the table
|
|
98
107
|
|
|
@@ -139,6 +148,26 @@ the exact statement `db:migrate` records for `diff.added_tables`.
|
|
|
139
148
|
Reverting a non-`create_table` change is a forward operation: update the
|
|
140
149
|
`@smrt` object definitions and run `db:migrate` again.
|
|
141
150
|
|
|
151
|
+
## `db:prune` is the retention cron entry point (#2375)
|
|
152
|
+
|
|
153
|
+
Runs `runRetentionSweep()` from `@happyvertical/smrt-core` over every
|
|
154
|
+
framework-owned system table plus every task other installed packages
|
|
155
|
+
registered (`_smrt_jobs`/`_smrt_job_events` from `smrt-jobs`, expired
|
|
156
|
+
sessions/magic-link tokens/CLI-auth requests from `smrt-users`).
|
|
157
|
+
|
|
158
|
+
- Defaults are the framework's documented retention windows; `retention` in
|
|
159
|
+
`smrt.config` overrides them persistently, and `--changes-days`,
|
|
160
|
+
`--usage-days`, `--dispatch-days` override them for one run.
|
|
161
|
+
- `--skip` takes **task** names, not table names — the same names the report
|
|
162
|
+
and `--json` print, so a package-contributed task is skipped the same way a
|
|
163
|
+
built-in one is.
|
|
164
|
+
- `--dry-run` counts with the identical predicates instead of deleting; the
|
|
165
|
+
report says `would prune`.
|
|
166
|
+
- The exit code is non-zero when **any** task failed, so a partial sweep never
|
|
167
|
+
looks clean to cron. Individual task failures never abort the others.
|
|
168
|
+
- Deployments running a jobs `TaskRunner` already get the same sweep every six
|
|
169
|
+
hours; this command is for those that do not, and for one-off operator runs.
|
|
170
|
+
|
|
142
171
|
## Architecture
|
|
143
172
|
|
|
144
173
|
- **Lazy command loading**: commands loaded on-demand via dynamic import (~100ms overhead on first use)
|
|
@@ -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, generateDDLForEngine, getClassName, isQualifiedName, migratePostgresSystemTimestamps, parseQualifiedName, planPostgresSystemTimestampMigrations } from "@happyvertical/smrt-core";
|
|
8
|
+
import { ObjectRegistry, SchemaComparer, checkLiveSchemaParity, createQualifiedName, ensureDeferredSystemTableCompatibility, generateDDLForEngine, getClassName, isQualifiedName, migratePostgresSystemTimestamps, parseQualifiedName, 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";
|
|
@@ -20,6 +20,7 @@ import https from "node:https";
|
|
|
20
20
|
import { homedir, tmpdir } from "node:os";
|
|
21
21
|
import { extract } from "tar";
|
|
22
22
|
import { importWorkspaceModule } from "@happyvertical/smrt-core/utils/import-workspace-module";
|
|
23
|
+
import { buildIntegerWidthTableStatements, collectIntegerWidthTargets, parsePostgresTimeoutMs, preflightIntegerWidthWidening, widenIntegerColumnsToBigInt } from "@happyvertical/smrt-core/migrations";
|
|
23
24
|
import { buildArchitectureContext, buildKnowledgeIndex, buildReviewContext, checkKnowledgeFreshness, diffKnowledgeIndex, renderFreshnessResult, renderKnowledgeIndexMarkdown } from "@happyvertical/smrt-dev-mcp/knowledge";
|
|
24
25
|
//#region src/commands/db-command-utils.ts
|
|
25
26
|
var SENSITIVE_QUERY_PARAMS = /* @__PURE__ */ new Set([
|
|
@@ -1400,6 +1401,191 @@ function formatDateTime(date) {
|
|
|
1400
1401
|
return date.toISOString().replace("T", " ").substring(0, 19);
|
|
1401
1402
|
}
|
|
1402
1403
|
//#endregion
|
|
1404
|
+
//#region src/commands/db-prune.ts
|
|
1405
|
+
/**
|
|
1406
|
+
* Packages that contribute retention tasks by registering them on import.
|
|
1407
|
+
*
|
|
1408
|
+
* `db:prune` runs in the CLI's own process, so a task only reaches the sweep
|
|
1409
|
+
* if this process actually loaded the package that registers it. These are
|
|
1410
|
+
* imported optionally — a project that does not depend on jobs or users simply
|
|
1411
|
+
* has no jobs or users tasks, which is the correct outcome, not an error.
|
|
1412
|
+
*/
|
|
1413
|
+
var RETENTION_TASK_PACKAGES = ["@happyvertical/smrt-jobs", "@happyvertical/smrt-users"];
|
|
1414
|
+
/**
|
|
1415
|
+
* Load every installed package that contributes retention tasks.
|
|
1416
|
+
*
|
|
1417
|
+
* A rejected import is always treated as "not installed" — this function
|
|
1418
|
+
* cannot reliably tell a genuine module-resolution miss apart from a package
|
|
1419
|
+
* that resolved but threw during its own top-level evaluation (error shapes
|
|
1420
|
+
* differ across bundlers and runtimes, and `importPackage` is caller-supplied
|
|
1421
|
+
* for exactly that flexibility). It still logs the message on `stderr`
|
|
1422
|
+
* rather than swallowing it outright, so an operator can tell "this project
|
|
1423
|
+
* doesn't depend on jobs/users" apart from "smrt-jobs is installed but broke
|
|
1424
|
+
* on import" without the sweep itself needing to fail over an optional
|
|
1425
|
+
* dependency.
|
|
1426
|
+
*
|
|
1427
|
+
* @returns The specifiers that loaded, in declaration order.
|
|
1428
|
+
*/
|
|
1429
|
+
async function loadRetentionTaskPackages(importPackage) {
|
|
1430
|
+
const loaded = [];
|
|
1431
|
+
for (const specifier of RETENTION_TASK_PACKAGES) try {
|
|
1432
|
+
await importPackage(specifier);
|
|
1433
|
+
loaded.push(specifier);
|
|
1434
|
+
} catch (error) {
|
|
1435
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1436
|
+
console.warn(`⚠️ Could not load ${specifier} (${message}). Treating its retention tasks as not contributed — if the package is installed, this may be a real import failure rather than a missing dependency.`);
|
|
1437
|
+
}
|
|
1438
|
+
return loaded;
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Merge command-line overrides onto the configured retention policy.
|
|
1442
|
+
*
|
|
1443
|
+
* `--skip` names tasks, not tables, so it covers both the built-in tables and
|
|
1444
|
+
* anything a package registered — the same names `db:prune --json` reports.
|
|
1445
|
+
*/
|
|
1446
|
+
function buildPrunePolicy(configured, options) {
|
|
1447
|
+
const policy = { ...configured ?? {} };
|
|
1448
|
+
policy.dryRun = options["dry-run"] || (configured?.dryRun ?? false);
|
|
1449
|
+
if (options["changes-days"] !== void 0) policy.changes = {
|
|
1450
|
+
...policy.changes === false ? {} : policy.changes ?? {},
|
|
1451
|
+
maxAgeDays: options["changes-days"]
|
|
1452
|
+
};
|
|
1453
|
+
if (options["usage-days"] !== void 0) policy.aiUsage = {
|
|
1454
|
+
...policy.aiUsage === false ? {} : policy.aiUsage ?? {},
|
|
1455
|
+
maxAgeDays: options["usage-days"]
|
|
1456
|
+
};
|
|
1457
|
+
if (options["dispatch-days"] !== void 0) policy.dispatch = {
|
|
1458
|
+
...policy.dispatch === false ? {} : policy.dispatch ?? {},
|
|
1459
|
+
completedOlderThanDays: options["dispatch-days"]
|
|
1460
|
+
};
|
|
1461
|
+
const skipped = (options.skip ?? "").split(",").map((name) => name.trim()).filter((name) => name.length > 0);
|
|
1462
|
+
for (const name of skipped) switch (name) {
|
|
1463
|
+
case "changes":
|
|
1464
|
+
policy.changes = false;
|
|
1465
|
+
break;
|
|
1466
|
+
case "ai-usage":
|
|
1467
|
+
policy.aiUsage = false;
|
|
1468
|
+
break;
|
|
1469
|
+
case "contexts":
|
|
1470
|
+
policy.contexts = false;
|
|
1471
|
+
break;
|
|
1472
|
+
case "dispatch":
|
|
1473
|
+
policy.dispatch = false;
|
|
1474
|
+
break;
|
|
1475
|
+
default: policy.tasks = {
|
|
1476
|
+
...policy.tasks ?? {},
|
|
1477
|
+
[name]: false
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
return policy;
|
|
1481
|
+
}
|
|
1482
|
+
/** Render a completed sweep as an operator-readable table. */
|
|
1483
|
+
function formatSweepResult(result) {
|
|
1484
|
+
const lines = [];
|
|
1485
|
+
const verb = result.dryRun ? "would prune" : "pruned";
|
|
1486
|
+
for (const task of result.tasks) {
|
|
1487
|
+
const status = task.error ? `error: ${task.error}` : task.skipped ? task.skipped : `${verb} ${task.pruned}`;
|
|
1488
|
+
const detail = task.details ? ` (${Object.entries(task.details).map(([key, value]) => `${key}=${value}`).join(", ")})` : "";
|
|
1489
|
+
lines.push(` ${task.task.padEnd(24)}${status}${detail}`);
|
|
1490
|
+
}
|
|
1491
|
+
lines.push("");
|
|
1492
|
+
lines.push(` ${"total".padEnd(24)}${verb} ${result.pruned} row(s) in ${result.durationMs}ms`);
|
|
1493
|
+
return lines.join("\n");
|
|
1494
|
+
}
|
|
1495
|
+
/**
|
|
1496
|
+
* Names passed to `--skip` that no task in the completed sweep answered to.
|
|
1497
|
+
*
|
|
1498
|
+
* A typo and a package that was never installed look identical on the command
|
|
1499
|
+
* line, so the command says which names it did not recognize rather than
|
|
1500
|
+
* silently doing less than the operator asked for.
|
|
1501
|
+
*/
|
|
1502
|
+
function unmatchedSkipNames(skip, result) {
|
|
1503
|
+
const known = new Set(result.tasks.map((task) => task.task));
|
|
1504
|
+
return (skip ?? "").split(",").map((name) => name.trim()).filter((name) => name.length > 0 && !known.has(name));
|
|
1505
|
+
}
|
|
1506
|
+
var dbPruneCommand = {
|
|
1507
|
+
name: "db:prune",
|
|
1508
|
+
description: "Prune framework-owned system tables to their retention windows",
|
|
1509
|
+
args: [],
|
|
1510
|
+
options: {
|
|
1511
|
+
"dry-run": {
|
|
1512
|
+
type: "boolean",
|
|
1513
|
+
description: "Report what would be deleted without deleting it",
|
|
1514
|
+
default: false
|
|
1515
|
+
},
|
|
1516
|
+
json: {
|
|
1517
|
+
type: "boolean",
|
|
1518
|
+
description: "Output as JSON (for CI/cron integration)",
|
|
1519
|
+
default: false,
|
|
1520
|
+
short: "j"
|
|
1521
|
+
},
|
|
1522
|
+
"changes-days": {
|
|
1523
|
+
type: "number",
|
|
1524
|
+
description: "Retention window for _smrt_changes, in days"
|
|
1525
|
+
},
|
|
1526
|
+
"usage-days": {
|
|
1527
|
+
type: "number",
|
|
1528
|
+
description: "Retention window for _smrt_ai_usage, in days"
|
|
1529
|
+
},
|
|
1530
|
+
"dispatch-days": {
|
|
1531
|
+
type: "number",
|
|
1532
|
+
description: "Retention window for completed _smrt_dispatch rows, in days"
|
|
1533
|
+
},
|
|
1534
|
+
skip: {
|
|
1535
|
+
type: "string",
|
|
1536
|
+
description: "Comma-separated task names to skip (changes, ai-usage, contexts, dispatch, …)"
|
|
1537
|
+
}
|
|
1538
|
+
},
|
|
1539
|
+
handler: async (_args, options) => {
|
|
1540
|
+
let db;
|
|
1541
|
+
try {
|
|
1542
|
+
const { getPackageConfig } = await import("@happyvertical/smrt-config");
|
|
1543
|
+
const { DEFAULT_CLI_CONFIG } = await import("./config-BwrFRL8L.js");
|
|
1544
|
+
const config = getPackageConfig("cli", DEFAULT_CLI_CONFIG);
|
|
1545
|
+
if (!config.database?.url) {
|
|
1546
|
+
const message = "Database not configured. Set database.url in smrt.config.ts.";
|
|
1547
|
+
if (options.json) console.log(JSON.stringify({ error: message }));
|
|
1548
|
+
else console.error(`\n❌ ${message}\n`);
|
|
1549
|
+
process.exitCode = 1;
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1552
|
+
const dbUrl = config.database.url;
|
|
1553
|
+
const dbType = config.database.type || "sqlite";
|
|
1554
|
+
const { getDatabase } = await import("@happyvertical/sql");
|
|
1555
|
+
db = await getDatabase({
|
|
1556
|
+
type: dbType,
|
|
1557
|
+
url: dbUrl
|
|
1558
|
+
});
|
|
1559
|
+
const { config: smrtConfig, importOptionalDependency, runRetentionSweep } = await import("@happyvertical/smrt-core");
|
|
1560
|
+
const loaded = await loadRetentionTaskPackages((specifier) => importOptionalDependency(specifier, "Install it in the project to include its retention tasks."));
|
|
1561
|
+
const policy = buildPrunePolicy(smrtConfig.toJSON().retention, options);
|
|
1562
|
+
const result = await runRetentionSweep(db, policy);
|
|
1563
|
+
const unmatched = unmatchedSkipNames(options.skip, result);
|
|
1564
|
+
if (options.json) console.log(JSON.stringify({
|
|
1565
|
+
...result,
|
|
1566
|
+
contributors: loaded,
|
|
1567
|
+
unmatched
|
|
1568
|
+
}, null, 2));
|
|
1569
|
+
else {
|
|
1570
|
+
console.log(`\n🧹 Retention sweep${result.dryRun ? " (dry run)" : ""}\n`);
|
|
1571
|
+
console.log(`Database: ${formatDatabaseDisplayUrl(dbType, dbUrl)}\n`);
|
|
1572
|
+
console.log(formatSweepResult(result));
|
|
1573
|
+
console.log();
|
|
1574
|
+
if (unmatched.length > 0) console.warn(`⚠️ --skip named no known task: ${unmatched.join(", ")}. Check the spelling, or the package that registers it may not be installed.
|
|
1575
|
+
`);
|
|
1576
|
+
}
|
|
1577
|
+
if (result.failed) process.exitCode = 1;
|
|
1578
|
+
} catch (error) {
|
|
1579
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1580
|
+
if (options.json) console.log(JSON.stringify({ error: message }));
|
|
1581
|
+
else console.error(`\n❌ Retention sweep failed: ${message}\n`);
|
|
1582
|
+
process.exitCode = 1;
|
|
1583
|
+
} finally {
|
|
1584
|
+
await closeDatabaseConnection(db);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
};
|
|
1588
|
+
//#endregion
|
|
1403
1589
|
//#region src/commands/db-rollback.ts
|
|
1404
1590
|
/**
|
|
1405
1591
|
* Name prefix of the only migration class `db:migrate` records a DOWN script
|
|
@@ -5850,6 +6036,100 @@ var playgroundCommands = {
|
|
|
5850
6036
|
}
|
|
5851
6037
|
};
|
|
5852
6038
|
//#endregion
|
|
6039
|
+
//#region src/commands/db-migrate-int8.ts
|
|
6040
|
+
/**
|
|
6041
|
+
* db:migrate-int8 Command
|
|
6042
|
+
*
|
|
6043
|
+
* Explicitly widens legacy PostgreSQL/DuckDB int4 columns created before
|
|
6044
|
+
* #2373. This is deliberately separate from `db:migrate`: PostgreSQL rewrites
|
|
6045
|
+
* each table, so an operator must review the row-count preflight and schedule
|
|
6046
|
+
* a maintenance window before opting in.
|
|
6047
|
+
*/
|
|
6048
|
+
var BACKFILL_NAME = "@happyvertical/smrt-core:integer-width:v1";
|
|
6049
|
+
var DEFAULT_POSTGRES_LOCK_TIMEOUT_MS = 3e4;
|
|
6050
|
+
var DEFAULT_POSTGRES_STATEMENT_TIMEOUT_MS = 6e4;
|
|
6051
|
+
var dbMigrateInt8Command = {
|
|
6052
|
+
name: "db:migrate-int8",
|
|
6053
|
+
description: "Widen legacy SMRT int4 columns to BIGINT after reviewing the maintenance-window preflight. Run after db:migrate.",
|
|
6054
|
+
aliases: ["migrate-int8", "db-migrate-int8"],
|
|
6055
|
+
args: [],
|
|
6056
|
+
options: {
|
|
6057
|
+
"dry-run": {
|
|
6058
|
+
type: "boolean",
|
|
6059
|
+
description: "Print the preflight and ALTER statements without writing.",
|
|
6060
|
+
default: false
|
|
6061
|
+
},
|
|
6062
|
+
verbose: {
|
|
6063
|
+
type: "boolean",
|
|
6064
|
+
description: "Print the full per-table preflight report.",
|
|
6065
|
+
default: false,
|
|
6066
|
+
short: "v"
|
|
6067
|
+
}
|
|
6068
|
+
},
|
|
6069
|
+
handler: async (_args, options) => {
|
|
6070
|
+
let db;
|
|
6071
|
+
const dryRun = Boolean(options["dry-run"]);
|
|
6072
|
+
try {
|
|
6073
|
+
const { getPackageConfig } = await import("@happyvertical/smrt-config");
|
|
6074
|
+
const { DEFAULT_CLI_CONFIG } = await import("./config-BwrFRL8L.js");
|
|
6075
|
+
const config = getPackageConfig("cli", DEFAULT_CLI_CONFIG);
|
|
6076
|
+
if (!config.database?.url || config.database.url === ":memory:") throw new Error("Database configuration required for db:migrate-int8. Configure database.url in smrt.config.ts (or DATABASE_URL).");
|
|
6077
|
+
await autoDiscoverAndLoad();
|
|
6078
|
+
const schemas = ObjectRegistry.getAllSchemasAsDefinitions();
|
|
6079
|
+
if (Object.keys(schemas).length === 0) throw new Error("No SMRT application schemas were discovered. Run this command from the project root after generating manifests.");
|
|
6080
|
+
const dbType = config.database.type || "sqlite";
|
|
6081
|
+
const dbUrl = config.database.url;
|
|
6082
|
+
const { getDatabase } = await import("@happyvertical/sql");
|
|
6083
|
+
db = await getDatabase({
|
|
6084
|
+
type: dbType,
|
|
6085
|
+
url: dbUrl
|
|
6086
|
+
});
|
|
6087
|
+
console.log("\n↔️ Integer-width migration\n");
|
|
6088
|
+
console.log(`✓ Connected to ${formatDatabaseDisplayUrl(dbType, dbUrl)}\n`);
|
|
6089
|
+
const targets = collectIntegerWidthTargets(schemas, { includeSystemTables: true });
|
|
6090
|
+
const preflight = await preflightIntegerWidthWidening(db, targets, { engineHint: dbType });
|
|
6091
|
+
console.log(preflight.summary);
|
|
6092
|
+
if (options.verbose && preflight.supported) {
|
|
6093
|
+
console.log("\nFull preflight report:");
|
|
6094
|
+
for (const table of preflight.tables) {
|
|
6095
|
+
const columns = table.columns.map((column) => `${column.column}: ${column.declaredType ?? "missing"} (${column.state})`).join(", ");
|
|
6096
|
+
console.log(` ${table.table}: ${table.rowCount ?? "not counted"} row(s); ${columns}`);
|
|
6097
|
+
}
|
|
6098
|
+
}
|
|
6099
|
+
if (!preflight.supported) {
|
|
6100
|
+
console.log("\nNo widening is needed on this engine.\n");
|
|
6101
|
+
return;
|
|
6102
|
+
}
|
|
6103
|
+
if (preflight.unexpectedColumns > 0) throw new Error("Some schema-declared integer columns have unexpected live types. Resolve ordinary schema drift before this widening pass.");
|
|
6104
|
+
if (preflight.pendingColumns === 0) {
|
|
6105
|
+
console.log("\nNo legacy int4 columns remain.\n");
|
|
6106
|
+
return;
|
|
6107
|
+
}
|
|
6108
|
+
const statements = preflight.tables.flatMap((table) => buildIntegerWidthTableStatements(preflight.engine, table.table, table.columns.filter((column) => column.state === "pending").map((column) => column.column)));
|
|
6109
|
+
console.log(`\n${dryRun ? "DRY RUN — would execute" : "Applying"} ${statements.length} lossless ALTER statement(s):`);
|
|
6110
|
+
for (const statement of statements) console.log(` ${statement};`);
|
|
6111
|
+
if (dryRun) {
|
|
6112
|
+
console.log("\nDry run complete — no changes applied.\n");
|
|
6113
|
+
return;
|
|
6114
|
+
}
|
|
6115
|
+
const postgresMigrationConfig = config.migrations?.postgres;
|
|
6116
|
+
const result = await widenIntegerColumnsToBigInt(db, targets, {
|
|
6117
|
+
engineHint: dbType,
|
|
6118
|
+
backfillName: BACKFILL_NAME,
|
|
6119
|
+
packageName: "@happyvertical/smrt-core",
|
|
6120
|
+
lockTimeout: parsePostgresTimeoutMs(postgresMigrationConfig?.lockTimeout, DEFAULT_POSTGRES_LOCK_TIMEOUT_MS),
|
|
6121
|
+
statementTimeout: parsePostgresTimeoutMs(postgresMigrationConfig?.statementTimeout, DEFAULT_POSTGRES_STATEMENT_TIMEOUT_MS)
|
|
6122
|
+
});
|
|
6123
|
+
console.log(result.ran ? `\n✓ Widened ${result.widenedColumns.length} column(s) to BIGINT.\n` : "\nNo widening was applied; no legacy int4 columns remain.\n");
|
|
6124
|
+
} catch (error) {
|
|
6125
|
+
console.error(`\n❌ int8 migration failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
6126
|
+
process.exitCode = 1;
|
|
6127
|
+
} finally {
|
|
6128
|
+
await closeDatabaseConnection(db);
|
|
6129
|
+
}
|
|
6130
|
+
}
|
|
6131
|
+
};
|
|
6132
|
+
//#endregion
|
|
5853
6133
|
//#region src/commands/db-migrate-uuid.ts
|
|
5854
6134
|
/**
|
|
5855
6135
|
* db:migrate-uuid Command
|
|
@@ -7084,6 +7364,28 @@ function resolveDDLPreviewEngine(dbType) {
|
|
|
7084
7364
|
default: return "sqlite";
|
|
7085
7365
|
}
|
|
7086
7366
|
}
|
|
7367
|
+
/**
|
|
7368
|
+
* Run the framework's deferred system-table compatibility pass after
|
|
7369
|
+
* `db:migrate` has created the tables it reshapes (issue #2376).
|
|
7370
|
+
*
|
|
7371
|
+
* `_smrt_jobs` and `_smrt_job_events` are dual-owned — created here from the
|
|
7372
|
+
* jobs manifest, then given their compatibility columns and indexes by
|
|
7373
|
+
* `@happyvertical/smrt-core`. On a fresh install the framework bootstrap runs
|
|
7374
|
+
* before those tables exist, so without this call the pass would first become
|
|
7375
|
+
* reachable at the next process start.
|
|
7376
|
+
*
|
|
7377
|
+
* Best-effort: the pass is idempotent and re-runs on the next boot, so a
|
|
7378
|
+
* failure here must not fail an otherwise-successful migration.
|
|
7379
|
+
*/
|
|
7380
|
+
async function settleDeferredCompatibilityAfterMigrate(db, dbType, { verbose }) {
|
|
7381
|
+
try {
|
|
7382
|
+
const { settled } = await ensureDeferredSystemTableCompatibility(db, dbType);
|
|
7383
|
+
if (verbose) console.log(settled ? "Deferred system-table compatibility settled (_smrt_jobs, _smrt_job_events)\n" : "Deferred system-table compatibility pending (jobs tables not present)\n");
|
|
7384
|
+
} catch (error) {
|
|
7385
|
+
console.warn(`⚠️ Deferred system-table compatibility did not complete: ${error instanceof Error ? error.message : String(error)}`);
|
|
7386
|
+
console.warn(" It will be retried the next time the framework starts.\n");
|
|
7387
|
+
}
|
|
7388
|
+
}
|
|
7087
7389
|
function formatStiConflictIdentity(conflict) {
|
|
7088
7390
|
const entries = Object.entries(conflict.conflictIdentity);
|
|
7089
7391
|
return `${entries.length > 0 ? entries.map(([column, value]) => `${column}=${JSON.stringify(value)}`).join(", ") : "no non-_meta_type conflict columns"}${conflict.legacyId || conflict.qualifiedId ? ` (legacy id: ${conflict.legacyId ?? "unknown"}, qualified id: ${conflict.qualifiedId ?? "unknown"})` : ""}`;
|
|
@@ -7175,7 +7477,7 @@ function resolveDeclaredViteMajor(packageJson) {
|
|
|
7175
7477
|
function assessDecoratorSupport(input) {
|
|
7176
7478
|
const { viteMajor, viteConfigContent, tsconfigContent } = input;
|
|
7177
7479
|
const hasOxcDecorator = viteConfigContent !== null && OXC_DECORATOR_BLOCK_RE.test(viteConfigContent);
|
|
7178
|
-
const hasTsconfigDecorators = tsconfigContent
|
|
7480
|
+
const hasTsconfigDecorators = tsconfigContent?.includes("experimentalDecorators");
|
|
7179
7481
|
const isVite8Plus = viteMajor !== null && viteMajor >= 8;
|
|
7180
7482
|
if (hasOxcDecorator) return {
|
|
7181
7483
|
status: "ok",
|
|
@@ -8229,11 +8531,14 @@ export default testManifest;
|
|
|
8229
8531
|
stiErrorCount,
|
|
8230
8532
|
dryRun: isDryRun
|
|
8231
8533
|
})) process.exitCode = 1;
|
|
8232
|
-
if (!isDryRun)
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8534
|
+
if (!isDryRun) {
|
|
8535
|
+
await settleDeferredCompatibilityAfterMigrate(db, dbType, { verbose: Boolean(options.verbose) });
|
|
8536
|
+
assertSchemaContract(await evaluateSchemaContract({
|
|
8537
|
+
discovered,
|
|
8538
|
+
schemaContract: config.schemaContract,
|
|
8539
|
+
db
|
|
8540
|
+
}));
|
|
8541
|
+
}
|
|
8237
8542
|
console.log("💡 Next steps:");
|
|
8238
8543
|
console.log(" - Run: smrt db:status (view migration status)");
|
|
8239
8544
|
console.log(" - Run: smrt db:history (view migration history)");
|
|
@@ -8504,6 +8809,8 @@ export default testManifest;
|
|
|
8504
8809
|
"db:rollback": dbRollbackCommand,
|
|
8505
8810
|
"db:generate": dbGenerateCommand,
|
|
8506
8811
|
"db:migrate-uuid": dbMigrateUuidCommand,
|
|
8812
|
+
"db:migrate-int8": dbMigrateInt8Command,
|
|
8813
|
+
"db:prune": dbPruneCommand,
|
|
8507
8814
|
"config:export": configExportCommand,
|
|
8508
8815
|
export: exportCommand
|
|
8509
8816
|
};
|
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-BOw91sjd.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-BOw91sjd.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-BOw91sjd.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-BOw91sjd.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-BOw91sjd.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-BOw91sjd.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-BOw91sjd.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-BOw91sjd.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-BOw91sjd.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.42.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-
|
|
36
|
-
"@happyvertical/smrt-
|
|
37
|
-
"@happyvertical/smrt-
|
|
38
|
-
"@happyvertical/smrt-
|
|
39
|
-
"@happyvertical/smrt-
|
|
40
|
-
"@happyvertical/smrt-
|
|
35
|
+
"@happyvertical/smrt-agents": "0.42.0",
|
|
36
|
+
"@happyvertical/smrt-config": "0.42.0",
|
|
37
|
+
"@happyvertical/smrt-core": "0.42.0",
|
|
38
|
+
"@happyvertical/smrt-dev-mcp": "0.42.0",
|
|
39
|
+
"@happyvertical/smrt-playground": "0.42.0",
|
|
40
|
+
"@happyvertical/smrt-types": "0.42.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "24.13.2",
|