@llmops/cli 0.1.0-beta.8 → 0.1.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/dist/index.mjs +1 -221
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { command, run, string } from "@drizzle-team/brocli";
|
|
3
3
|
import { logger } from "@llmops/core";
|
|
4
|
-
import { createDatabaseFromConnection, detectDatabaseType } from "@llmops/core/db";
|
|
4
|
+
import { createDatabaseFromConnection, detectDatabaseType, getMigrations } from "@llmops/core/db";
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
6
6
|
import yoctoSpinner from "yocto-spinner";
|
|
7
7
|
import chalk from "chalk";
|
|
8
8
|
import prompts from "prompts";
|
|
9
9
|
import { loadConfig } from "c12";
|
|
10
10
|
import path from "node:path";
|
|
11
|
-
import { sql } from "kysely";
|
|
12
11
|
|
|
13
12
|
//#region src/lib/get-config.ts
|
|
14
13
|
const getConfig = async ({ cwd, configPath }) => {
|
|
@@ -20,225 +19,6 @@ const getConfig = async ({ cwd, configPath }) => {
|
|
|
20
19
|
}
|
|
21
20
|
};
|
|
22
21
|
|
|
23
|
-
//#endregion
|
|
24
|
-
//#region src/lib/get-migration.ts
|
|
25
|
-
const typeMap = {
|
|
26
|
-
postgres: {
|
|
27
|
-
uuid: [
|
|
28
|
-
"character varying",
|
|
29
|
-
"varchar",
|
|
30
|
-
"text",
|
|
31
|
-
"uuid"
|
|
32
|
-
],
|
|
33
|
-
text: [
|
|
34
|
-
"character varying",
|
|
35
|
-
"varchar",
|
|
36
|
-
"text"
|
|
37
|
-
],
|
|
38
|
-
timestamp: [
|
|
39
|
-
"timestamptz",
|
|
40
|
-
"timestamp",
|
|
41
|
-
"date"
|
|
42
|
-
],
|
|
43
|
-
jsonb: ["json", "jsonb"]
|
|
44
|
-
},
|
|
45
|
-
mysql: {
|
|
46
|
-
text: ["varchar", "text"],
|
|
47
|
-
timestamp: [
|
|
48
|
-
"timestamp",
|
|
49
|
-
"datetime",
|
|
50
|
-
"date"
|
|
51
|
-
],
|
|
52
|
-
jsonb: ["json"]
|
|
53
|
-
},
|
|
54
|
-
sqlite: {
|
|
55
|
-
text: ["TEXT"],
|
|
56
|
-
date: ["DATE", "INTEGER"],
|
|
57
|
-
integer: ["INTEGER"],
|
|
58
|
-
boolean: [
|
|
59
|
-
"INTEGER",
|
|
60
|
-
"BOOLEAN",
|
|
61
|
-
"TEXT"
|
|
62
|
-
],
|
|
63
|
-
jsonb: ["TEXT"]
|
|
64
|
-
},
|
|
65
|
-
mssql: {
|
|
66
|
-
varchar: [
|
|
67
|
-
"varchar",
|
|
68
|
-
"nvarchar",
|
|
69
|
-
"uniqueidentifier"
|
|
70
|
-
],
|
|
71
|
-
datetime2: [
|
|
72
|
-
"datetime2",
|
|
73
|
-
"date",
|
|
74
|
-
"datetime"
|
|
75
|
-
],
|
|
76
|
-
jsonb: ["varchar", "nvarchar"]
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
function matchType(columnDataType, fieldType, dbType) {
|
|
80
|
-
const normalize = (type) => type.toLowerCase().split("(")[0].trim();
|
|
81
|
-
const types = typeMap[dbType];
|
|
82
|
-
for (const [expectedType, variants] of Object.entries(types)) if (fieldType.toLowerCase().includes(expectedType.toLowerCase())) return variants.some((variant) => variant.toLowerCase() === normalize(columnDataType));
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Get the current PostgreSQL schema (search_path) for the database connection
|
|
87
|
-
*/
|
|
88
|
-
async function getPostgresSchema(db) {
|
|
89
|
-
try {
|
|
90
|
-
const result = await sql`SHOW search_path`.execute(db);
|
|
91
|
-
if (result.rows[0]?.search_path) return result.rows[0].search_path.split(",").map((s) => s.trim()).map((s) => s.replace(/^["']|["']$/g, "")).filter((s) => !s.startsWith("$"))[0] || "public";
|
|
92
|
-
} catch {}
|
|
93
|
-
return "public";
|
|
94
|
-
}
|
|
95
|
-
async function getMigrations(db, dbType) {
|
|
96
|
-
let currentSchema = "public";
|
|
97
|
-
if (dbType === "postgres") {
|
|
98
|
-
currentSchema = await getPostgresSchema(db);
|
|
99
|
-
logger.debug(`PostgreSQL migration: Using schema '${currentSchema}'`);
|
|
100
|
-
}
|
|
101
|
-
const allTableMetadata = await db.introspection.getTables();
|
|
102
|
-
let tableMetadata = allTableMetadata;
|
|
103
|
-
if (dbType === "postgres") try {
|
|
104
|
-
const tablesInSchema = await sql`
|
|
105
|
-
SELECT table_name
|
|
106
|
-
FROM information_schema.tables
|
|
107
|
-
WHERE table_schema = ${currentSchema}
|
|
108
|
-
AND table_type = 'BASE TABLE'
|
|
109
|
-
`.execute(db);
|
|
110
|
-
const tableNamesInSchema = new Set(tablesInSchema.rows.map((row) => row.table_name));
|
|
111
|
-
tableMetadata = allTableMetadata.filter((table) => table.schema === currentSchema && tableNamesInSchema.has(table.name));
|
|
112
|
-
logger.debug(`Found ${tableMetadata.length} table(s) in schema '${currentSchema}'`);
|
|
113
|
-
} catch (error) {
|
|
114
|
-
logger.warn("Could not filter tables by schema. Using all discovered tables.");
|
|
115
|
-
}
|
|
116
|
-
const schema = (await import("@llmops/core/db")).SCHEMA_METADATA.tables;
|
|
117
|
-
const toBeCreated = [];
|
|
118
|
-
const toBeAdded = [];
|
|
119
|
-
for (const [tableName, tableConfig] of Object.entries(schema)) {
|
|
120
|
-
const existingTable = tableMetadata.find((t) => t.name === tableName);
|
|
121
|
-
if (!existingTable) {
|
|
122
|
-
toBeCreated.push({
|
|
123
|
-
table: tableName,
|
|
124
|
-
fields: tableConfig.fields,
|
|
125
|
-
order: tableConfig.order
|
|
126
|
-
});
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
const missingFields = {};
|
|
130
|
-
for (const [fieldName, fieldConfig] of Object.entries(tableConfig.fields)) {
|
|
131
|
-
const existingColumn = existingTable.columns.find((c) => c.name === fieldName);
|
|
132
|
-
if (!existingColumn) {
|
|
133
|
-
missingFields[fieldName] = fieldConfig;
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
if (!matchType(existingColumn.dataType, fieldConfig.type, dbType)) logger.warn(`Field ${fieldName} in table ${tableName} has a different type. Expected ${fieldConfig.type} but got ${existingColumn.dataType}.`);
|
|
137
|
-
}
|
|
138
|
-
if (Object.keys(missingFields).length > 0) toBeAdded.push({
|
|
139
|
-
table: tableName,
|
|
140
|
-
fields: missingFields,
|
|
141
|
-
order: tableConfig.order
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
toBeCreated.sort((a, b) => a.order - b.order);
|
|
145
|
-
toBeAdded.sort((a, b) => a.order - b.order);
|
|
146
|
-
const migrations = [];
|
|
147
|
-
function getColumnType(fieldConfig, fieldName) {
|
|
148
|
-
const { type } = fieldConfig;
|
|
149
|
-
return {
|
|
150
|
-
uuid: {
|
|
151
|
-
postgres: "uuid",
|
|
152
|
-
mysql: "varchar(36)",
|
|
153
|
-
sqlite: "text",
|
|
154
|
-
mssql: "varchar(36)"
|
|
155
|
-
},
|
|
156
|
-
text: {
|
|
157
|
-
postgres: "text",
|
|
158
|
-
mysql: fieldConfig.unique ? "varchar(255)" : "text",
|
|
159
|
-
sqlite: "text",
|
|
160
|
-
mssql: fieldConfig.unique ? "varchar(255)" : "varchar(8000)"
|
|
161
|
-
},
|
|
162
|
-
timestamp: {
|
|
163
|
-
postgres: "timestamptz",
|
|
164
|
-
mysql: "timestamp(3)",
|
|
165
|
-
sqlite: "date",
|
|
166
|
-
mssql: sql`datetime2(3)`
|
|
167
|
-
},
|
|
168
|
-
jsonb: {
|
|
169
|
-
postgres: "jsonb",
|
|
170
|
-
mysql: "json",
|
|
171
|
-
sqlite: "text",
|
|
172
|
-
mssql: "varchar(8000)"
|
|
173
|
-
},
|
|
174
|
-
boolean: {
|
|
175
|
-
postgres: "boolean",
|
|
176
|
-
mysql: "boolean",
|
|
177
|
-
sqlite: "integer",
|
|
178
|
-
mssql: sql`bit`
|
|
179
|
-
},
|
|
180
|
-
integer: {
|
|
181
|
-
postgres: "integer",
|
|
182
|
-
mysql: "integer",
|
|
183
|
-
sqlite: "integer",
|
|
184
|
-
mssql: "integer"
|
|
185
|
-
}
|
|
186
|
-
}[type]?.[dbType] || "text";
|
|
187
|
-
}
|
|
188
|
-
for (const table of toBeCreated) {
|
|
189
|
-
let builder = db.schema.createTable(table.table);
|
|
190
|
-
for (const [fieldName, fieldConfig] of Object.entries(table.fields)) {
|
|
191
|
-
const type = getColumnType(fieldConfig, fieldName);
|
|
192
|
-
builder = builder.addColumn(fieldName, type, (col) => {
|
|
193
|
-
let c = col;
|
|
194
|
-
if (fieldName === "id") if (dbType === "postgres") c = c.primaryKey().defaultTo(sql`gen_random_uuid()`).notNull();
|
|
195
|
-
else c = c.primaryKey().notNull();
|
|
196
|
-
else if (!fieldConfig.nullable) c = c.notNull();
|
|
197
|
-
if (fieldConfig.references && fieldName !== "id") {
|
|
198
|
-
const refTable = fieldConfig.references.table;
|
|
199
|
-
const refColumn = fieldConfig.references.column;
|
|
200
|
-
c = c.references(`${refTable}.${refColumn}`).onDelete("cascade");
|
|
201
|
-
}
|
|
202
|
-
if (fieldConfig.unique && fieldName !== "id") c = c.unique();
|
|
203
|
-
if (fieldConfig.default === "now()" && fieldName !== "id" && dbType !== "sqlite") if (dbType === "mysql") c = c.defaultTo(sql`CURRENT_TIMESTAMP(3)`);
|
|
204
|
-
else c = c.defaultTo(sql`CURRENT_TIMESTAMP`);
|
|
205
|
-
return c;
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
migrations.push(builder);
|
|
209
|
-
}
|
|
210
|
-
for (const table of toBeAdded) for (const [fieldName, fieldConfig] of Object.entries(table.fields)) {
|
|
211
|
-
const type = getColumnType(fieldConfig, fieldName);
|
|
212
|
-
const builder = db.schema.alterTable(table.table).addColumn(fieldName, type, (col) => {
|
|
213
|
-
let c = col;
|
|
214
|
-
if (!fieldConfig.nullable) c = c.notNull();
|
|
215
|
-
if (fieldConfig.references) {
|
|
216
|
-
const refTable = fieldConfig.references.table;
|
|
217
|
-
const refColumn = fieldConfig.references.column;
|
|
218
|
-
c = c.references(`${refTable}.${refColumn}`).onDelete("cascade");
|
|
219
|
-
}
|
|
220
|
-
if (fieldConfig.unique) c = c.unique();
|
|
221
|
-
if (fieldConfig.default === "now()" && dbType !== "sqlite") if (dbType === "mysql") c = c.defaultTo(sql`CURRENT_TIMESTAMP(3)`);
|
|
222
|
-
else c = c.defaultTo(sql`CURRENT_TIMESTAMP`);
|
|
223
|
-
return c;
|
|
224
|
-
});
|
|
225
|
-
migrations.push(builder);
|
|
226
|
-
}
|
|
227
|
-
async function runMigrations() {
|
|
228
|
-
for (const migration of migrations) await migration.execute();
|
|
229
|
-
}
|
|
230
|
-
async function compileMigrations() {
|
|
231
|
-
return migrations.map((m) => m.compile().sql).join(";\n\n") + ";";
|
|
232
|
-
}
|
|
233
|
-
return {
|
|
234
|
-
toBeCreated,
|
|
235
|
-
toBeAdded,
|
|
236
|
-
runMigrations,
|
|
237
|
-
compileMigrations,
|
|
238
|
-
migrations
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
|
|
242
22
|
//#endregion
|
|
243
23
|
//#region src/commands/migrate.ts
|
|
244
24
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llmops/cli",
|
|
3
|
-
"version": "0.1.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "LLMOps CLI - A pluggable LLMOps toolkit for TypeScript teams",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"kysely": "^0.28.8",
|
|
46
46
|
"prompts": "^2.4.2",
|
|
47
47
|
"yocto-spinner": "^1.0.0",
|
|
48
|
-
"@llmops/core": "0.1.0
|
|
49
|
-
"@llmops/sdk": "0.1.0
|
|
48
|
+
"@llmops/core": "^0.1.0",
|
|
49
|
+
"@llmops/sdk": "^0.1.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/pg": "^8.15.6",
|