aiex-cli 0.1.0 → 0.1.1-beta.2
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/README.md +8 -29
- package/dist/cli.mjs +434 -483
- package/dist/{generate-drizzle-schema-BqSy0OMm.mjs → generate-drizzle-schema-sZ01QiYJ.mjs} +139 -75
- package/dist/index.d.mts +18 -2
- package/dist/index.mjs +1 -1
- package/dist/table-schema.json +37 -9
- package/dist/web/assets/{AISettings-CxoghpZX.js → AISettings-CRumcTjo.js} +10 -10
- package/dist/web/assets/{DataBrowser-BqmnFDWC.js → DataBrowser-BqRaZJrX.js} +2 -2
- package/dist/web/assets/{ExtractionViewer-Bs8c6xa2.js → ExtractionViewer-lbdQGv2F.js} +1 -1
- package/dist/web/assets/{JsonSchemaEditor-ITVm2zG1.js → JsonSchemaEditor-BywWmkGm.js} +9 -9
- package/dist/web/assets/api-client-CpqFbcyH.js +1 -0
- package/dist/web/assets/{index-DhL7jaO_.js → index-7jWPnI_e.js} +8 -8
- package/dist/web/assets/index-BiNFY3ky.css +2 -0
- package/dist/web/assets/object-utils-OmJ-hFrY.js +1 -0
- package/dist/web/index.html +4 -4
- package/dist/{zh-CN-B2yrInX9.mjs → zh-CN-BAGJklRp.mjs} +15 -56
- package/package.json +1 -1
- package/dist/web/assets/api-client-EJKabzZK.js +0 -1
- package/dist/web/assets/index-D0So2rJE.css +0 -2
- package/dist/web/assets/object-utils-CqCiBqJ4.js +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as package_default, C as DEFAULT_PROMPT_CONFIG, D as seedConfig, E as createConfig, O as description, S as DEFAULT_MINERU_CONFIG, T as PLACEHOLDER_TEXT, _ as doctorDiagnosticsSeverityRows, a as recognizeImageText, b as DEFAULT_LITEPARSE_CONFIG, c as t, d as writeAIConfig, f as AIConfigSchema, h as toSnakeCase, j as version, k as name, l as getDefaultAIConfig, m as parseJsonSchema, n as collectDoctorDiagnostics, o as shouldUseImageOcrFallback, p as JsonSchemaDefinitionSchema, r as createMigrationConfig, s as initI18n, t as generateDrizzleSchema, u as readAIConfig, v as doctorDiagnosticsTableRows, w as PLACEHOLDER_SCHEMA, x as DEFAULT_MINERU_API_CONFIG, y as formatDoctorDiagnosticsJson } from "./generate-drizzle-schema-
|
|
1
|
+
import { A as package_default, C as DEFAULT_PROMPT_CONFIG, D as seedConfig, E as createConfig, O as description, S as DEFAULT_MINERU_CONFIG, T as PLACEHOLDER_TEXT, _ as doctorDiagnosticsSeverityRows, a as recognizeImageText, b as DEFAULT_LITEPARSE_CONFIG, c as t, d as writeAIConfig, f as AIConfigSchema, h as toSnakeCase, j as version, k as name, l as getDefaultAIConfig, m as parseJsonSchema, n as collectDoctorDiagnostics, o as shouldUseImageOcrFallback, p as JsonSchemaDefinitionSchema, r as createMigrationConfig, s as initI18n, t as generateDrizzleSchema, u as readAIConfig, v as doctorDiagnosticsTableRows, w as PLACEHOLDER_SCHEMA, x as DEFAULT_MINERU_API_CONFIG, y as formatDoctorDiagnosticsJson } from "./generate-drizzle-schema-sZ01QiYJ.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import os from "node:os";
|
|
@@ -13,13 +13,12 @@ import { defineCommand, runMain } from "citty";
|
|
|
13
13
|
import { consola } from "consola";
|
|
14
14
|
import updateNotifier from "update-notifier";
|
|
15
15
|
import CliTable3 from "cli-table3";
|
|
16
|
-
import fs$1 from "node:fs";
|
|
17
16
|
import { confirm, intro, isCancel, outro, select, spinner, text } from "@clack/prompts";
|
|
18
17
|
import pc from "picocolors";
|
|
19
|
-
import
|
|
20
|
-
import * as XLSX from "xlsx";
|
|
18
|
+
import fs$1 from "node:fs";
|
|
21
19
|
import { TextDecoder, promisify } from "node:util";
|
|
22
20
|
import { fileTypeFromBuffer, fileTypeFromFile } from "file-type";
|
|
21
|
+
import { Buffer } from "node:buffer";
|
|
23
22
|
import { glob, globSync } from "tinyglobby";
|
|
24
23
|
import { extractText, getDocumentProxy, getMeta } from "unpdf";
|
|
25
24
|
import AdmZip from "adm-zip";
|
|
@@ -144,260 +143,9 @@ const doctorCommand = defineCommand({
|
|
|
144
143
|
}
|
|
145
144
|
});
|
|
146
145
|
|
|
147
|
-
//#endregion
|
|
148
|
-
//#region src/application/export/export-manager.ts
|
|
149
|
-
function formatRowsConformingToSchema(rows, columns, schema, format) {
|
|
150
|
-
return rows.map((row) => {
|
|
151
|
-
const newRow = {};
|
|
152
|
-
columns.forEach((col) => {
|
|
153
|
-
const colName = col.name;
|
|
154
|
-
const val = row[colName];
|
|
155
|
-
const type = (schema?.properties?.[colName])?.type || "";
|
|
156
|
-
if (val === null || val === void 0) newRow[colName] = "";
|
|
157
|
-
else if (type === "boolean") if (format === "xlsx") newRow[colName] = val === 1 || val === "1" || val === true;
|
|
158
|
-
else newRow[colName] = val === 1 || val === "1" || val === true ? "true" : "false";
|
|
159
|
-
else if (type === "number" || type === "integer") if (val === "") newRow[colName] = "";
|
|
160
|
-
else {
|
|
161
|
-
const num = Number(val);
|
|
162
|
-
newRow[colName] = Number.isNaN(num) ? val : num;
|
|
163
|
-
}
|
|
164
|
-
else if (typeof val === "object") newRow[colName] = JSON.stringify(val);
|
|
165
|
-
else {
|
|
166
|
-
const dbType = (col.type || "").toLowerCase();
|
|
167
|
-
if ((dbType.includes("int") || dbType.includes("real") || dbType.includes("num") || dbType.includes("double") || dbType.includes("float")) && typeof val === "string" && val !== "") {
|
|
168
|
-
const num = Number(val);
|
|
169
|
-
newRow[colName] = Number.isNaN(num) ? val : num;
|
|
170
|
-
} else newRow[colName] = val;
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
return newRow;
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
function generateExportBuffer(tableName, formattedRows, columns, format) {
|
|
177
|
-
const ws = XLSX.utils.json_to_sheet(formattedRows, { header: columns.map((col) => col.name) });
|
|
178
|
-
if (format === "xlsx") {
|
|
179
|
-
const wb = XLSX.utils.book_new();
|
|
180
|
-
XLSX.utils.book_append_sheet(wb, ws, tableName.slice(0, 31));
|
|
181
|
-
return XLSX.write(wb, {
|
|
182
|
-
bookType: "xlsx",
|
|
183
|
-
type: "buffer"
|
|
184
|
-
});
|
|
185
|
-
} else {
|
|
186
|
-
const csv = XLSX.utils.sheet_to_csv(ws);
|
|
187
|
-
return Buffer.from("" + csv, "utf8");
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
//#endregion
|
|
192
|
-
//#region src/application/schema/load-schema.ts
|
|
193
|
-
const JSON_EXT_RE$1 = /\.json$/;
|
|
194
|
-
async function loadSchema(config, schemaName) {
|
|
195
|
-
const schemaPath = path.join(config.schemaPath, `${schemaName}.json`);
|
|
196
|
-
try {
|
|
197
|
-
const parsed = await readFile(schemaPath);
|
|
198
|
-
return { schema: JsonSchemaDefinitionSchema.parse(parsed) };
|
|
199
|
-
} catch (e) {
|
|
200
|
-
if (e instanceof ZodError) return {
|
|
201
|
-
schema: null,
|
|
202
|
-
error: t("errors.schema.validationFailed", {
|
|
203
|
-
name: `${schemaName}.json`,
|
|
204
|
-
issues: e.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n")
|
|
205
|
-
})
|
|
206
|
-
};
|
|
207
|
-
if (e.code === "ENOENT") return {
|
|
208
|
-
schema: null,
|
|
209
|
-
error: t("errors.schema.cannotRead", { name: `${schemaName}.json` })
|
|
210
|
-
};
|
|
211
|
-
if (e instanceof SyntaxError) return {
|
|
212
|
-
schema: null,
|
|
213
|
-
error: t("errors.schema.invalidJson", { name: `${schemaName}.json` })
|
|
214
|
-
};
|
|
215
|
-
return {
|
|
216
|
-
schema: null,
|
|
217
|
-
error: String(e)
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
async function listSchemas(aiexDir) {
|
|
222
|
-
try {
|
|
223
|
-
const dir = path.join(aiexDir, "schema");
|
|
224
|
-
return (await fs.readdir(dir)).filter((f) => f.endsWith(".json")).map((f) => f.replace(JSON_EXT_RE$1, "")).sort();
|
|
225
|
-
} catch {
|
|
226
|
-
return [];
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
//#endregion
|
|
231
|
-
//#region src/commands/utils.ts
|
|
232
|
-
function failCommand(message) {
|
|
233
|
-
if (message) consola.error(message);
|
|
234
|
-
outro(t("common.failed"));
|
|
235
|
-
process.exitCode = 1;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
//#endregion
|
|
239
|
-
//#region src/commands/dump.ts
|
|
240
|
-
const dumpCommand = defineCommand({
|
|
241
|
-
meta: {
|
|
242
|
-
name: "dump",
|
|
243
|
-
description: t("command.dump.description")
|
|
244
|
-
},
|
|
245
|
-
args: {
|
|
246
|
-
table: {
|
|
247
|
-
type: "string",
|
|
248
|
-
alias: "t",
|
|
249
|
-
description: t("command.dump.args.table")
|
|
250
|
-
},
|
|
251
|
-
schema: {
|
|
252
|
-
type: "string",
|
|
253
|
-
alias: "s",
|
|
254
|
-
description: t("command.dump.args.schema")
|
|
255
|
-
},
|
|
256
|
-
format: {
|
|
257
|
-
type: "string",
|
|
258
|
-
alias: "f",
|
|
259
|
-
description: t("command.dump.args.format")
|
|
260
|
-
},
|
|
261
|
-
output: {
|
|
262
|
-
type: "string",
|
|
263
|
-
alias: "o",
|
|
264
|
-
description: t("command.dump.args.output")
|
|
265
|
-
}
|
|
266
|
-
},
|
|
267
|
-
async run({ args }) {
|
|
268
|
-
intro(pc.inverse(" aiex dump "));
|
|
269
|
-
await initI18n();
|
|
270
|
-
if (!args.table && !args.schema) {
|
|
271
|
-
failCommand(t("command.dump.errors.tableOrSchemaRequired"));
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
const cwd = process.cwd();
|
|
275
|
-
const config = createMigrationConfig(cwd);
|
|
276
|
-
const schemaDir = config.schemaPath;
|
|
277
|
-
let tableName = args.table || "";
|
|
278
|
-
let schema = null;
|
|
279
|
-
if (args.schema) {
|
|
280
|
-
const schemaLoad = await loadSchema(config, args.schema);
|
|
281
|
-
if (!schemaLoad.schema) {
|
|
282
|
-
failCommand(schemaLoad.error || t("command.dump.errors.schemaNotFound", { name: args.schema }));
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
schema = schemaLoad.schema;
|
|
286
|
-
const tName = schema.table?.name;
|
|
287
|
-
if (!tName) {
|
|
288
|
-
failCommand(t("command.dump.errors.noTableName", { name: args.schema }));
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
if (tableName && tableName !== tName) {
|
|
292
|
-
failCommand(t("command.dump.errors.tableMismatch", {
|
|
293
|
-
table: tableName,
|
|
294
|
-
schemaTable: tName
|
|
295
|
-
}));
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
tableName = tName;
|
|
299
|
-
} else try {
|
|
300
|
-
if (fs$1.existsSync(schemaDir)) {
|
|
301
|
-
const files = fs$1.readdirSync(schemaDir).filter((f) => f.endsWith(".json"));
|
|
302
|
-
for (const file of files) {
|
|
303
|
-
const s$1 = await readFile(path.join(schemaDir, file));
|
|
304
|
-
if (s$1.table?.name === tableName) {
|
|
305
|
-
schema = s$1;
|
|
306
|
-
break;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
} catch {}
|
|
311
|
-
let format = args.format?.toLowerCase();
|
|
312
|
-
const outputPathArg = args.output;
|
|
313
|
-
if (outputPathArg) {
|
|
314
|
-
const ext = path.extname(outputPathArg).toLowerCase();
|
|
315
|
-
if (!format) {
|
|
316
|
-
if (ext === ".xlsx") format = "xlsx";
|
|
317
|
-
else if (ext === ".csv") format = "csv";
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
if (!format) format = "csv";
|
|
321
|
-
if (format !== "csv" && format !== "xlsx") {
|
|
322
|
-
failCommand(t("command.dump.errors.unsupportedFormat", { format }));
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
const resolvedOutput = outputPathArg ? path.resolve(outputPathArg) : path.resolve(cwd, `${tableName}.${format}`);
|
|
326
|
-
if (!fs$1.existsSync(config.databasePath)) {
|
|
327
|
-
failCommand(t("command.dump.errors.dbNotFound", {
|
|
328
|
-
path: config.databasePath,
|
|
329
|
-
cmd: "aiex schema"
|
|
330
|
-
}));
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
const s = spinner();
|
|
334
|
-
s.start(t("command.dump.loading", { name: tableName }));
|
|
335
|
-
let columns = [];
|
|
336
|
-
let rows = [];
|
|
337
|
-
try {
|
|
338
|
-
const db = new Database(config.databasePath, { readonly: true });
|
|
339
|
-
if (!db.prepare(`
|
|
340
|
-
select name from sqlite_master
|
|
341
|
-
where type = 'table' and name = ?
|
|
342
|
-
`).get(tableName)) {
|
|
343
|
-
s.stop(t("command.dump.dbQueryFailed"));
|
|
344
|
-
failCommand(t("command.dump.errors.tableNotFound", {
|
|
345
|
-
name: tableName,
|
|
346
|
-
cmd: "aiex schema"
|
|
347
|
-
}));
|
|
348
|
-
db.close();
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
columns = db.pragma(`table_info(${tableName})`);
|
|
352
|
-
rows = db.prepare(`select * from ${tableName}`).all();
|
|
353
|
-
db.close();
|
|
354
|
-
} catch (error) {
|
|
355
|
-
s.stop(t("command.dump.dbQueryFailed"));
|
|
356
|
-
failCommand(error instanceof Error ? error.message : String(error));
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
if (rows.length === 0) {
|
|
360
|
-
s.stop(t("command.dump.emptyTable"));
|
|
361
|
-
consola.warn(t("command.dump.errors.tableEmpty", { name: tableName }));
|
|
362
|
-
} else s.stop(t("command.dump.loaded", { count: rows.length }));
|
|
363
|
-
const s2 = spinner();
|
|
364
|
-
s2.start(t("command.dump.formatting"));
|
|
365
|
-
let formattedRows;
|
|
366
|
-
try {
|
|
367
|
-
formattedRows = formatRowsConformingToSchema(rows, columns, schema, format);
|
|
368
|
-
s2.stop(t("command.dump.formatted"));
|
|
369
|
-
} catch (error) {
|
|
370
|
-
s2.stop(t("command.dump.dbQueryFailed"));
|
|
371
|
-
failCommand(error instanceof Error ? error.message : String(error));
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
const s3 = spinner();
|
|
375
|
-
s3.start(t("command.dump.writing", {
|
|
376
|
-
format: format.toUpperCase(),
|
|
377
|
-
path: resolvedOutput
|
|
378
|
-
}));
|
|
379
|
-
try {
|
|
380
|
-
const buffer = generateExportBuffer(tableName, formattedRows, columns, format);
|
|
381
|
-
const outputDir = path.dirname(resolvedOutput);
|
|
382
|
-
if (!fs$1.existsSync(outputDir)) fs$1.mkdirSync(outputDir, { recursive: true });
|
|
383
|
-
fs$1.writeFileSync(resolvedOutput, buffer);
|
|
384
|
-
s3.stop(t("command.dump.dumpCompleted"));
|
|
385
|
-
consola.success(t("command.dump.successMsg", {
|
|
386
|
-
count: rows.length,
|
|
387
|
-
path: pc.cyan(resolvedOutput)
|
|
388
|
-
}));
|
|
389
|
-
} catch (error) {
|
|
390
|
-
s3.stop(t("command.dump.fileWriteFailed"));
|
|
391
|
-
failCommand(error instanceof Error ? error.message : String(error));
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
outro(t("common.done"));
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
|
|
398
146
|
//#endregion
|
|
399
147
|
//#region src/application/extraction/quality.ts
|
|
400
|
-
function formatInputProcessing
|
|
148
|
+
function formatInputProcessing(input) {
|
|
401
149
|
const handler = input.converter ? `${input.handler}(${input.converter})` : input.handler;
|
|
402
150
|
return `${input.mime ?? input.kind} -> ${handler}`;
|
|
403
151
|
}
|
|
@@ -1414,16 +1162,16 @@ function getFileHash(filePath) {
|
|
|
1414
1162
|
//#endregion
|
|
1415
1163
|
//#region src/domain/ai-extraction/evidence.ts
|
|
1416
1164
|
const NUMERIC_RE = /^-?\d+(?:\.\d+)?$/;
|
|
1417
|
-
function isRecord$
|
|
1165
|
+
function isRecord$3(value) {
|
|
1418
1166
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1419
1167
|
}
|
|
1420
1168
|
function stripEvidence(data) {
|
|
1421
|
-
if (!isRecord$
|
|
1169
|
+
if (!isRecord$3(data)) return { data };
|
|
1422
1170
|
const { _evidence, ...businessData } = data;
|
|
1423
|
-
if (!isRecord$
|
|
1171
|
+
if (!isRecord$3(_evidence)) return { data: businessData };
|
|
1424
1172
|
return {
|
|
1425
1173
|
data: businessData,
|
|
1426
|
-
rawEvidence: Object.fromEntries(Object.entries(_evidence).filter(([, value]) => isRecord$
|
|
1174
|
+
rawEvidence: Object.fromEntries(Object.entries(_evidence).filter(([, value]) => isRecord$3(value)).map(([field, value]) => [field, value]))
|
|
1427
1175
|
};
|
|
1428
1176
|
}
|
|
1429
1177
|
function findExactUnique(text$1, quote) {
|
|
@@ -1455,7 +1203,7 @@ function quoteContainsValue(quote, value, property) {
|
|
|
1455
1203
|
return false;
|
|
1456
1204
|
}
|
|
1457
1205
|
function verifyFieldEvidence(input) {
|
|
1458
|
-
if (!input.text || !isRecord$
|
|
1206
|
+
if (!input.text || !isRecord$3(input.data) || !input.rawEvidence) return void 0;
|
|
1459
1207
|
const verified = {};
|
|
1460
1208
|
for (const [field, raw] of Object.entries(input.rawEvidence)) {
|
|
1461
1209
|
const property = input.schema.properties[field];
|
|
@@ -1689,7 +1437,7 @@ function propertyToExtractionSchema(property) {
|
|
|
1689
1437
|
}
|
|
1690
1438
|
return { type: nullableType(property.type) };
|
|
1691
1439
|
}
|
|
1692
|
-
function isRecord$
|
|
1440
|
+
function isRecord$2(value) {
|
|
1693
1441
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1694
1442
|
}
|
|
1695
1443
|
function schemaToExtractionOutputSchema(schema) {
|
|
@@ -1727,7 +1475,7 @@ function validatePropertyValue(path$1, property, value, issues) {
|
|
|
1727
1475
|
}
|
|
1728
1476
|
return;
|
|
1729
1477
|
case "object":
|
|
1730
|
-
if (!isRecord$
|
|
1478
|
+
if (!isRecord$2(value)) {
|
|
1731
1479
|
issues.push(`${path$1}: expected object or null`);
|
|
1732
1480
|
return;
|
|
1733
1481
|
}
|
|
@@ -1750,7 +1498,7 @@ function validateProperties(basePath, properties, data, issues) {
|
|
|
1750
1498
|
}
|
|
1751
1499
|
}
|
|
1752
1500
|
function validateExtractedData(schema, data) {
|
|
1753
|
-
if (!isRecord$
|
|
1501
|
+
if (!isRecord$2(data)) return {
|
|
1754
1502
|
success: false,
|
|
1755
1503
|
error: "Extracted data must be a JSON object."
|
|
1756
1504
|
};
|
|
@@ -1771,11 +1519,11 @@ const EVIDENCE_INSTRUCTIONS = `Evidence requirements:
|
|
|
1771
1519
|
- The quote must be an exact contiguous substring copied from the input text.
|
|
1772
1520
|
- Do not invent offsets. Only provide quotes.
|
|
1773
1521
|
- If no exact quote supports a field, omit that field from "_evidence".`;
|
|
1774
|
-
function isRecord(value) {
|
|
1522
|
+
function isRecord$1(value) {
|
|
1775
1523
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1776
1524
|
}
|
|
1777
1525
|
function withEvidenceSchema(schema) {
|
|
1778
|
-
const properties = isRecord(schema.properties) ? schema.properties : {};
|
|
1526
|
+
const properties = isRecord$1(schema.properties) ? schema.properties : {};
|
|
1779
1527
|
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
1780
1528
|
return {
|
|
1781
1529
|
...schema,
|
|
@@ -2166,6 +1914,45 @@ Please output the corrected JSON object now:`;
|
|
|
2166
1914
|
}
|
|
2167
1915
|
}
|
|
2168
1916
|
|
|
1917
|
+
//#endregion
|
|
1918
|
+
//#region src/application/schema/load-schema.ts
|
|
1919
|
+
const JSON_EXT_RE$1 = /\.json$/;
|
|
1920
|
+
async function loadSchema(config, schemaName) {
|
|
1921
|
+
const schemaPath = path.join(config.schemaPath, `${schemaName}.json`);
|
|
1922
|
+
try {
|
|
1923
|
+
const parsed = await readFile(schemaPath);
|
|
1924
|
+
return { schema: JsonSchemaDefinitionSchema.parse(parsed) };
|
|
1925
|
+
} catch (e) {
|
|
1926
|
+
if (e instanceof ZodError) return {
|
|
1927
|
+
schema: null,
|
|
1928
|
+
error: t("errors.schema.validationFailed", {
|
|
1929
|
+
name: `${schemaName}.json`,
|
|
1930
|
+
issues: e.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n")
|
|
1931
|
+
})
|
|
1932
|
+
};
|
|
1933
|
+
if (e.code === "ENOENT") return {
|
|
1934
|
+
schema: null,
|
|
1935
|
+
error: t("errors.schema.cannotRead", { name: `${schemaName}.json` })
|
|
1936
|
+
};
|
|
1937
|
+
if (e instanceof SyntaxError) return {
|
|
1938
|
+
schema: null,
|
|
1939
|
+
error: t("errors.schema.invalidJson", { name: `${schemaName}.json` })
|
|
1940
|
+
};
|
|
1941
|
+
return {
|
|
1942
|
+
schema: null,
|
|
1943
|
+
error: String(e)
|
|
1944
|
+
};
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
async function listSchemas(aiexDir) {
|
|
1948
|
+
try {
|
|
1949
|
+
const dir = path.join(aiexDir, "schema");
|
|
1950
|
+
return (await fs.readdir(dir)).filter((f) => f.endsWith(".json")).map((f) => f.replace(JSON_EXT_RE$1, "")).sort();
|
|
1951
|
+
} catch {
|
|
1952
|
+
return [];
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
|
|
2169
1956
|
//#endregion
|
|
2170
1957
|
//#region src/infrastructure/extraction/insert-extracted-data.ts
|
|
2171
1958
|
function convertValue(value, column) {
|
|
@@ -2511,7 +2298,7 @@ async function runAuditedExtraction(options) {
|
|
|
2511
2298
|
filePath = input.filePath;
|
|
2512
2299
|
inputProcessing = input.inputProcessing;
|
|
2513
2300
|
inputQuality = input.quality;
|
|
2514
|
-
if (!quiet) consola.info(`Input: ${formatInputProcessing
|
|
2301
|
+
if (!quiet) consola.info(`Input: ${formatInputProcessing(inputProcessing)}`);
|
|
2515
2302
|
await updateExtractionAuditRecord(aiexDir, audit.id, {
|
|
2516
2303
|
inputProcessing,
|
|
2517
2304
|
quality: inputQuality
|
|
@@ -2719,36 +2506,15 @@ async function runBatchExtraction(aiexDir, config, aiConfig, schemaName, dir, gl
|
|
|
2719
2506
|
}
|
|
2720
2507
|
|
|
2721
2508
|
//#endregion
|
|
2722
|
-
//#region src/commands/
|
|
2723
|
-
function
|
|
2724
|
-
if (
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
}
|
|
2728
|
-
function isExtractSubCommand(rawArgs) {
|
|
2729
|
-
if (!Array.isArray(rawArgs)) return false;
|
|
2730
|
-
return rawArgs.some((arg) => typeof arg === "string" && [
|
|
2731
|
-
"history",
|
|
2732
|
-
"show",
|
|
2733
|
-
"retry",
|
|
2734
|
-
"rm"
|
|
2735
|
-
].includes(arg));
|
|
2736
|
-
}
|
|
2737
|
-
function formatSource(source) {
|
|
2738
|
-
return source.type === "file" ? source.fileName || "file" : "unknown";
|
|
2739
|
-
}
|
|
2740
|
-
function formatInputProcessing(input) {
|
|
2741
|
-
if (!input) return "";
|
|
2742
|
-
const handler = input.converter ? `${input.handler}(${input.converter})` : input.handler;
|
|
2743
|
-
return ` [${input.mime ?? input.kind} -> ${handler}]`;
|
|
2744
|
-
}
|
|
2745
|
-
function formatQuality(quality, failureStage) {
|
|
2746
|
-
if (failureStage) return ` [failed:${failureStage}]`;
|
|
2747
|
-
if (quality?.input?.pdf) return ` [pdf:${quality.input.pdf.pageCount}p/${quality.input.pdf.textLength}chars${quality.input.pdf.fallbackUsed ? "/fallback" : ""}]`;
|
|
2748
|
-
if (quality?.input?.ocr) return ` [ocr:${Math.round(quality.input.ocr.confidence * 100)}%/${quality.input.ocr.textLength}chars]`;
|
|
2749
|
-
if (quality?.ai?.missingFieldRate !== void 0) return ` [missing:${Math.round(quality.ai.missingFieldRate * 100)}%]`;
|
|
2750
|
-
return "";
|
|
2509
|
+
//#region src/commands/utils.ts
|
|
2510
|
+
function failCommand(message) {
|
|
2511
|
+
if (message) consola.error(message);
|
|
2512
|
+
outro(t("common.failed"));
|
|
2513
|
+
process.exitCode = 1;
|
|
2751
2514
|
}
|
|
2515
|
+
|
|
2516
|
+
//#endregion
|
|
2517
|
+
//#region src/commands/extract.ts
|
|
2752
2518
|
async function loadConfiguredAI(aiexDir) {
|
|
2753
2519
|
const aiConfig = await readAIConfig(aiexDir);
|
|
2754
2520
|
if (!aiConfig) {
|
|
@@ -2777,143 +2543,11 @@ function resolveModelOverride(aiConfig, modelName) {
|
|
|
2777
2543
|
}
|
|
2778
2544
|
return matched;
|
|
2779
2545
|
}
|
|
2780
|
-
const historyCommand = defineCommand({
|
|
2781
|
-
meta: {
|
|
2782
|
-
name: "history",
|
|
2783
|
-
description: t("command.extract.history.description")
|
|
2784
|
-
},
|
|
2785
|
-
async run() {
|
|
2786
|
-
const config = createMigrationConfig(process.cwd());
|
|
2787
|
-
const records = await listExtractionAuditRecords(path.dirname(config.schemaPath));
|
|
2788
|
-
if (records.length === 0) {
|
|
2789
|
-
consola.info(t("command.extract.history.empty"));
|
|
2790
|
-
return;
|
|
2791
|
-
}
|
|
2792
|
-
for (const record of records) {
|
|
2793
|
-
const suffix = record.error ? ` — ${record.error}` : record.outputName ? ` — ${record.outputName}` : "";
|
|
2794
|
-
consola.info(`${record.status.padEnd(9)} ${record.id} ${record.schemaName} ${formatSource(record.source)}${formatInputProcessing(record.inputProcessing)}${formatQuality(record.quality, record.failureStage)}${suffix}`);
|
|
2795
|
-
}
|
|
2796
|
-
}
|
|
2797
|
-
});
|
|
2798
|
-
const showCommand = defineCommand({
|
|
2799
|
-
meta: {
|
|
2800
|
-
name: "show",
|
|
2801
|
-
description: t("command.extract.show.description")
|
|
2802
|
-
},
|
|
2803
|
-
args: { id: {
|
|
2804
|
-
type: "string",
|
|
2805
|
-
description: t("command.extract.show.args.id")
|
|
2806
|
-
} },
|
|
2807
|
-
async run({ args }) {
|
|
2808
|
-
const id = getIdArg(args);
|
|
2809
|
-
if (!id) {
|
|
2810
|
-
failCommand(t("command.extract.history.errors.idRequired"));
|
|
2811
|
-
return;
|
|
2812
|
-
}
|
|
2813
|
-
const config = createMigrationConfig(process.cwd());
|
|
2814
|
-
const record = await readExtractionAuditRecord(path.dirname(config.schemaPath), id);
|
|
2815
|
-
if (!record) {
|
|
2816
|
-
failCommand(t("command.extract.history.errors.recordNotFound", { id }));
|
|
2817
|
-
return;
|
|
2818
|
-
}
|
|
2819
|
-
consola.info(JSON.stringify(record, null, 2));
|
|
2820
|
-
}
|
|
2821
|
-
});
|
|
2822
|
-
const retryCommand = defineCommand({
|
|
2823
|
-
meta: {
|
|
2824
|
-
name: "retry",
|
|
2825
|
-
description: t("command.extract.retry.description")
|
|
2826
|
-
},
|
|
2827
|
-
args: {
|
|
2828
|
-
id: {
|
|
2829
|
-
type: "string",
|
|
2830
|
-
description: t("command.extract.retry.args.id")
|
|
2831
|
-
},
|
|
2832
|
-
noInsert: {
|
|
2833
|
-
type: "boolean",
|
|
2834
|
-
description: t("command.extract.retry.args.noInsert"),
|
|
2835
|
-
default: false
|
|
2836
|
-
}
|
|
2837
|
-
},
|
|
2838
|
-
async run({ args }) {
|
|
2839
|
-
intro(pc.inverse(" aiex extract retry "));
|
|
2840
|
-
await initI18n();
|
|
2841
|
-
const id = getIdArg(args);
|
|
2842
|
-
if (!id) {
|
|
2843
|
-
failCommand(t("command.extract.history.errors.idRequired"));
|
|
2844
|
-
return;
|
|
2845
|
-
}
|
|
2846
|
-
const config = createMigrationConfig(process.cwd());
|
|
2847
|
-
const aiexDir = path.dirname(config.schemaPath);
|
|
2848
|
-
const record = await readExtractionAuditRecord(aiexDir, id);
|
|
2849
|
-
if (!record) {
|
|
2850
|
-
failCommand(t("command.extract.history.errors.recordNotFound", { id }));
|
|
2851
|
-
return;
|
|
2852
|
-
}
|
|
2853
|
-
const aiConfig = await loadConfiguredAI(aiexDir);
|
|
2854
|
-
if (!aiConfig) return;
|
|
2855
|
-
const modelOverride = resolveModelOverride(aiConfig, record.modelName);
|
|
2856
|
-
if (modelOverride === null) return;
|
|
2857
|
-
try {
|
|
2858
|
-
const result = await runAuditedExtraction({
|
|
2859
|
-
aiexDir,
|
|
2860
|
-
config,
|
|
2861
|
-
aiConfig,
|
|
2862
|
-
schemaName: record.schemaName,
|
|
2863
|
-
source: record.source,
|
|
2864
|
-
modelOverride,
|
|
2865
|
-
retryOf: record.id,
|
|
2866
|
-
insert: !args.noInsert,
|
|
2867
|
-
force: true
|
|
2868
|
-
});
|
|
2869
|
-
if (!result.success) {
|
|
2870
|
-
failCommand(result.error);
|
|
2871
|
-
return;
|
|
2872
|
-
}
|
|
2873
|
-
outro(t("common.done"));
|
|
2874
|
-
} catch (error) {
|
|
2875
|
-
if (isMissingUploadFileError(error)) {
|
|
2876
|
-
failCommand(MISSING_UPLOAD_FILE_TEXT);
|
|
2877
|
-
return;
|
|
2878
|
-
}
|
|
2879
|
-
failCommand(error instanceof Error ? error.message : String(error));
|
|
2880
|
-
}
|
|
2881
|
-
}
|
|
2882
|
-
});
|
|
2883
|
-
const rmCommand = defineCommand({
|
|
2884
|
-
meta: {
|
|
2885
|
-
name: "rm",
|
|
2886
|
-
description: t("command.extract.rm.description")
|
|
2887
|
-
},
|
|
2888
|
-
args: { id: {
|
|
2889
|
-
type: "string",
|
|
2890
|
-
description: t("command.extract.rm.args.id")
|
|
2891
|
-
} },
|
|
2892
|
-
async run({ args }) {
|
|
2893
|
-
const id = getIdArg(args);
|
|
2894
|
-
if (!id) {
|
|
2895
|
-
failCommand(t("command.extract.history.errors.idRequired"));
|
|
2896
|
-
return;
|
|
2897
|
-
}
|
|
2898
|
-
const config = createMigrationConfig(process.cwd());
|
|
2899
|
-
if (!await deleteExtractionAuditRecord(path.dirname(config.schemaPath), id)) {
|
|
2900
|
-
failCommand(t("command.extract.history.errors.recordNotFound", { id }));
|
|
2901
|
-
return;
|
|
2902
|
-
}
|
|
2903
|
-
consola.success(t("command.extract.history.deleted", { id }));
|
|
2904
|
-
}
|
|
2905
|
-
});
|
|
2906
2546
|
const extractCommand = defineCommand({
|
|
2907
2547
|
meta: {
|
|
2908
2548
|
name: "extract",
|
|
2909
2549
|
description: t("command.extract.description")
|
|
2910
2550
|
},
|
|
2911
|
-
subCommands: {
|
|
2912
|
-
history: historyCommand,
|
|
2913
|
-
show: showCommand,
|
|
2914
|
-
retry: retryCommand,
|
|
2915
|
-
rm: rmCommand
|
|
2916
|
-
},
|
|
2917
2551
|
args: {
|
|
2918
2552
|
schema: {
|
|
2919
2553
|
type: "string",
|
|
@@ -2951,8 +2585,7 @@ const extractCommand = defineCommand({
|
|
|
2951
2585
|
default: false
|
|
2952
2586
|
}
|
|
2953
2587
|
},
|
|
2954
|
-
async run({ args
|
|
2955
|
-
if (isExtractSubCommand(rawArgs)) return;
|
|
2588
|
+
async run({ args }) {
|
|
2956
2589
|
intro(pc.inverse(" aiex extract "));
|
|
2957
2590
|
await initI18n();
|
|
2958
2591
|
const config = createMigrationConfig(process.cwd());
|
|
@@ -3033,7 +2666,7 @@ async function runInteractive(aiexDir, config, aiConfig, modelOverride) {
|
|
|
3033
2666
|
}))
|
|
3034
2667
|
});
|
|
3035
2668
|
if (isCancel(schemaName)) {
|
|
3036
|
-
cancel(t("common.cancelled"));
|
|
2669
|
+
cancel$1(t("common.cancelled"));
|
|
3037
2670
|
return false;
|
|
3038
2671
|
}
|
|
3039
2672
|
const inputSource = await select({
|
|
@@ -3049,7 +2682,7 @@ async function runInteractive(aiexDir, config, aiConfig, modelOverride) {
|
|
|
3049
2682
|
}]
|
|
3050
2683
|
});
|
|
3051
2684
|
if (isCancel(inputSource)) {
|
|
3052
|
-
cancel(t("common.cancelled"));
|
|
2685
|
+
cancel$1(t("common.cancelled"));
|
|
3053
2686
|
return false;
|
|
3054
2687
|
}
|
|
3055
2688
|
if (inputSource === "file") {
|
|
@@ -3060,7 +2693,7 @@ async function runInteractive(aiexDir, config, aiConfig, modelOverride) {
|
|
|
3060
2693
|
}
|
|
3061
2694
|
});
|
|
3062
2695
|
if (isCancel(filePathStr)) {
|
|
3063
|
-
cancel(t("common.cancelled"));
|
|
2696
|
+
cancel$1(t("common.cancelled"));
|
|
3064
2697
|
return false;
|
|
3065
2698
|
}
|
|
3066
2699
|
const fp = filePathStr;
|
|
@@ -3069,7 +2702,7 @@ async function runInteractive(aiexDir, config, aiConfig, modelOverride) {
|
|
|
3069
2702
|
initialValue: false
|
|
3070
2703
|
});
|
|
3071
2704
|
if (isCancel(force)) {
|
|
3072
|
-
cancel(t("common.cancelled"));
|
|
2705
|
+
cancel$1(t("common.cancelled"));
|
|
3073
2706
|
return false;
|
|
3074
2707
|
}
|
|
3075
2708
|
return (await runAuditedExtraction({
|
|
@@ -3092,7 +2725,7 @@ async function runInteractive(aiexDir, config, aiConfig, modelOverride) {
|
|
|
3092
2725
|
}
|
|
3093
2726
|
});
|
|
3094
2727
|
if (isCancel(dirPath)) {
|
|
3095
|
-
cancel(t("common.cancelled"));
|
|
2728
|
+
cancel$1(t("common.cancelled"));
|
|
3096
2729
|
return false;
|
|
3097
2730
|
}
|
|
3098
2731
|
const force = await confirm({
|
|
@@ -3100,7 +2733,7 @@ async function runInteractive(aiexDir, config, aiConfig, modelOverride) {
|
|
|
3100
2733
|
initialValue: false
|
|
3101
2734
|
});
|
|
3102
2735
|
if (isCancel(force)) {
|
|
3103
|
-
cancel(t("common.cancelled"));
|
|
2736
|
+
cancel$1(t("common.cancelled"));
|
|
3104
2737
|
return false;
|
|
3105
2738
|
}
|
|
3106
2739
|
const result = await runBatchExtraction(aiexDir, config, aiConfig, schemaName, dirPath, void 0, modelOverride, { force });
|
|
@@ -3109,12 +2742,79 @@ async function runInteractive(aiexDir, config, aiConfig, modelOverride) {
|
|
|
3109
2742
|
}
|
|
3110
2743
|
return false;
|
|
3111
2744
|
}
|
|
3112
|
-
function cancel(msg) {
|
|
2745
|
+
function cancel$1(msg) {
|
|
3113
2746
|
consola.info(msg);
|
|
3114
2747
|
outro(t("common.cancelled"));
|
|
3115
2748
|
process.exitCode = 0;
|
|
3116
2749
|
}
|
|
3117
2750
|
|
|
2751
|
+
//#endregion
|
|
2752
|
+
//#region src/domain/schema/migration-risk.ts
|
|
2753
|
+
function keyOf(entry) {
|
|
2754
|
+
return `${entry.table}.${entry.column}`;
|
|
2755
|
+
}
|
|
2756
|
+
function maxSeverity(items) {
|
|
2757
|
+
if (items.some((item) => item.severity === "high")) return "high";
|
|
2758
|
+
if (items.some((item) => item.severity === "medium")) return "medium";
|
|
2759
|
+
if (items.some((item) => item.severity === "low")) return "low";
|
|
2760
|
+
return "none";
|
|
2761
|
+
}
|
|
2762
|
+
function addRisk(items, severity, kind, table, column, message) {
|
|
2763
|
+
items.push({
|
|
2764
|
+
severity,
|
|
2765
|
+
kind,
|
|
2766
|
+
table,
|
|
2767
|
+
column,
|
|
2768
|
+
message
|
|
2769
|
+
});
|
|
2770
|
+
}
|
|
2771
|
+
function enumValues(entry) {
|
|
2772
|
+
return new Set((entry.constraints?.enumValues ?? []).map((value) => JSON.stringify(value)));
|
|
2773
|
+
}
|
|
2774
|
+
function isEnumNarrowed(previous, next) {
|
|
2775
|
+
const prev = enumValues(previous);
|
|
2776
|
+
const current = enumValues(next);
|
|
2777
|
+
if (prev.size === 0 || current.size === 0 || current.size >= prev.size) return false;
|
|
2778
|
+
return [...current].every((value) => prev.has(value));
|
|
2779
|
+
}
|
|
2780
|
+
function enumChanged(previous, next) {
|
|
2781
|
+
const prev = enumValues(previous);
|
|
2782
|
+
const current = enumValues(next);
|
|
2783
|
+
if (prev.size === 0 && current.size === 0) return false;
|
|
2784
|
+
if (prev.size !== current.size) return true;
|
|
2785
|
+
return [...prev].some((value) => !current.has(value));
|
|
2786
|
+
}
|
|
2787
|
+
function analyzeMigrationRisk(previousEntries, nextEntries) {
|
|
2788
|
+
const items = [];
|
|
2789
|
+
const previousByKey = new Map(previousEntries.map((entry) => [keyOf(entry), entry]));
|
|
2790
|
+
const nextByKey = new Map(nextEntries.map((entry) => [keyOf(entry), entry]));
|
|
2791
|
+
const previousTables = new Set(previousEntries.map((entry) => entry.table));
|
|
2792
|
+
const nextTables = new Set(nextEntries.map((entry) => entry.table));
|
|
2793
|
+
for (const table of previousTables) if (!nextTables.has(table)) addRisk(items, "high", "table_removed", table, void 0, `Table "${table}" will be removed.`);
|
|
2794
|
+
for (const table of nextTables) if (!previousTables.has(table)) addRisk(items, "low", "table_added", table, void 0, `Table "${table}" will be added.`);
|
|
2795
|
+
for (const [key, previous] of previousByKey) {
|
|
2796
|
+
const next = nextByKey.get(key);
|
|
2797
|
+
if (!next) {
|
|
2798
|
+
addRisk(items, "high", "column_removed", previous.table, previous.column, `Column "${key}" will be removed.`);
|
|
2799
|
+
continue;
|
|
2800
|
+
}
|
|
2801
|
+
if (previous.sqliteType !== next.sqliteType || previous.drizzleType !== next.drizzleType) addRisk(items, "high", "column_type_changed", previous.table, previous.column, `Column "${key}" type changes from ${previous.drizzleType} to ${next.drizzleType}.`);
|
|
2802
|
+
if (previous.nullable && !next.nullable) addRisk(items, "high", "nullable_tightened", previous.table, previous.column, `Column "${key}" changes from nullable to not null.`);
|
|
2803
|
+
else if (!previous.nullable && next.nullable) addRisk(items, "medium", "nullable_relaxed", previous.table, previous.column, `Column "${key}" changes from not null to nullable.`);
|
|
2804
|
+
if (!previous.unique && next.unique) addRisk(items, "high", "unique_added", previous.table, previous.column, `Column "${key}" adds a unique constraint.`);
|
|
2805
|
+
if (previous.primary !== next.primary) addRisk(items, "high", "primary_changed", previous.table, previous.column, `Column "${key}" primary key status changes.`);
|
|
2806
|
+
if (isEnumNarrowed(previous, next)) addRisk(items, "high", "enum_narrowed", previous.table, previous.column, `Column "${key}" enum values are narrowed.`);
|
|
2807
|
+
else if (enumChanged(previous, next)) addRisk(items, "medium", "enum_changed", previous.table, previous.column, `Column "${key}" enum values change.`);
|
|
2808
|
+
}
|
|
2809
|
+
for (const [key, next] of nextByKey) if (!previousByKey.has(key)) addRisk(items, next.nullable ? "low" : "medium", "column_added", next.table, next.column, `Column "${key}" will be added.`);
|
|
2810
|
+
const level = maxSeverity(items);
|
|
2811
|
+
return {
|
|
2812
|
+
level,
|
|
2813
|
+
items,
|
|
2814
|
+
hasHighRisk: level === "high"
|
|
2815
|
+
};
|
|
2816
|
+
}
|
|
2817
|
+
|
|
3118
2818
|
//#endregion
|
|
3119
2819
|
//#region src/infrastructure/runtime/package-paths.ts
|
|
3120
2820
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -3139,6 +2839,102 @@ function resolveHelperPath() {
|
|
|
3139
2839
|
}
|
|
3140
2840
|
}
|
|
3141
2841
|
|
|
2842
|
+
//#endregion
|
|
2843
|
+
//#region src/domain/schema/dialect.ts
|
|
2844
|
+
const TOP_LEVEL_KEYS = new Set([
|
|
2845
|
+
"$schema",
|
|
2846
|
+
"title",
|
|
2847
|
+
"description",
|
|
2848
|
+
"type",
|
|
2849
|
+
"table",
|
|
2850
|
+
"properties",
|
|
2851
|
+
"required",
|
|
2852
|
+
"examples"
|
|
2853
|
+
]);
|
|
2854
|
+
const PROPERTY_KEYS = new Set([
|
|
2855
|
+
"description",
|
|
2856
|
+
"type",
|
|
2857
|
+
"format",
|
|
2858
|
+
"pattern",
|
|
2859
|
+
"enum",
|
|
2860
|
+
"primary",
|
|
2861
|
+
"autoIncrement",
|
|
2862
|
+
"unique",
|
|
2863
|
+
"default",
|
|
2864
|
+
"maxLength",
|
|
2865
|
+
"minLength",
|
|
2866
|
+
"minimum",
|
|
2867
|
+
"maximum",
|
|
2868
|
+
"examples",
|
|
2869
|
+
"xPrompt",
|
|
2870
|
+
"drizzle",
|
|
2871
|
+
"nested",
|
|
2872
|
+
"foreignKey",
|
|
2873
|
+
"properties",
|
|
2874
|
+
"items",
|
|
2875
|
+
"required"
|
|
2876
|
+
]);
|
|
2877
|
+
const DRIZZLE_KEYS = new Set(["mode", "customType"]);
|
|
2878
|
+
const NESTED_KEYS = new Set(["enabled", "relation"]);
|
|
2879
|
+
const FOREIGN_KEY_KEYS = new Set(["table", "column"]);
|
|
2880
|
+
const TABLE_KEYS = new Set([
|
|
2881
|
+
"name",
|
|
2882
|
+
"timestamps",
|
|
2883
|
+
"softDelete"
|
|
2884
|
+
]);
|
|
2885
|
+
const UNSUPPORTED_JSON_SCHEMA_KEYWORDS = new Set([
|
|
2886
|
+
"oneOf",
|
|
2887
|
+
"anyOf",
|
|
2888
|
+
"allOf",
|
|
2889
|
+
"not",
|
|
2890
|
+
"if",
|
|
2891
|
+
"then",
|
|
2892
|
+
"else",
|
|
2893
|
+
"const",
|
|
2894
|
+
"contains",
|
|
2895
|
+
"prefixItems",
|
|
2896
|
+
"additionalItems",
|
|
2897
|
+
"additionalProperties",
|
|
2898
|
+
"patternProperties",
|
|
2899
|
+
"propertyNames",
|
|
2900
|
+
"dependentRequired",
|
|
2901
|
+
"dependentSchemas",
|
|
2902
|
+
"dependencies",
|
|
2903
|
+
"unevaluatedItems",
|
|
2904
|
+
"unevaluatedProperties",
|
|
2905
|
+
"multipleOf",
|
|
2906
|
+
"exclusiveMinimum",
|
|
2907
|
+
"exclusiveMaximum"
|
|
2908
|
+
]);
|
|
2909
|
+
function isRecord(value) {
|
|
2910
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2911
|
+
}
|
|
2912
|
+
function warnUnsupportedKey(warnings, path$1, key) {
|
|
2913
|
+
const reason = UNSUPPORTED_JSON_SCHEMA_KEYWORDS.has(key) ? "is not part of the AIEX Drizzle-backed schema dialect and cannot be mapped reliably" : "is not recognized by the AIEX Drizzle-backed schema dialect";
|
|
2914
|
+
warnings.push(`${path$1}.${key} ${reason}.`);
|
|
2915
|
+
}
|
|
2916
|
+
function inspectRecordKeys(warnings, value, path$1, allowedKeys) {
|
|
2917
|
+
if (!isRecord(value)) return;
|
|
2918
|
+
for (const key of Object.keys(value)) if (!allowedKeys.has(key)) warnUnsupportedKey(warnings, path$1, key);
|
|
2919
|
+
}
|
|
2920
|
+
function inspectProperty(warnings, property, path$1) {
|
|
2921
|
+
if (!isRecord(property)) return;
|
|
2922
|
+
inspectRecordKeys(warnings, property, path$1, PROPERTY_KEYS);
|
|
2923
|
+
inspectRecordKeys(warnings, property.drizzle, `${path$1}.drizzle`, DRIZZLE_KEYS);
|
|
2924
|
+
inspectRecordKeys(warnings, property.nested, `${path$1}.nested`, NESTED_KEYS);
|
|
2925
|
+
inspectRecordKeys(warnings, property.foreignKey, `${path$1}.foreignKey`, FOREIGN_KEY_KEYS);
|
|
2926
|
+
if (isRecord(property.properties)) for (const [name$1, child] of Object.entries(property.properties)) inspectProperty(warnings, child, `${path$1}.properties.${name$1}`);
|
|
2927
|
+
if (property.items) inspectProperty(warnings, property.items, `${path$1}.items`);
|
|
2928
|
+
}
|
|
2929
|
+
function collectDialectWarnings(schema, filePath) {
|
|
2930
|
+
if (!isRecord(schema)) return [];
|
|
2931
|
+
const warnings = [];
|
|
2932
|
+
inspectRecordKeys(warnings, schema, filePath, TOP_LEVEL_KEYS);
|
|
2933
|
+
inspectRecordKeys(warnings, schema.table, `${filePath}.table`, TABLE_KEYS);
|
|
2934
|
+
if (isRecord(schema.properties)) for (const [name$1, property] of Object.entries(schema.properties)) inspectProperty(warnings, property, `${filePath}.properties.${name$1}`);
|
|
2935
|
+
return warnings;
|
|
2936
|
+
}
|
|
2937
|
+
|
|
3142
2938
|
//#endregion
|
|
3143
2939
|
//#region src/application/schema/parse-all-schemas.ts
|
|
3144
2940
|
function formatZodError(error, filePath) {
|
|
@@ -3149,6 +2945,7 @@ function parseAllSchemas(entries) {
|
|
|
3149
2945
|
const relations = [];
|
|
3150
2946
|
const reverseRelations = [];
|
|
3151
2947
|
const warnings = [];
|
|
2948
|
+
const mapping = [];
|
|
3152
2949
|
for (const { filePath, content } of entries) {
|
|
3153
2950
|
let schema;
|
|
3154
2951
|
try {
|
|
@@ -3160,11 +2957,16 @@ function parseAllSchemas(entries) {
|
|
|
3160
2957
|
};
|
|
3161
2958
|
}
|
|
3162
2959
|
try {
|
|
2960
|
+
warnings.push(...collectDialectWarnings(schema, filePath));
|
|
3163
2961
|
const result = parseJsonSchema(JsonSchemaDefinitionSchema.parse(schema));
|
|
3164
2962
|
tables.push(...result.tables);
|
|
3165
2963
|
relations.push(...result.relations);
|
|
3166
2964
|
reverseRelations.push(...result.reverseRelations);
|
|
3167
2965
|
warnings.push(...result.warnings);
|
|
2966
|
+
mapping.push(...(result.mapping ?? []).map((entry) => ({
|
|
2967
|
+
...entry,
|
|
2968
|
+
schemaPath: `${filePath}${entry.schemaPath.slice(1)}`
|
|
2969
|
+
})));
|
|
3168
2970
|
} catch (e) {
|
|
3169
2971
|
if (e instanceof ZodError) return {
|
|
3170
2972
|
success: false,
|
|
@@ -3180,6 +2982,7 @@ function parseAllSchemas(entries) {
|
|
|
3180
2982
|
relations,
|
|
3181
2983
|
reverseRelations,
|
|
3182
2984
|
warnings,
|
|
2985
|
+
mapping,
|
|
3183
2986
|
drizzleCode: generateDrizzleSchema({
|
|
3184
2987
|
tables,
|
|
3185
2988
|
relations,
|
|
@@ -3193,6 +2996,24 @@ function parseAllSchemas(entries) {
|
|
|
3193
2996
|
//#endregion
|
|
3194
2997
|
//#region src/application/schema/schema-sync.ts
|
|
3195
2998
|
const execFileAsync = promisify(execFile);
|
|
2999
|
+
const NO_RISK_REPORT = {
|
|
3000
|
+
level: "none",
|
|
3001
|
+
items: [],
|
|
3002
|
+
hasHighRisk: false
|
|
3003
|
+
};
|
|
3004
|
+
function schemaMapPath(config) {
|
|
3005
|
+
return path.join(path.dirname(config.drizzleSchemaPath), "schema-map.json");
|
|
3006
|
+
}
|
|
3007
|
+
async function readPreviousSchemaMap(config) {
|
|
3008
|
+
try {
|
|
3009
|
+
const content = await fs.readFile(schemaMapPath(config), "utf-8");
|
|
3010
|
+
const parsed = JSON.parse(content);
|
|
3011
|
+
if (Array.isArray(parsed.baselineEntries)) return parsed.baselineEntries;
|
|
3012
|
+
return Array.isArray(parsed.entries) ? parsed.entries : [];
|
|
3013
|
+
} catch {
|
|
3014
|
+
return [];
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3196
3017
|
async function listSchemaFiles(schemaDir) {
|
|
3197
3018
|
try {
|
|
3198
3019
|
return (await fs.readdir(schemaDir)).filter((f) => f.endsWith(".json")).map((f) => path.join(schemaDir, f)).sort();
|
|
@@ -3200,7 +3021,8 @@ async function listSchemaFiles(schemaDir) {
|
|
|
3200
3021
|
return [];
|
|
3201
3022
|
}
|
|
3202
3023
|
}
|
|
3203
|
-
async function generateSchemaFromFiles(schemaFiles, config) {
|
|
3024
|
+
async function generateSchemaFromFiles(schemaFiles, config, options = {}) {
|
|
3025
|
+
const previousMapping = await readPreviousSchemaMap(config);
|
|
3204
3026
|
const result = parseAllSchemas(await Promise.all(schemaFiles.map(async (filePath) => {
|
|
3205
3027
|
return {
|
|
3206
3028
|
filePath,
|
|
@@ -3213,17 +3035,30 @@ async function generateSchemaFromFiles(schemaFiles, config) {
|
|
|
3213
3035
|
warnings: [],
|
|
3214
3036
|
schemaCount: schemaFiles.length,
|
|
3215
3037
|
tables: 0,
|
|
3216
|
-
relations: 0
|
|
3038
|
+
relations: 0,
|
|
3039
|
+
mappingEntries: 0,
|
|
3040
|
+
riskReport: NO_RISK_REPORT
|
|
3217
3041
|
};
|
|
3218
|
-
const { tables, relations, reverseRelations, warnings, drizzleCode } = result.data;
|
|
3042
|
+
const { tables, relations, reverseRelations, warnings, mapping, drizzleCode } = result.data;
|
|
3043
|
+
const riskReport = previousMapping.length > 0 ? analyzeMigrationRisk(previousMapping, mapping) : NO_RISK_REPORT;
|
|
3219
3044
|
await fs.mkdir(path.dirname(config.drizzleSchemaPath), { recursive: true });
|
|
3220
3045
|
await fs.writeFile(config.drizzleSchemaPath, drizzleCode);
|
|
3046
|
+
await fs.writeFile(schemaMapPath(config), `${JSON.stringify({
|
|
3047
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3048
|
+
dialect: "aiex-drizzle-sqlite",
|
|
3049
|
+
entries: mapping,
|
|
3050
|
+
baselineEntries: riskReport.hasHighRisk && !options.force ? previousMapping : void 0,
|
|
3051
|
+
warnings,
|
|
3052
|
+
migrationRisk: riskReport
|
|
3053
|
+
}, null, 2)}\n`);
|
|
3221
3054
|
return {
|
|
3222
3055
|
success: true,
|
|
3223
3056
|
warnings,
|
|
3224
3057
|
schemaCount: schemaFiles.length,
|
|
3225
3058
|
tables: tables.length,
|
|
3226
|
-
relations: relations.length + reverseRelations.length
|
|
3059
|
+
relations: relations.length + reverseRelations.length,
|
|
3060
|
+
mappingEntries: mapping.length,
|
|
3061
|
+
riskReport
|
|
3227
3062
|
};
|
|
3228
3063
|
}
|
|
3229
3064
|
function parseMigrationOutput(stdout, stderr) {
|
|
@@ -3275,24 +3110,34 @@ async function runSchemaSync(config, options = {}) {
|
|
|
3275
3110
|
warnings: [],
|
|
3276
3111
|
schemaCount: 0,
|
|
3277
3112
|
tables: 0,
|
|
3278
|
-
relations: 0
|
|
3113
|
+
relations: 0,
|
|
3114
|
+
mappingEntries: 0,
|
|
3115
|
+
riskReport: NO_RISK_REPORT
|
|
3279
3116
|
};
|
|
3280
|
-
const generated = await generateSchemaFromFiles(schemaFiles, config);
|
|
3117
|
+
const generated = await generateSchemaFromFiles(schemaFiles, config, { force: options.force });
|
|
3281
3118
|
if (!generated.success) return {
|
|
3282
3119
|
success: false,
|
|
3283
3120
|
error: generated.error,
|
|
3284
3121
|
warnings: generated.warnings,
|
|
3285
3122
|
schemaCount: generated.schemaCount,
|
|
3286
3123
|
tables: generated.tables,
|
|
3287
|
-
relations: generated.relations
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
success: true,
|
|
3291
|
-
warnings: generated.warnings,
|
|
3292
|
-
schemaCount: generated.schemaCount,
|
|
3293
|
-
tables: generated.tables,
|
|
3294
|
-
relations: generated.relations
|
|
3124
|
+
relations: generated.relations,
|
|
3125
|
+
mappingEntries: generated.mappingEntries,
|
|
3126
|
+
riskReport: generated.riskReport
|
|
3295
3127
|
};
|
|
3128
|
+
if (options.generateOnly || generated.riskReport.hasHighRisk && !options.force) {
|
|
3129
|
+
const blockedByRisk = generated.riskReport.hasHighRisk && !options.force && !options.generateOnly;
|
|
3130
|
+
return {
|
|
3131
|
+
success: !blockedByRisk,
|
|
3132
|
+
error: blockedByRisk ? t("errors.schema.highRiskMigrationBlocked") : void 0,
|
|
3133
|
+
warnings: generated.warnings,
|
|
3134
|
+
schemaCount: generated.schemaCount,
|
|
3135
|
+
tables: generated.tables,
|
|
3136
|
+
relations: generated.relations,
|
|
3137
|
+
mappingEntries: generated.mappingEntries,
|
|
3138
|
+
riskReport: generated.riskReport
|
|
3139
|
+
};
|
|
3140
|
+
}
|
|
3296
3141
|
const migration = await runSchemaMigration(config, options.migrationName);
|
|
3297
3142
|
return {
|
|
3298
3143
|
success: migration.success,
|
|
@@ -3301,6 +3146,8 @@ async function runSchemaSync(config, options = {}) {
|
|
|
3301
3146
|
schemaCount: generated.schemaCount,
|
|
3302
3147
|
tables: generated.tables,
|
|
3303
3148
|
relations: generated.relations,
|
|
3149
|
+
mappingEntries: generated.mappingEntries,
|
|
3150
|
+
riskReport: generated.riskReport,
|
|
3304
3151
|
migration
|
|
3305
3152
|
};
|
|
3306
3153
|
}
|
|
@@ -3322,6 +3169,11 @@ const schemaCommand = defineCommand({
|
|
|
3322
3169
|
name: {
|
|
3323
3170
|
type: "string",
|
|
3324
3171
|
description: t("command.schema.args.name")
|
|
3172
|
+
},
|
|
3173
|
+
force: {
|
|
3174
|
+
type: "boolean",
|
|
3175
|
+
description: t("command.schema.args.force"),
|
|
3176
|
+
default: false
|
|
3325
3177
|
}
|
|
3326
3178
|
},
|
|
3327
3179
|
async run({ args }) {
|
|
@@ -3336,7 +3188,7 @@ const schemaCommand = defineCommand({
|
|
|
3336
3188
|
}
|
|
3337
3189
|
const s1 = spinner();
|
|
3338
3190
|
s1.start(t("command.schema.generating"));
|
|
3339
|
-
const generated = await generateSchemaFromFiles(schemaFiles, config);
|
|
3191
|
+
const generated = await generateSchemaFromFiles(schemaFiles, config, { force: args.force });
|
|
3340
3192
|
for (const warning of generated.warnings) consola.warn(warning);
|
|
3341
3193
|
if (generated.success) consola.success(t("command.schema.generated", {
|
|
3342
3194
|
path: pc.cyan(".aiex/drizzle/schema.ts"),
|
|
@@ -3348,10 +3200,21 @@ const schemaCommand = defineCommand({
|
|
|
3348
3200
|
failCommand(t("common.failed"));
|
|
3349
3201
|
return;
|
|
3350
3202
|
}
|
|
3203
|
+
if (generated.riskReport.items.length > 0) {
|
|
3204
|
+
consola.info(t("command.schema.riskSummary", {
|
|
3205
|
+
level: generated.riskReport.level,
|
|
3206
|
+
count: generated.riskReport.items.length
|
|
3207
|
+
}));
|
|
3208
|
+
for (const item of generated.riskReport.items) consola.warn(`[${item.severity}] ${item.message}`);
|
|
3209
|
+
}
|
|
3351
3210
|
if (args.generate) {
|
|
3352
3211
|
outro(t("command.schema.runWithoutGenerate"));
|
|
3353
3212
|
return;
|
|
3354
3213
|
}
|
|
3214
|
+
if (generated.riskReport.hasHighRisk && !args.force) {
|
|
3215
|
+
failCommand(t("command.schema.highRiskBlocked", { flag: pc.cyan("--force") }));
|
|
3216
|
+
return;
|
|
3217
|
+
}
|
|
3355
3218
|
const s2 = spinner();
|
|
3356
3219
|
s2.start(t("command.schema.runningMigrations"));
|
|
3357
3220
|
const migration = await runSchemaMigration(config, args.name);
|
|
@@ -3549,6 +3412,14 @@ const watchCommand = defineCommand({
|
|
|
3549
3412
|
async run({ args }) {
|
|
3550
3413
|
intro(pc.inverse(" aiex watch "));
|
|
3551
3414
|
await initI18n();
|
|
3415
|
+
const config = createMigrationConfig(process.cwd());
|
|
3416
|
+
const aiexDir = path.dirname(config.schemaPath);
|
|
3417
|
+
if (!args.schema && !args.dir && !args.model) {
|
|
3418
|
+
const aiConfig$1 = await loadConfiguredAI(aiexDir);
|
|
3419
|
+
if (!aiConfig$1) return;
|
|
3420
|
+
if (await runInteractiveWatch(aiexDir, config, aiConfig$1)) consola.info(t("command.watch.events.pressCtrlC"));
|
|
3421
|
+
return;
|
|
3422
|
+
}
|
|
3552
3423
|
if (!args.schema) {
|
|
3553
3424
|
failCommand(t("command.watch.errors.schemaRequired"));
|
|
3554
3425
|
return;
|
|
@@ -3557,33 +3428,18 @@ const watchCommand = defineCommand({
|
|
|
3557
3428
|
failCommand(t("command.watch.errors.dirRequired"));
|
|
3558
3429
|
return;
|
|
3559
3430
|
}
|
|
3560
|
-
const config = createMigrationConfig(process.cwd());
|
|
3561
|
-
const aiexDir = path.dirname(config.schemaPath);
|
|
3562
3431
|
const schemaLoad = await loadSchema(config, args.schema);
|
|
3563
3432
|
if (!schemaLoad.schema) {
|
|
3564
3433
|
failCommand(schemaLoad.error || t("command.watch.errors.schemaNotFound", { name: args.schema }));
|
|
3565
3434
|
return;
|
|
3566
3435
|
}
|
|
3567
|
-
let watchDirStat;
|
|
3568
|
-
try {
|
|
3569
|
-
watchDirStat = fs$1.statSync(args.dir);
|
|
3570
|
-
} catch (e) {
|
|
3571
|
-
failCommand(t("command.watch.errors.dirNotExist", {
|
|
3572
|
-
dir: args.dir,
|
|
3573
|
-
error: e instanceof Error ? e.message : String(e)
|
|
3574
|
-
}));
|
|
3575
|
-
return;
|
|
3576
|
-
}
|
|
3577
|
-
if (!watchDirStat.isDirectory()) {
|
|
3578
|
-
failCommand(t("command.watch.errors.notADirectory", { dir: args.dir }));
|
|
3579
|
-
return;
|
|
3580
|
-
}
|
|
3581
3436
|
const watchDirAbs = path.resolve(args.dir);
|
|
3437
|
+
if (!validateWatchDir(watchDirAbs)) return;
|
|
3582
3438
|
const aiConfig = await loadConfiguredAI(aiexDir);
|
|
3583
3439
|
if (!aiConfig) return;
|
|
3584
3440
|
const modelOverride = resolveModelOverride(aiConfig, args.model);
|
|
3585
3441
|
if (modelOverride === null) return;
|
|
3586
|
-
|
|
3442
|
+
registerCleanup(startWatch({
|
|
3587
3443
|
aiexDir,
|
|
3588
3444
|
config,
|
|
3589
3445
|
aiConfig,
|
|
@@ -3591,18 +3447,112 @@ const watchCommand = defineCommand({
|
|
|
3591
3447
|
watchDir: watchDirAbs,
|
|
3592
3448
|
modelOverride,
|
|
3593
3449
|
insert: !args.noInsert
|
|
3594
|
-
});
|
|
3595
|
-
const cleanup = async () => {
|
|
3596
|
-
consola.info(t("command.watch.events.stopped"));
|
|
3597
|
-
await watcher.close();
|
|
3598
|
-
consola.success(t("command.watch.events.stoppedOk"));
|
|
3599
|
-
process.exit(0);
|
|
3600
|
-
};
|
|
3601
|
-
process.on("SIGINT", cleanup);
|
|
3602
|
-
process.on("SIGTERM", cleanup);
|
|
3450
|
+
}));
|
|
3603
3451
|
consola.info(t("command.watch.events.pressCtrlC"));
|
|
3604
3452
|
}
|
|
3605
3453
|
});
|
|
3454
|
+
async function runInteractiveWatch(aiexDir, config, aiConfig) {
|
|
3455
|
+
const schemas = await listSchemas(aiexDir);
|
|
3456
|
+
if (schemas.length === 0) {
|
|
3457
|
+
failCommand(t("command.extract.errors.noSchemas", {
|
|
3458
|
+
path: pc.cyan(".aiex/schema/"),
|
|
3459
|
+
cmd: pc.cyan("aiex web")
|
|
3460
|
+
}));
|
|
3461
|
+
return false;
|
|
3462
|
+
}
|
|
3463
|
+
const schemaName = await select({
|
|
3464
|
+
message: t("command.watch.interactive.selectSchema"),
|
|
3465
|
+
options: schemas.map((s) => ({
|
|
3466
|
+
label: s,
|
|
3467
|
+
value: s
|
|
3468
|
+
}))
|
|
3469
|
+
});
|
|
3470
|
+
if (isCancel(schemaName)) {
|
|
3471
|
+
cancel(t("common.cancelled"));
|
|
3472
|
+
return false;
|
|
3473
|
+
}
|
|
3474
|
+
const dirPath = await text({
|
|
3475
|
+
message: t("command.watch.interactive.enterDirPath"),
|
|
3476
|
+
validate(value) {
|
|
3477
|
+
if (!value || value.trim().length === 0) return t("command.watch.interactive.dirPathRequired");
|
|
3478
|
+
}
|
|
3479
|
+
});
|
|
3480
|
+
if (isCancel(dirPath)) {
|
|
3481
|
+
cancel(t("common.cancelled"));
|
|
3482
|
+
return false;
|
|
3483
|
+
}
|
|
3484
|
+
const selectedModel = await select({
|
|
3485
|
+
message: t("command.watch.interactive.selectModel"),
|
|
3486
|
+
options: [{
|
|
3487
|
+
label: t("command.watch.interactive.autoModel"),
|
|
3488
|
+
value: ""
|
|
3489
|
+
}, ...aiConfig.provider.models.map((model) => ({
|
|
3490
|
+
label: model.name,
|
|
3491
|
+
value: model.name
|
|
3492
|
+
}))]
|
|
3493
|
+
});
|
|
3494
|
+
if (isCancel(selectedModel)) {
|
|
3495
|
+
cancel(t("common.cancelled"));
|
|
3496
|
+
return false;
|
|
3497
|
+
}
|
|
3498
|
+
const noInsert = await confirm({
|
|
3499
|
+
message: t("command.watch.interactive.askNoInsert"),
|
|
3500
|
+
initialValue: false
|
|
3501
|
+
});
|
|
3502
|
+
if (isCancel(noInsert)) {
|
|
3503
|
+
cancel(t("common.cancelled"));
|
|
3504
|
+
return false;
|
|
3505
|
+
}
|
|
3506
|
+
const watchDir = path.resolve(dirPath);
|
|
3507
|
+
if (!validateWatchDir(watchDir)) return false;
|
|
3508
|
+
const modelOverride = resolveModelOverride(aiConfig, selectedModel ? selectedModel : void 0);
|
|
3509
|
+
if (modelOverride === null) return false;
|
|
3510
|
+
registerCleanup(startWatch({
|
|
3511
|
+
aiexDir,
|
|
3512
|
+
config,
|
|
3513
|
+
aiConfig,
|
|
3514
|
+
schemaName,
|
|
3515
|
+
watchDir,
|
|
3516
|
+
modelOverride,
|
|
3517
|
+
insert: !noInsert
|
|
3518
|
+
}));
|
|
3519
|
+
return true;
|
|
3520
|
+
}
|
|
3521
|
+
function validateWatchDir(dir) {
|
|
3522
|
+
let watchDirStat;
|
|
3523
|
+
try {
|
|
3524
|
+
watchDirStat = fs$1.statSync(dir);
|
|
3525
|
+
} catch (e) {
|
|
3526
|
+
failCommand(t("command.watch.errors.dirNotExist", {
|
|
3527
|
+
dir,
|
|
3528
|
+
error: e instanceof Error ? e.message : String(e)
|
|
3529
|
+
}));
|
|
3530
|
+
return false;
|
|
3531
|
+
}
|
|
3532
|
+
if (!watchDirStat.isDirectory()) {
|
|
3533
|
+
failCommand(t("command.watch.errors.notADirectory", { dir }));
|
|
3534
|
+
return false;
|
|
3535
|
+
}
|
|
3536
|
+
return true;
|
|
3537
|
+
}
|
|
3538
|
+
function startWatch(options) {
|
|
3539
|
+
return startWatcher(options);
|
|
3540
|
+
}
|
|
3541
|
+
function registerCleanup(watcher) {
|
|
3542
|
+
const cleanup = async () => {
|
|
3543
|
+
consola.info(t("command.watch.events.stopped"));
|
|
3544
|
+
await watcher.close();
|
|
3545
|
+
consola.success(t("command.watch.events.stoppedOk"));
|
|
3546
|
+
process.exit(0);
|
|
3547
|
+
};
|
|
3548
|
+
process.on("SIGINT", cleanup);
|
|
3549
|
+
process.on("SIGTERM", cleanup);
|
|
3550
|
+
}
|
|
3551
|
+
function cancel(msg) {
|
|
3552
|
+
consola.info(msg);
|
|
3553
|
+
outro(t("common.cancelled"));
|
|
3554
|
+
process.exitCode = 0;
|
|
3555
|
+
}
|
|
3606
3556
|
|
|
3607
3557
|
//#endregion
|
|
3608
3558
|
//#region src/domain/ai-extraction/model-capabilities.json
|
|
@@ -16928,12 +16878,13 @@ function schemaRoutes(config) {
|
|
|
16928
16878
|
app.post("/migrate", async (c) => {
|
|
16929
16879
|
try {
|
|
16930
16880
|
await ensureDir();
|
|
16931
|
-
const result = await runSchemaSync(config);
|
|
16881
|
+
const result = await runSchemaSync(config, { force: c.req.query("force") === "true" });
|
|
16932
16882
|
if (!result.success) {
|
|
16933
|
-
const status = result.schemaCount === 0 ? 400 : 500;
|
|
16883
|
+
const status = result.schemaCount === 0 ? 400 : result.riskReport.hasHighRisk ? 409 : 500;
|
|
16934
16884
|
return c.json({
|
|
16935
16885
|
success: false,
|
|
16936
|
-
error: result.error || t("server.migrationFailed")
|
|
16886
|
+
error: result.error || t("server.migrationFailed"),
|
|
16887
|
+
riskReport: result.riskReport
|
|
16937
16888
|
}, status);
|
|
16938
16889
|
}
|
|
16939
16890
|
return c.json({
|
|
@@ -16942,7 +16893,8 @@ function schemaRoutes(config) {
|
|
|
16942
16893
|
tag: result.migration?.tag,
|
|
16943
16894
|
tables: result.tables,
|
|
16944
16895
|
relations: result.relations,
|
|
16945
|
-
warnings: result.warnings
|
|
16896
|
+
warnings: result.warnings,
|
|
16897
|
+
riskReport: result.riskReport
|
|
16946
16898
|
});
|
|
16947
16899
|
} catch (error) {
|
|
16948
16900
|
return c.json({
|
|
@@ -17062,7 +17014,6 @@ const subCommands = {
|
|
|
17062
17014
|
schema: schemaCommand,
|
|
17063
17015
|
extract: extractCommand,
|
|
17064
17016
|
watch: watchCommand,
|
|
17065
|
-
dump: dumpCommand,
|
|
17066
17017
|
completion: completionCommand,
|
|
17067
17018
|
doctor: doctorCommand
|
|
17068
17019
|
};
|