tina4-nodejs 3.13.120 → 3.13.121
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/CLAUDE.md +2 -2
- package/package.json +2 -1
- package/packages/cli/dist/bin.js +10724 -10668
- package/packages/cli/src/bin.ts +5 -2
- package/packages/cli/src/commands/generate.ts +117 -19
- package/packages/cli/src/commands/migrateCreate.ts +32 -37
- package/packages/core/dist/index.js +2394 -339
- package/packages/core/src/mcp.ts +62 -11
- package/packages/orm/dist/index.js +2443 -388
- package/types/cli/src/commands/generate.d.ts +16 -0
- package/types/cli/src/commands/migrateCreate.d.ts +1 -1
package/packages/core/src/mcp.ts
CHANGED
|
@@ -1452,19 +1452,70 @@ export function registerDevTools(server: McpServer): void {
|
|
|
1452
1452
|
|
|
1453
1453
|
server.registerTool(
|
|
1454
1454
|
"migration_create",
|
|
1455
|
-
(args) => {
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1455
|
+
async (args) => {
|
|
1456
|
+
// 3.13.121 (ADR-0063): delegate to the CLI's canonical `generate migration`
|
|
1457
|
+
// so the MCP surface emits the SAME `generate_v1_1` envelope (edit_hints[]
|
|
1458
|
+
// + next[]) and the SAME `YYYYMMDDHHMMSS_<slug>.sql` + `.down.sql` file
|
|
1459
|
+
// pair a user gets from `tina4 migrate:create` / `tina4 generate
|
|
1460
|
+
// migration`. Before this the MCP tool wrote a single sequential
|
|
1461
|
+
// `000001_x.sql` with no envelope — two shapes for one operation.
|
|
1462
|
+
//
|
|
1463
|
+
// Dynamic in-process import (parity with migration_status / migration_run
|
|
1464
|
+
// above, which reach `../../orm/src/index.js`) so there is no compile-
|
|
1465
|
+
// time cycle between @tina4/core and @tina4/cli.
|
|
1466
|
+
try {
|
|
1467
|
+
const rawDesc = String(args.description ?? "").trim();
|
|
1468
|
+
if (!rawDesc) return { ok: false, error: "description is required" };
|
|
1469
|
+
|
|
1470
|
+
// Sanitise like the CLI's `migrate:create` front door: lowercase,
|
|
1471
|
+
// non-alphanumeric → "_", trim underscores. `generate migration`
|
|
1472
|
+
// takes its name verbatim by design; the MCP tool is the human-facing
|
|
1473
|
+
// front door, so we sanitise here too.
|
|
1474
|
+
const slug = rawDesc.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
1475
|
+
if (!slug) return { ok: false, error: "description sanitised to an empty slug" };
|
|
1476
|
+
|
|
1477
|
+
// Duplicate-slug guard (parity with PHP + Python MCPs): a `..._<slug>.sql`
|
|
1478
|
+
// that already lives in migrations/ means an earlier `migration_create`
|
|
1479
|
+
// ran for the same request. Surface a clear conflict rather than layering
|
|
1480
|
+
// a second timestamp for the same intent (which would confuse migrate:run).
|
|
1481
|
+
const migrationsDir = path.join(projectRoot, "migrations");
|
|
1482
|
+
if (fs.existsSync(migrationsDir)) {
|
|
1483
|
+
const upSuffix = `_${slug}.sql`;
|
|
1484
|
+
const downSuffix = `_${slug}.down.sql`;
|
|
1485
|
+
const existing = fs.readdirSync(migrationsDir).filter((f) =>
|
|
1486
|
+
(f.endsWith(upSuffix) && !f.endsWith(downSuffix)) || f.endsWith(downSuffix),
|
|
1487
|
+
);
|
|
1488
|
+
if (existing.length > 0) {
|
|
1489
|
+
return {
|
|
1490
|
+
ok: false,
|
|
1491
|
+
error: `A migration with slug "${slug}" already exists`,
|
|
1492
|
+
existing,
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
// The generator writes files RELATIVE to process.cwd(). The MCP tool
|
|
1498
|
+
// may be invoked from anywhere, so pin cwd to the resolved projectRoot
|
|
1499
|
+
// for the duration of the call.
|
|
1500
|
+
const originalCwd = process.cwd();
|
|
1501
|
+
try {
|
|
1502
|
+
process.chdir(projectRoot);
|
|
1503
|
+
const gen = await import("../../cli/src/commands/generate.js");
|
|
1504
|
+
// --no-test keeps parity with the CLI's `migrate:create` front door
|
|
1505
|
+
// (a plain migration, no co-emitted test); an operator who wants a
|
|
1506
|
+
// test uses `generate migration` directly.
|
|
1507
|
+
const envelope = await gen.generateProgrammatic("migration", slug, ["--no-test"]);
|
|
1508
|
+
const migrationPath = envelope.resolution?.migration_path;
|
|
1509
|
+
const created = migrationPath ? path.basename(migrationPath) : "";
|
|
1510
|
+
return { ok: true, created, resolution: envelope };
|
|
1511
|
+
} finally {
|
|
1512
|
+
process.chdir(originalCwd);
|
|
1513
|
+
}
|
|
1514
|
+
} catch (e) {
|
|
1515
|
+
return { ok: false, error: (e as Error).message };
|
|
1460
1516
|
}
|
|
1461
|
-
const existing = fs.readdirSync(migrationsDir).filter((f) => f.endsWith(".sql"));
|
|
1462
|
-
const nextNum = String(existing.length + 1).padStart(6, "0");
|
|
1463
|
-
const filename = `${nextNum}_${desc}.sql`;
|
|
1464
|
-
fs.writeFileSync(path.join(migrationsDir, filename), `-- Migration: ${args.description}\n`, "utf-8");
|
|
1465
|
-
return { created: filename };
|
|
1466
1517
|
},
|
|
1467
|
-
"Create a new migration file",
|
|
1518
|
+
"Create a new migration file (delegates to `generate migration` — emits the ADR-0063 generate_v1_1 envelope + timestamped filename)",
|
|
1468
1519
|
schemaFromParams([{ name: "description", type: "string" }]),
|
|
1469
1520
|
);
|
|
1470
1521
|
|