aiex-cli 0.0.1-beta.26 → 0.0.1-beta.28
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 +13 -3
- package/dist/cli.mjs +691 -334
- package/dist/core/schema-sqlite/migrate-helper.mjs +1 -1
- package/dist/{doctor-lG3xWNvv.mjs → doctor-collector-D2q6iD_e.mjs} +233 -231
- package/dist/index.d.mts +14 -15
- package/dist/index.mjs +1 -1
- package/dist/web/assets/{AISettings-pnBUZsQ6.js → AISettings-DFi-nXIi.js} +9 -9
- package/dist/web/assets/DataBrowser-BWSX8O2h.js +5 -0
- package/dist/web/assets/ExtractionViewer-WMUdXeyU.js +1 -0
- package/dist/web/assets/JsonSchemaEditor-B57coz1O.js +929 -0
- package/dist/web/assets/api-client-By2rWtpv.js +1 -0
- package/dist/web/assets/button-Cdgr9Igy.js +927 -0
- package/dist/web/assets/{cssMode-BHaAN4pp.js → cssMode-BM5FOYIl.js} +1 -1
- package/dist/web/assets/dialog-dMXSeJQQ.js +108 -0
- package/dist/web/assets/{editor.main-BzaWETn1.js → editor.main-C2Q97Dkk.js} +2 -2
- package/dist/web/assets/{freemarker2-Dvg4gFZa.js → freemarker2-BqyJTCTn.js} +1 -1
- package/dist/web/assets/{handlebars-4itI_hGB.js → handlebars-DxRJTefg.js} +1 -1
- package/dist/web/assets/{html-CWSOAdTS.js → html-gyvgrapw.js} +1 -1
- package/dist/web/assets/{htmlMode-0upoBPJE.js → htmlMode-CNjCRwdY.js} +1 -1
- package/dist/web/assets/index-C9N8oWt4.css +2 -0
- package/dist/web/assets/{index-B0ePNkfC.js → index-CuOQk7nB.js} +38 -38
- package/dist/web/assets/{javascript-Ce8CZ78h.js → javascript-BK6ufvq6.js} +1 -1
- package/dist/web/assets/{jsonMode-BLSJZHAW.js → jsonMode-m2trGjkO.js} +1 -1
- package/dist/web/assets/{liquid-D5rz2UL9.js → liquid-BtyuYqQQ.js} +1 -1
- package/dist/web/assets/{mdx-z1JVBQDz.js → mdx-C8K4EvCQ.js} +1 -1
- package/dist/web/assets/{monaco.contribution-CFfFGfn5.js → monaco.contribution-BTr-G8hO.js} +2 -2
- package/dist/web/assets/{overlayeventbus-DTdph1rb.js → overlayeventbus-CRKW6UCj.js} +3 -3
- package/dist/web/assets/{python-BPJc-4Rv.js → python-8dyH1nS_.js} +1 -1
- package/dist/web/assets/{razor-BHU9dOiw.js → razor-DtWMI74k.js} +1 -1
- package/dist/web/assets/runtime-dom.esm-bundler-DmdkgxQM.js +1 -0
- package/dist/web/assets/{table-schema-Dqg6xhKC.js → table-schema-C90NJyfq.js} +1 -1
- package/dist/web/assets/{tsMode-Uy_NSojR.js → tsMode-Dv8YG-YK.js} +1 -1
- package/dist/web/assets/{typescript-BaLsBfTJ.js → typescript-DbClKYS3.js} +1 -1
- package/dist/web/assets/{xml-Dn0SxzSa.js → xml-Bb59gjP6.js} +1 -1
- package/dist/web/assets/{yaml-Bzmyj5Wb.js → yaml-DVMb_IfV.js} +1 -1
- package/dist/web/index.html +8 -7
- package/package.json +3 -1
- package/dist/web/assets/DataBrowser-X9E-XE0s.js +0 -5
- package/dist/web/assets/JsonSchemaEditor-LVbTMi5f.js +0 -929
- package/dist/web/assets/api-client-CjfczSUd.js +0 -1
- package/dist/web/assets/button-kTMweGMc.js +0 -927
- package/dist/web/assets/dialog-CWuu7WjI.js +0 -108
- package/dist/web/assets/index-CRcl3j1y.css +0 -2
- /package/dist/web/assets/{abap-B48h0VdJ.js → abap-DiwvWnMr.js} +0 -0
- /package/dist/web/assets/{apex-1DPyVnhQ.js → apex-CmtZjKlf.js} +0 -0
- /package/dist/web/assets/{azcli-BpwkVh9R.js → azcli-DL2My_i-.js} +0 -0
- /package/dist/web/assets/{bat-BwJAl50I.js → bat-B-nC98wG.js} +0 -0
- /package/dist/web/assets/{bicep-B6QlbnRF.js → bicep-Ju5MwOgh.js} +0 -0
- /package/dist/web/assets/{cameligo-CUPQxz1J.js → cameligo-8Eu1TyBr.js} +0 -0
- /package/dist/web/assets/{clojure-BlwtfO9v.js → clojure-u-RpMkH3.js} +0 -0
- /package/dist/web/assets/{coffee-BzDjX1gN.js → coffee-CdA7bbTe.js} +0 -0
- /package/dist/web/assets/{cpp-CM5j04eT.js → cpp-CzNFP8ks.js} +0 -0
- /package/dist/web/assets/{csharp-BqWJjCNe.js → csharp-j1LThmcE.js} +0 -0
- /package/dist/web/assets/{csp-C-n5jZKF.js → csp-CLRC61y6.js} +0 -0
- /package/dist/web/assets/{css-BRXHjK80.js → css-r6rC_7P2.js} +0 -0
- /package/dist/web/assets/{cypher-Ib4R9oAB.js → cypher-CW08XVUh.js} +0 -0
- /package/dist/web/assets/{dart-Bmn0CjCu.js → dart-Cs9aL5T_.js} +0 -0
- /package/dist/web/assets/{dockerfile-BJvHmfcK.js → dockerfile-BWM0M184.js} +0 -0
- /package/dist/web/assets/{ecl-C8Lr2n0s.js → ecl-MJJuer5P.js} +0 -0
- /package/dist/web/assets/{editor.api-DgeBt89R.js → editor.api-nsOUOZde.js} +0 -0
- /package/dist/web/assets/{elixir-GMkKEJvJ.js → elixir-D2AIuXqn.js} +0 -0
- /package/dist/web/assets/{flow9-BQbuvXMU.js → flow9-B2H24giC.js} +0 -0
- /package/dist/web/assets/{fsharp-CDI_AxQw.js → fsharp-CFNadkg7.js} +0 -0
- /package/dist/web/assets/{go-DmsC2k-Y.js → go-dSur1iB2.js} +0 -0
- /package/dist/web/assets/{graphql-C8hjT6Ki.js → graphql-qyhAo11d.js} +0 -0
- /package/dist/web/assets/{hcl-C15cAQOZ.js → hcl-DFzjMyzm.js} +0 -0
- /package/dist/web/assets/{ini-CKrAe0ag.js → ini-TdzA8TIl.js} +0 -0
- /package/dist/web/assets/{java-BVhjILyl.js → java-CSGA9pkE.js} +0 -0
- /package/dist/web/assets/{julia-BzPDHDOG.js → julia-9izz5OsY.js} +0 -0
- /package/dist/web/assets/{kotlin-DQMAn-b6.js → kotlin-DuPK7AtF.js} +0 -0
- /package/dist/web/assets/{less-428mfr1h.js → less-B8d93iCg.js} +0 -0
- /package/dist/web/assets/{lexon-B09dCO6A.js → lexon-DWtEIyu7.js} +0 -0
- /package/dist/web/assets/{lua-CVQ0BJif.js → lua-Ciq0OGgt.js} +0 -0
- /package/dist/web/assets/{m3-CiPQ1ljw.js → m3-Cki6JWj_.js} +0 -0
- /package/dist/web/assets/{markdown--G0dqL-7.js → markdown-Cu47xwU0.js} +0 -0
- /package/dist/web/assets/{mips-BaboCM3T.js → mips-BM8ui995.js} +0 -0
- /package/dist/web/assets/{msdax-DUaqkqre.js → msdax-DqLio0_c.js} +0 -0
- /package/dist/web/assets/{mysql-CUE6XF4r.js → mysql-v1wbjJOq.js} +0 -0
- /package/dist/web/assets/{objective-c-C4MUnzeT.js → objective-c-CQl3PGSB.js} +0 -0
- /package/dist/web/assets/{pascal-CWMUMx__.js → pascal-D4iW0ZtD.js} +0 -0
- /package/dist/web/assets/{pascaligo-DLCVutek.js → pascaligo-BdC9CZdj.js} +0 -0
- /package/dist/web/assets/{perl-JYoirQpx.js → perl-BL10m4XD.js} +0 -0
- /package/dist/web/assets/{pgsql-BqOy7sqx.js → pgsql-Be_oqVo3.js} +0 -0
- /package/dist/web/assets/{php-PZqsysO1.js → php-BtvXSFRI.js} +0 -0
- /package/dist/web/assets/{pla-BiwqVlg6.js → pla-B2vUy15C.js} +0 -0
- /package/dist/web/assets/{postiats-COxQtXCD.js → postiats-CbmTTfXr.js} +0 -0
- /package/dist/web/assets/{powerquery-DdXUmaWa.js → powerquery-DszLhJGx.js} +0 -0
- /package/dist/web/assets/{powershell-D05yu9sz.js → powershell-B0dYktF6.js} +0 -0
- /package/dist/web/assets/{preload-helper-DzyYoeor.js → preload-helper-DWTEM3RW.js} +0 -0
- /package/dist/web/assets/{protobuf-BDsm0ZB_.js → protobuf-CZvaj1VX.js} +0 -0
- /package/dist/web/assets/{pug-3CmTiGoi.js → pug-CPDx1B3S.js} +0 -0
- /package/dist/web/assets/{qsharp-CDUCQwQO.js → qsharp-CAxMZVjw.js} +0 -0
- /package/dist/web/assets/{r-Decg_RIU.js → r-8DbbFX2l.js} +0 -0
- /package/dist/web/assets/{redis-Cl3EBA4R.js → redis-DRWj9MtJ.js} +0 -0
- /package/dist/web/assets/{redshift-5ZsNLhOp.js → redshift-C6cElE_5.js} +0 -0
- /package/dist/web/assets/{restructuredtext-BulNNF_e.js → restructuredtext-W9pS9n3m.js} +0 -0
- /package/dist/web/assets/{ruby-D3Axi_9w.js → ruby-BKnzWnk-.js} +0 -0
- /package/dist/web/assets/{rust-Csys1Tos.js → rust-YPCclWwe.js} +0 -0
- /package/dist/web/assets/{sb-C_iBPphi.js → sb-BgM4DTFb.js} +0 -0
- /package/dist/web/assets/{scala-Cg4p-EZ2.js → scala-fz1OPLMl.js} +0 -0
- /package/dist/web/assets/{scheme-BlVnEL_j.js → scheme-8Uz1RIbu.js} +0 -0
- /package/dist/web/assets/{scss-CmLW8ojr.js → scss-Djo3IYXr.js} +0 -0
- /package/dist/web/assets/{shell-B1DV_gpl.js → shell-CINF5Tx_.js} +0 -0
- /package/dist/web/assets/{solidity-glFpNhe3.js → solidity-GgiNEuUm.js} +0 -0
- /package/dist/web/assets/{sophia-D9j4cFkA.js → sophia-Culj97P9.js} +0 -0
- /package/dist/web/assets/{sparql-DV5Ux9cO.js → sparql-C2ZlpxOY.js} +0 -0
- /package/dist/web/assets/{sql-K8tNKFcf.js → sql-BEf5Pg7Y.js} +0 -0
- /package/dist/web/assets/{st-BhIdE2hj.js → st-CT6UUoeH.js} +0 -0
- /package/dist/web/assets/{swift-B0pzSmmx.js → swift-B5g0xTG3.js} +0 -0
- /package/dist/web/assets/{systemverilog-CeBgixbN.js → systemverilog-CEgQz9DR.js} +0 -0
- /package/dist/web/assets/{tcl-B0Ji3IbZ.js → tcl-D0qL2L0I.js} +0 -0
- /package/dist/web/assets/{twig-KUgPCP41.js → twig-BFUAVf1E.js} +0 -0
- /package/dist/web/assets/{typespec-ryrhjid6.js → typespec-CjVVcNKm.js} +0 -0
- /package/dist/web/assets/{vb-Z68-YtMY.js → vb-CZJr-DQz.js} +0 -0
- /package/dist/web/assets/{wgsl-bH-W-d_T.js → wgsl-ivoXUo2e.js} +0 -0
package/dist/cli.mjs
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
import { C as formatDoctorDiagnosticsJson, S as doctorDiagnosticsTableRows, _ as
|
|
1
|
+
import { C as formatDoctorDiagnosticsJson, S as doctorDiagnosticsTableRows, _ as description, a as parseJsonSchema, b as version, c as getDefaultAIConfig, d as DEFAULT_PROMPT_CONFIG, f as PLACEHOLDER_SCHEMA, g as seedConfig, h as createConfig, i as JsonSchemaDefinitionSchema, l as readAIConfig, m as AIConfigSchema, n as createMigrationConfig, o as toSnakeCase, p as PLACEHOLDER_TEXT, s as generateDrizzleSchema, t as collectDoctorDiagnostics, u as writeAIConfig, v as name, y as package_default } from "./doctor-collector-D2q6iD_e.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import process from "node:process";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { ZodError } from "zod";
|
|
7
|
-
import
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { defineCommand, runMain } from "citty";
|
|
9
9
|
import { consola } from "consola";
|
|
10
10
|
import updateNotifier from "update-notifier";
|
|
11
11
|
import CliTable3 from "cli-table3";
|
|
12
|
-
import { intro, outro, spinner } from "@clack/prompts";
|
|
13
|
-
import Database from "better-sqlite3";
|
|
12
|
+
import { intro, isCancel, outro, select, spinner, text } from "@clack/prompts";
|
|
14
13
|
import pc from "picocolors";
|
|
15
14
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
16
15
|
import { LangfuseSpanProcessor } from "@langfuse/otel";
|
|
17
16
|
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
18
17
|
import { APICallError, Output, generateText, jsonSchema } from "ai";
|
|
18
|
+
import fs$1 from "node:fs";
|
|
19
|
+
import Database from "better-sqlite3";
|
|
20
|
+
import picomatch from "picomatch";
|
|
19
21
|
import { Buffer } from "node:buffer";
|
|
20
22
|
import { extractText, getMeta } from "unpdf";
|
|
21
|
-
import {
|
|
23
|
+
import { execFile } from "node:child_process";
|
|
22
24
|
import { promisify } from "node:util";
|
|
23
25
|
import { serve } from "@hono/node-server";
|
|
24
26
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
@@ -100,7 +102,7 @@ function parseAllSchemas(entries) {
|
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
//#endregion
|
|
103
|
-
//#region src/
|
|
105
|
+
//#region src/core/completion-scripts.ts
|
|
104
106
|
function bashScript(name$1) {
|
|
105
107
|
return `# ${name$1} bash completion
|
|
106
108
|
_${name$1}() {
|
|
@@ -127,7 +129,7 @@ function fishScript(name$1) {
|
|
|
127
129
|
complete -c ${name$1} -f -a '(${name$1} _complete (commandline -cp) 2>/dev/null)'
|
|
128
130
|
`;
|
|
129
131
|
}
|
|
130
|
-
function
|
|
132
|
+
function generateCompletionScript(name$1, shell) {
|
|
131
133
|
switch (shell) {
|
|
132
134
|
case "bash": return bashScript(name$1);
|
|
133
135
|
case "zsh": return zshScript(name$1);
|
|
@@ -135,6 +137,9 @@ function generateScript(name$1, shell) {
|
|
|
135
137
|
default: throw new Error(`Unsupported shell: ${shell}. Use bash, zsh, or fish.`);
|
|
136
138
|
}
|
|
137
139
|
}
|
|
140
|
+
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/commands/completion.ts
|
|
138
143
|
const completionCommand = defineCommand({
|
|
139
144
|
meta: {
|
|
140
145
|
name: "completion",
|
|
@@ -149,7 +154,7 @@ const completionCommand = defineCommand({
|
|
|
149
154
|
const name$1 = "aiex";
|
|
150
155
|
const shell = args.shell;
|
|
151
156
|
try {
|
|
152
|
-
process.stdout.write(
|
|
157
|
+
process.stdout.write(generateCompletionScript(name$1, shell));
|
|
153
158
|
} catch (error) {
|
|
154
159
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
155
160
|
process.exit(1);
|
|
@@ -189,6 +194,14 @@ const doctorCommand = defineCommand({
|
|
|
189
194
|
}
|
|
190
195
|
});
|
|
191
196
|
|
|
197
|
+
//#endregion
|
|
198
|
+
//#region src/commands/utils.ts
|
|
199
|
+
function failCommand(message) {
|
|
200
|
+
if (message) consola.error(message);
|
|
201
|
+
outro("Failed!");
|
|
202
|
+
process.exitCode = 1;
|
|
203
|
+
}
|
|
204
|
+
|
|
192
205
|
//#endregion
|
|
193
206
|
//#region src/core/ai-extraction/model-capabilities.json
|
|
194
207
|
var model_capabilities_default = {
|
|
@@ -12788,8 +12801,8 @@ async function withRetry(fn, onRetry, maxRetries = 5) {
|
|
|
12788
12801
|
|
|
12789
12802
|
//#endregion
|
|
12790
12803
|
//#region src/core/ai-extraction/json-utils.ts
|
|
12791
|
-
function stripFences(text) {
|
|
12792
|
-
const trimmed = text.trim();
|
|
12804
|
+
function stripFences(text$1) {
|
|
12805
|
+
const trimmed = text$1.trim();
|
|
12793
12806
|
if (!trimmed.startsWith("```")) return null;
|
|
12794
12807
|
const endIndex = trimmed.lastIndexOf("```");
|
|
12795
12808
|
if (endIndex <= 3) return null;
|
|
@@ -12798,8 +12811,8 @@ function stripFences(text) {
|
|
|
12798
12811
|
if (firstNewline === -1) return null;
|
|
12799
12812
|
return inside.slice(firstNewline + 1).trim();
|
|
12800
12813
|
}
|
|
12801
|
-
function extractFirstJSON(text) {
|
|
12802
|
-
const trimmed = text.trim();
|
|
12814
|
+
function extractFirstJSON(text$1) {
|
|
12815
|
+
const trimmed = text$1.trim();
|
|
12803
12816
|
const firstBrace = trimmed.indexOf("{");
|
|
12804
12817
|
const firstBracket = trimmed.indexOf("[");
|
|
12805
12818
|
let start = -1;
|
|
@@ -12810,8 +12823,8 @@ function extractFirstJSON(text) {
|
|
|
12810
12823
|
if (end <= start) return null;
|
|
12811
12824
|
return trimmed.slice(start, end);
|
|
12812
12825
|
}
|
|
12813
|
-
function safeParseJSON(text) {
|
|
12814
|
-
const cleaned = text.trim();
|
|
12826
|
+
function safeParseJSON(text$1) {
|
|
12827
|
+
const cleaned = text$1.trim();
|
|
12815
12828
|
try {
|
|
12816
12829
|
return JSON.parse(cleaned);
|
|
12817
12830
|
} catch {}
|
|
@@ -12823,7 +12836,7 @@ function safeParseJSON(text) {
|
|
|
12823
12836
|
if (extracted) try {
|
|
12824
12837
|
return JSON.parse(extracted);
|
|
12825
12838
|
} catch {}
|
|
12826
|
-
const truncated = text.length > 200 ? `${text.slice(0, 200)}...` : text;
|
|
12839
|
+
const truncated = text$1.length > 200 ? `${text$1.slice(0, 200)}...` : text$1;
|
|
12827
12840
|
throw new Error(`Failed to parse JSON from model output. Expected a valid JSON object or array but received unparseable text. Raw output: ${truncated}`);
|
|
12828
12841
|
}
|
|
12829
12842
|
|
|
@@ -12911,11 +12924,11 @@ function schemaToDescription(schema) {
|
|
|
12911
12924
|
}
|
|
12912
12925
|
return lines.join("\n");
|
|
12913
12926
|
}
|
|
12914
|
-
function generateExtractionPrompt(schema, text, promptConfig = DEFAULT_PROMPT_CONFIG) {
|
|
12927
|
+
function generateExtractionPrompt(schema, text$1, promptConfig = DEFAULT_PROMPT_CONFIG) {
|
|
12915
12928
|
const schemaDescription = schemaToDescription(schema);
|
|
12916
12929
|
return {
|
|
12917
12930
|
system: promptConfig.systemTemplate.replaceAll(PLACEHOLDER_SCHEMA, schemaDescription),
|
|
12918
|
-
user: promptConfig.userTemplate.replaceAll(PLACEHOLDER_TEXT, text)
|
|
12931
|
+
user: promptConfig.userTemplate.replaceAll(PLACEHOLDER_TEXT, text$1)
|
|
12919
12932
|
};
|
|
12920
12933
|
}
|
|
12921
12934
|
function generatePromptSnapshot(schema, promptConfig = DEFAULT_PROMPT_CONFIG) {
|
|
@@ -13098,14 +13111,14 @@ async function loadPromptSnapshot(aiexDir, tableName) {
|
|
|
13098
13111
|
return null;
|
|
13099
13112
|
}
|
|
13100
13113
|
async function extractStructuredData(input) {
|
|
13101
|
-
const { config, schema, text, aiexDir, file, modelOverride } = input;
|
|
13114
|
+
const { config, schema, text: text$1, aiexDir, file, modelOverride } = input;
|
|
13102
13115
|
if (!config.provider.apiKey) return {
|
|
13103
13116
|
success: false,
|
|
13104
13117
|
error: "API Key not configured. Please configure AI settings in the web UI."
|
|
13105
13118
|
};
|
|
13106
13119
|
const useFileContent = !!file;
|
|
13107
13120
|
const isImageFile = useFileContent && detectMimeType(file).startsWith("image/");
|
|
13108
|
-
const inputTokens = text ? Math.ceil(text.length / 2) : void 0;
|
|
13121
|
+
const inputTokens = text$1 ? Math.ceil(text$1.length / 2) : void 0;
|
|
13109
13122
|
const fieldCount = schema.properties ? Object.keys(schema.properties).length : 0;
|
|
13110
13123
|
const outputTokens = fieldCount > 0 ? fieldCount * 80 : void 0;
|
|
13111
13124
|
let selected;
|
|
@@ -13135,7 +13148,7 @@ async function extractStructuredData(input) {
|
|
|
13135
13148
|
let system;
|
|
13136
13149
|
let user;
|
|
13137
13150
|
const snapshot = await loadPromptSnapshot(aiexDir, schema.table.name);
|
|
13138
|
-
const promptText = file ? PLACEHOLDER_TEXT : text;
|
|
13151
|
+
const promptText = file ? PLACEHOLDER_TEXT : text$1;
|
|
13139
13152
|
if (snapshot) {
|
|
13140
13153
|
system = snapshot.system;
|
|
13141
13154
|
user = snapshot.user.replaceAll(PLACEHOLDER_TEXT, promptText);
|
|
@@ -13152,7 +13165,7 @@ async function extractStructuredData(input) {
|
|
|
13152
13165
|
const fileName = filePart.type === "file" ? filePart.filename : path.basename(file);
|
|
13153
13166
|
const contentParts = [{
|
|
13154
13167
|
type: "text",
|
|
13155
|
-
text: user.includes(PLACEHOLDER_TEXT) ? user.replaceAll(PLACEHOLDER_TEXT, text || `Data is contained in the attached file: ${fileName}`) : user
|
|
13168
|
+
text: user.includes(PLACEHOLDER_TEXT) ? user.replaceAll(PLACEHOLDER_TEXT, text$1 || `Data is contained in the attached file: ${fileName}`) : user
|
|
13156
13169
|
}, filePart];
|
|
13157
13170
|
const fileOpts = {
|
|
13158
13171
|
model: provider.chatModel(selected.name),
|
|
@@ -13389,7 +13402,7 @@ function createPdfConverter(type) {
|
|
|
13389
13402
|
}
|
|
13390
13403
|
|
|
13391
13404
|
//#endregion
|
|
13392
|
-
//#region src/
|
|
13405
|
+
//#region src/core/extract-runner.ts
|
|
13393
13406
|
const FILE_PART_EXTENSIONS = new Set([
|
|
13394
13407
|
"png",
|
|
13395
13408
|
"jpg",
|
|
@@ -13399,12 +13412,20 @@ const FILE_PART_EXTENSIONS = new Set([
|
|
|
13399
13412
|
"bmp",
|
|
13400
13413
|
"svg"
|
|
13401
13414
|
]);
|
|
13415
|
+
const SUPPORTED_EXTENSIONS = new Set([
|
|
13416
|
+
...FILE_PART_EXTENSIONS,
|
|
13417
|
+
"pdf",
|
|
13418
|
+
"txt",
|
|
13419
|
+
"md",
|
|
13420
|
+
"csv",
|
|
13421
|
+
"json",
|
|
13422
|
+
"html",
|
|
13423
|
+
"xml",
|
|
13424
|
+
"yaml",
|
|
13425
|
+
"yml"
|
|
13426
|
+
]);
|
|
13427
|
+
const JSON_EXT_RE = /\.json$/;
|
|
13402
13428
|
const PDF_CONVERTER = createPdfConverter();
|
|
13403
|
-
function fail$1(message) {
|
|
13404
|
-
if (message) consola.error(message);
|
|
13405
|
-
outro("Failed!");
|
|
13406
|
-
process.exitCode = 1;
|
|
13407
|
-
}
|
|
13408
13429
|
async function ensureDatabaseReady(dbPath, schema) {
|
|
13409
13430
|
try {
|
|
13410
13431
|
await fs.access(dbPath);
|
|
@@ -13424,6 +13445,194 @@ async function ensureDatabaseReady(dbPath, schema) {
|
|
|
13424
13445
|
}
|
|
13425
13446
|
return null;
|
|
13426
13447
|
}
|
|
13448
|
+
function listSupportedFiles(dir, pattern) {
|
|
13449
|
+
const entries = fs$1.readdirSync(dir, { withFileTypes: true });
|
|
13450
|
+
const files = [];
|
|
13451
|
+
for (const entry of entries) {
|
|
13452
|
+
if (entry.isDirectory()) continue;
|
|
13453
|
+
const ext = path.extname(entry.name).toLowerCase().replace(".", "");
|
|
13454
|
+
if (!SUPPORTED_EXTENSIONS.has(ext)) continue;
|
|
13455
|
+
if (pattern && !picomatch.isMatch(entry.name, pattern)) continue;
|
|
13456
|
+
files.push(path.join(dir, entry.name));
|
|
13457
|
+
}
|
|
13458
|
+
return files.sort();
|
|
13459
|
+
}
|
|
13460
|
+
async function loadSchema(config, schemaName) {
|
|
13461
|
+
const schemaPath = path.join(config.schemaPath, `${schemaName}.json`);
|
|
13462
|
+
try {
|
|
13463
|
+
const content = await fs.readFile(schemaPath, "utf-8");
|
|
13464
|
+
const parsed = JSON.parse(content);
|
|
13465
|
+
return { schema: JsonSchemaDefinitionSchema.parse(parsed) };
|
|
13466
|
+
} catch (e) {
|
|
13467
|
+
if (e instanceof ZodError) return {
|
|
13468
|
+
schema: null,
|
|
13469
|
+
error: `Schema validation failed: ${schemaName}.json\n${e.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n")}`
|
|
13470
|
+
};
|
|
13471
|
+
if (e.code === "ENOENT") return {
|
|
13472
|
+
schema: null,
|
|
13473
|
+
error: `Cannot read schema file: ${schemaName}.json`
|
|
13474
|
+
};
|
|
13475
|
+
if (e instanceof SyntaxError) return {
|
|
13476
|
+
schema: null,
|
|
13477
|
+
error: `Invalid JSON in schema file: ${schemaName}.json`
|
|
13478
|
+
};
|
|
13479
|
+
return {
|
|
13480
|
+
schema: null,
|
|
13481
|
+
error: String(e)
|
|
13482
|
+
};
|
|
13483
|
+
}
|
|
13484
|
+
}
|
|
13485
|
+
async function listSchemas(aiexDir) {
|
|
13486
|
+
try {
|
|
13487
|
+
const dir = path.join(aiexDir, "schema");
|
|
13488
|
+
return (await fs.readdir(dir)).filter((f) => f.endsWith(".json")).map((f) => f.replace(JSON_EXT_RE, "")).sort();
|
|
13489
|
+
} catch {
|
|
13490
|
+
return [];
|
|
13491
|
+
}
|
|
13492
|
+
}
|
|
13493
|
+
async function readExtractFileInput(filePath) {
|
|
13494
|
+
const ext = path.extname(filePath).toLowerCase().replace(".", "");
|
|
13495
|
+
if (FILE_PART_EXTENSIONS.has(ext)) return {
|
|
13496
|
+
text: "",
|
|
13497
|
+
filePath
|
|
13498
|
+
};
|
|
13499
|
+
if (ext === "pdf") {
|
|
13500
|
+
const buffer = await fs.readFile(filePath);
|
|
13501
|
+
const result = await PDF_CONVERTER.convert(buffer);
|
|
13502
|
+
consola.info(`Extracted ${result.pageCount} page(s) from PDF`);
|
|
13503
|
+
return { text: result.text };
|
|
13504
|
+
}
|
|
13505
|
+
return { text: await fs.readFile(filePath, "utf-8") };
|
|
13506
|
+
}
|
|
13507
|
+
async function extractSingle(aiexDir, config, aiConfig, schemaName, text$1, filePath, modelOverride, options) {
|
|
13508
|
+
const schemaLoad = await loadSchema(config, schemaName);
|
|
13509
|
+
if (!schemaLoad.schema) {
|
|
13510
|
+
if (!options?.quiet) consola.error(schemaLoad.error);
|
|
13511
|
+
return {
|
|
13512
|
+
success: false,
|
|
13513
|
+
error: schemaLoad.error
|
|
13514
|
+
};
|
|
13515
|
+
}
|
|
13516
|
+
const s = spinner();
|
|
13517
|
+
if (!options?.quiet) s.start(filePath ? `Extracting from ${path.basename(filePath)}...` : "Extracting data...");
|
|
13518
|
+
const result = await extractStructuredData({
|
|
13519
|
+
config: aiConfig,
|
|
13520
|
+
schema: schemaLoad.schema,
|
|
13521
|
+
text: text$1 ?? "",
|
|
13522
|
+
aiexDir,
|
|
13523
|
+
file: filePath,
|
|
13524
|
+
modelOverride,
|
|
13525
|
+
onRetry(info) {
|
|
13526
|
+
if (!options?.quiet) s.message(`API responded with ${info.statusCode}, retrying in ${info.delayMs / 1e3}s (${info.attempt}/${info.maxRetries})...`);
|
|
13527
|
+
}
|
|
13528
|
+
});
|
|
13529
|
+
if (!result.success) {
|
|
13530
|
+
if (!options?.quiet) {
|
|
13531
|
+
s.stop("Extraction failed");
|
|
13532
|
+
consola.error(result.error || "Unknown error");
|
|
13533
|
+
}
|
|
13534
|
+
return {
|
|
13535
|
+
success: false,
|
|
13536
|
+
error: result.error || "Unknown error"
|
|
13537
|
+
};
|
|
13538
|
+
}
|
|
13539
|
+
if (!options?.quiet) s.stop("Extraction complete");
|
|
13540
|
+
if (result.outputPath && !options?.quiet) consola.success(`Result saved: ${pc.cyan(result.outputPath)}`);
|
|
13541
|
+
if (result.tokensUsed && !options?.quiet) consola.info(pc.gray(`Token usage: prompt=${result.tokensUsed.prompt}, completion=${result.tokensUsed.completion}, total=${result.tokensUsed.total}`));
|
|
13542
|
+
if (result.data) {
|
|
13543
|
+
const s2 = spinner();
|
|
13544
|
+
if (!options?.quiet) s2.start("Inserting into database...");
|
|
13545
|
+
const dbError = await ensureDatabaseReady(config.databasePath, schemaLoad.schema);
|
|
13546
|
+
if (dbError) {
|
|
13547
|
+
if (!options?.quiet) s2.stop("Database not ready");
|
|
13548
|
+
consola.error(dbError);
|
|
13549
|
+
return {
|
|
13550
|
+
success: false,
|
|
13551
|
+
error: dbError
|
|
13552
|
+
};
|
|
13553
|
+
}
|
|
13554
|
+
try {
|
|
13555
|
+
const db = new Database(config.databasePath);
|
|
13556
|
+
try {
|
|
13557
|
+
const insertResult = insertExtractedData(db, schemaLoad.schema, result.data);
|
|
13558
|
+
if (insertResult.success) {
|
|
13559
|
+
if (!options?.quiet) s2.stop(`Inserted into ${insertResult.tablesInserted.length} table(s)`);
|
|
13560
|
+
} else {
|
|
13561
|
+
if (!options?.quiet) s2.stop("Database insert failed");
|
|
13562
|
+
consola.error(insertResult.error || "Unknown error");
|
|
13563
|
+
return {
|
|
13564
|
+
success: false,
|
|
13565
|
+
error: insertResult.error
|
|
13566
|
+
};
|
|
13567
|
+
}
|
|
13568
|
+
} finally {
|
|
13569
|
+
db.close();
|
|
13570
|
+
}
|
|
13571
|
+
} catch (e) {
|
|
13572
|
+
if (!options?.quiet) s2.stop("Database insert failed");
|
|
13573
|
+
consola.error(e instanceof Error ? e.message : String(e));
|
|
13574
|
+
return {
|
|
13575
|
+
success: false,
|
|
13576
|
+
error: String(e)
|
|
13577
|
+
};
|
|
13578
|
+
}
|
|
13579
|
+
}
|
|
13580
|
+
return { success: true };
|
|
13581
|
+
}
|
|
13582
|
+
async function processOneFile(aiexDir, config, aiConfig, schemaName, filePath, modelOverride) {
|
|
13583
|
+
try {
|
|
13584
|
+
const input = await readExtractFileInput(filePath);
|
|
13585
|
+
const r = await extractSingle(aiexDir, config, aiConfig, schemaName, input.text, input.filePath, modelOverride, { quiet: false });
|
|
13586
|
+
if (r.success) {
|
|
13587
|
+
consola.success(`Processed: ${path.basename(filePath)}`);
|
|
13588
|
+
return true;
|
|
13589
|
+
} else {
|
|
13590
|
+
consola.error(`Failed: ${r.error}`);
|
|
13591
|
+
return false;
|
|
13592
|
+
}
|
|
13593
|
+
} catch (e) {
|
|
13594
|
+
consola.error(`Error processing ${path.basename(filePath)}: ${e instanceof Error ? e.message : String(e)}`);
|
|
13595
|
+
return false;
|
|
13596
|
+
}
|
|
13597
|
+
}
|
|
13598
|
+
async function runBatchExtraction(aiexDir, config, aiConfig, schemaName, dir, globPattern, modelOverride) {
|
|
13599
|
+
consola.info(`Scanning ${pc.cyan(dir)} for supported files...`);
|
|
13600
|
+
let files;
|
|
13601
|
+
try {
|
|
13602
|
+
files = listSupportedFiles(dir, globPattern);
|
|
13603
|
+
} catch {
|
|
13604
|
+
return {
|
|
13605
|
+
ok: false,
|
|
13606
|
+
successCount: 0,
|
|
13607
|
+
failCount: 0,
|
|
13608
|
+
error: `Cannot read directory: ${dir}`
|
|
13609
|
+
};
|
|
13610
|
+
}
|
|
13611
|
+
if (files.length === 0) return {
|
|
13612
|
+
ok: false,
|
|
13613
|
+
successCount: 0,
|
|
13614
|
+
failCount: 0,
|
|
13615
|
+
error: `No supported files found in ${dir}`
|
|
13616
|
+
};
|
|
13617
|
+
consola.info(`Found ${files.length} file(s) to process`);
|
|
13618
|
+
let successCount = 0;
|
|
13619
|
+
let failCount = 0;
|
|
13620
|
+
for (let i = 0; i < files.length; i++) {
|
|
13621
|
+
const file = files[i];
|
|
13622
|
+
consola.info(`\n[${i + 1}/${files.length}] Processing: ${pc.cyan(path.basename(file))}`);
|
|
13623
|
+
if (await processOneFile(aiexDir, config, aiConfig, schemaName, file, modelOverride)) successCount++;
|
|
13624
|
+
else failCount++;
|
|
13625
|
+
}
|
|
13626
|
+
consola.info(`\nBatch complete: ${pc.green(`${successCount} succeeded`)}, ${pc.red(`${failCount} failed`)}, ${files.length} total`);
|
|
13627
|
+
return {
|
|
13628
|
+
ok: true,
|
|
13629
|
+
successCount,
|
|
13630
|
+
failCount
|
|
13631
|
+
};
|
|
13632
|
+
}
|
|
13633
|
+
|
|
13634
|
+
//#endregion
|
|
13635
|
+
//#region src/commands/extract.ts
|
|
13427
13636
|
const extractCommand = defineCommand({
|
|
13428
13637
|
meta: {
|
|
13429
13638
|
name: "extract",
|
|
@@ -13433,8 +13642,7 @@ const extractCommand = defineCommand({
|
|
|
13433
13642
|
schema: {
|
|
13434
13643
|
type: "string",
|
|
13435
13644
|
alias: "s",
|
|
13436
|
-
description: "Schema name (without .json extension)"
|
|
13437
|
-
required: true
|
|
13645
|
+
description: "Schema name (without .json extension)"
|
|
13438
13646
|
},
|
|
13439
13647
|
text: {
|
|
13440
13648
|
type: "string",
|
|
@@ -13450,31 +13658,41 @@ const extractCommand = defineCommand({
|
|
|
13450
13658
|
type: "string",
|
|
13451
13659
|
alias: "m",
|
|
13452
13660
|
description: "AI model to use for extraction (overrides auto-selection)"
|
|
13661
|
+
},
|
|
13662
|
+
dir: {
|
|
13663
|
+
type: "string",
|
|
13664
|
+
alias: "d",
|
|
13665
|
+
description: "Directory containing files to batch extract"
|
|
13666
|
+
},
|
|
13667
|
+
glob: {
|
|
13668
|
+
type: "string",
|
|
13669
|
+
alias: "g",
|
|
13670
|
+
description: "Glob pattern to filter files in batch mode (e.g. \"*.pdf\")"
|
|
13453
13671
|
}
|
|
13454
13672
|
},
|
|
13455
13673
|
async run({ args }) {
|
|
13456
13674
|
intro(pc.inverse(" aiex extract "));
|
|
13457
13675
|
const config = createMigrationConfig(process.cwd());
|
|
13458
13676
|
const aiexDir = path.dirname(config.schemaPath);
|
|
13459
|
-
if (
|
|
13460
|
-
|
|
13677
|
+
if (args.dir && args.text) {
|
|
13678
|
+
failCommand("Cannot combine -t/--text with -d/--dir");
|
|
13461
13679
|
return;
|
|
13462
13680
|
}
|
|
13463
|
-
if (args.
|
|
13464
|
-
|
|
13681
|
+
if (args.dir && args.file) {
|
|
13682
|
+
failCommand("Cannot combine -f/--file with -d/--dir");
|
|
13465
13683
|
return;
|
|
13466
13684
|
}
|
|
13467
13685
|
const aiConfig = await readAIConfig(aiexDir);
|
|
13468
13686
|
if (!aiConfig) {
|
|
13469
|
-
|
|
13687
|
+
failCommand("AI configuration not found. Please run \"aiex web\" to configure AI settings first");
|
|
13470
13688
|
return;
|
|
13471
13689
|
}
|
|
13472
13690
|
if (!aiConfig.provider.apiKey) {
|
|
13473
|
-
|
|
13691
|
+
failCommand("API Key not configured. Please configure AI settings in the Web interface first");
|
|
13474
13692
|
return;
|
|
13475
13693
|
}
|
|
13476
13694
|
if (!aiConfig.provider.models?.length) {
|
|
13477
|
-
|
|
13695
|
+
failCommand("No models configured. Please add at least one model in AI Settings");
|
|
13478
13696
|
return;
|
|
13479
13697
|
}
|
|
13480
13698
|
let modelOverride;
|
|
@@ -13482,108 +13700,265 @@ const extractCommand = defineCommand({
|
|
|
13482
13700
|
const matched = aiConfig.provider.models.find((m) => m.name === args.model);
|
|
13483
13701
|
if (!matched) {
|
|
13484
13702
|
const available = aiConfig.provider.models.map((m) => m.name).join(", ");
|
|
13485
|
-
|
|
13703
|
+
failCommand(`Model "${args.model}" not found in configuration. Available models: ${available}`);
|
|
13486
13704
|
return;
|
|
13487
13705
|
}
|
|
13488
13706
|
modelOverride = matched;
|
|
13489
13707
|
}
|
|
13490
|
-
|
|
13491
|
-
|
|
13492
|
-
if (args.file) {
|
|
13493
|
-
const ext = path.extname(args.file).toLowerCase().replace(".", "");
|
|
13494
|
-
if (FILE_PART_EXTENSIONS.has(ext)) filePath = args.file;
|
|
13495
|
-
else if (ext === "pdf") {
|
|
13496
|
-
const buffer = await fs.readFile(args.file);
|
|
13497
|
-
const result$1 = await PDF_CONVERTER.convert(buffer);
|
|
13498
|
-
text = result$1.text;
|
|
13499
|
-
consola.info(`Extracted ${result$1.pageCount} page(s) from PDF`);
|
|
13500
|
-
} else text = await fs.readFile(args.file, "utf-8");
|
|
13501
|
-
} else if (args.text) text = args.text;
|
|
13502
|
-
const schemaName = args.schema;
|
|
13503
|
-
const schemaPath = path.join(config.schemaPath, `${schemaName}.json`);
|
|
13504
|
-
let schema;
|
|
13505
|
-
try {
|
|
13506
|
-
const content = await fs.readFile(schemaPath, "utf-8");
|
|
13507
|
-
schema = JSON.parse(content);
|
|
13508
|
-
} catch {
|
|
13509
|
-
fail$1(`Cannot read schema file: ${schemaName}.json`);
|
|
13708
|
+
if (!args.schema && !args.text && !args.file && !args.dir) {
|
|
13709
|
+
if (await runInteractive(aiexDir, config, aiConfig, modelOverride)) outro("Done!");
|
|
13510
13710
|
return;
|
|
13511
13711
|
}
|
|
13512
|
-
|
|
13513
|
-
|
|
13514
|
-
|
|
13515
|
-
|
|
13516
|
-
consola.error(`Schema validation failed: ${schemaName}.json`);
|
|
13517
|
-
for (const issue of e.issues) consola.error(` - ${issue.path.join(".")}: ${issue.message}`);
|
|
13712
|
+
if (args.dir) {
|
|
13713
|
+
if (!args.schema) {
|
|
13714
|
+
failCommand("Schema name (-s) is required in batch mode");
|
|
13715
|
+
return;
|
|
13518
13716
|
}
|
|
13519
|
-
|
|
13717
|
+
const result = await runBatchExtraction(aiexDir, config, aiConfig, args.schema, args.dir, args.glob, modelOverride);
|
|
13718
|
+
if (!result.ok) {
|
|
13719
|
+
failCommand(result.error);
|
|
13720
|
+
return;
|
|
13721
|
+
}
|
|
13722
|
+
if (result.failCount > 0) process.exitCode = 1;
|
|
13723
|
+
if (result.failCount > 0) outro(`Completed with failures (${result.failCount} failed)`);
|
|
13724
|
+
else outro("Done!");
|
|
13520
13725
|
return;
|
|
13521
13726
|
}
|
|
13522
|
-
|
|
13523
|
-
|
|
13524
|
-
|
|
13525
|
-
|
|
13526
|
-
|
|
13527
|
-
text
|
|
13528
|
-
|
|
13529
|
-
|
|
13530
|
-
|
|
13531
|
-
|
|
13532
|
-
|
|
13727
|
+
if (!args.schema) {
|
|
13728
|
+
failCommand("Please provide a schema name (-s) to extract from");
|
|
13729
|
+
return;
|
|
13730
|
+
}
|
|
13731
|
+
if (!args.text && !args.file) {
|
|
13732
|
+
failCommand("Please provide text (-t) or a file (-f) to extract from");
|
|
13733
|
+
return;
|
|
13734
|
+
}
|
|
13735
|
+
if (args.text && args.file) {
|
|
13736
|
+
failCommand("-t and -f cannot be used together");
|
|
13737
|
+
return;
|
|
13738
|
+
}
|
|
13739
|
+
let text$1 = "";
|
|
13740
|
+
let filePath;
|
|
13741
|
+
if (args.file) try {
|
|
13742
|
+
const input = await readExtractFileInput(args.file);
|
|
13743
|
+
text$1 = input.text;
|
|
13744
|
+
filePath = input.filePath;
|
|
13745
|
+
} catch (e) {
|
|
13746
|
+
failCommand(`Cannot read file: ${args.file} — ${e instanceof Error ? e.message : String(e)}`);
|
|
13747
|
+
return;
|
|
13748
|
+
}
|
|
13749
|
+
else if (args.text) text$1 = args.text;
|
|
13750
|
+
if (!(await extractSingle(aiexDir, config, aiConfig, args.schema, text$1, filePath, modelOverride)).success) {
|
|
13751
|
+
failCommand();
|
|
13752
|
+
return;
|
|
13753
|
+
}
|
|
13754
|
+
outro("Done!");
|
|
13755
|
+
}
|
|
13756
|
+
});
|
|
13757
|
+
async function runInteractive(aiexDir, config, aiConfig, modelOverride) {
|
|
13758
|
+
const schemas = await listSchemas(aiexDir);
|
|
13759
|
+
if (schemas.length === 0) {
|
|
13760
|
+
failCommand(`No schema files found in ${pc.cyan(".aiex/schema/")}. Run ${pc.cyan("aiex schema --init")} first, or add JSON Schema files.`);
|
|
13761
|
+
return false;
|
|
13762
|
+
}
|
|
13763
|
+
const schemaName = await select({
|
|
13764
|
+
message: "Select a schema to extract data for:",
|
|
13765
|
+
options: schemas.map((s) => ({
|
|
13766
|
+
label: s,
|
|
13767
|
+
value: s
|
|
13768
|
+
}))
|
|
13769
|
+
});
|
|
13770
|
+
if (isCancel(schemaName)) {
|
|
13771
|
+
cancel("Cancelled");
|
|
13772
|
+
return false;
|
|
13773
|
+
}
|
|
13774
|
+
const inputSource = await select({
|
|
13775
|
+
message: "Choose input source:",
|
|
13776
|
+
options: [
|
|
13777
|
+
{
|
|
13778
|
+
label: "Text content",
|
|
13779
|
+
value: "text",
|
|
13780
|
+
hint: "Paste or type text directly"
|
|
13781
|
+
},
|
|
13782
|
+
{
|
|
13783
|
+
label: "Single file",
|
|
13784
|
+
value: "file",
|
|
13785
|
+
hint: "Extract from a file (txt, pdf, image)"
|
|
13786
|
+
},
|
|
13787
|
+
{
|
|
13788
|
+
label: "Batch directory",
|
|
13789
|
+
value: "dir",
|
|
13790
|
+
hint: "Extract all supported files in a directory"
|
|
13791
|
+
}
|
|
13792
|
+
]
|
|
13793
|
+
});
|
|
13794
|
+
if (isCancel(inputSource)) {
|
|
13795
|
+
cancel("Cancelled");
|
|
13796
|
+
return false;
|
|
13797
|
+
}
|
|
13798
|
+
if (inputSource === "text") {
|
|
13799
|
+
const textContent = await text({
|
|
13800
|
+
message: "Enter text content to extract:",
|
|
13801
|
+
validate(value) {
|
|
13802
|
+
if (!value || value.trim().length === 0) return "Please enter some text";
|
|
13533
13803
|
}
|
|
13534
13804
|
});
|
|
13535
|
-
if (
|
|
13536
|
-
|
|
13537
|
-
|
|
13538
|
-
return;
|
|
13805
|
+
if (isCancel(textContent)) {
|
|
13806
|
+
cancel("Cancelled");
|
|
13807
|
+
return false;
|
|
13539
13808
|
}
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
|
|
13543
|
-
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
const dbError = await ensureDatabaseReady(config.databasePath, schema);
|
|
13547
|
-
if (dbError) {
|
|
13548
|
-
s2.stop("Database not ready");
|
|
13549
|
-
fail$1(dbError);
|
|
13550
|
-
return;
|
|
13809
|
+
return (await extractSingle(aiexDir, config, aiConfig, schemaName, textContent, void 0, modelOverride)).success;
|
|
13810
|
+
} else if (inputSource === "file") {
|
|
13811
|
+
const filePathStr = await text({
|
|
13812
|
+
message: "Enter file path:",
|
|
13813
|
+
validate(value) {
|
|
13814
|
+
if (!value || value.trim().length === 0) return "Please enter a file path";
|
|
13551
13815
|
}
|
|
13552
|
-
|
|
13553
|
-
|
|
13554
|
-
|
|
13555
|
-
|
|
13556
|
-
|
|
13557
|
-
|
|
13558
|
-
|
|
13559
|
-
|
|
13560
|
-
|
|
13561
|
-
|
|
13562
|
-
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13816
|
+
});
|
|
13817
|
+
if (isCancel(filePathStr)) {
|
|
13818
|
+
cancel("Cancelled");
|
|
13819
|
+
return false;
|
|
13820
|
+
}
|
|
13821
|
+
const fp = filePathStr;
|
|
13822
|
+
try {
|
|
13823
|
+
const input = await readExtractFileInput(fp);
|
|
13824
|
+
return (await extractSingle(aiexDir, config, aiConfig, schemaName, input.text, input.filePath, modelOverride)).success;
|
|
13825
|
+
} catch (e) {
|
|
13826
|
+
consola.error(`Cannot read file: ${fp} — ${e instanceof Error ? e.message : String(e)}`);
|
|
13827
|
+
return false;
|
|
13828
|
+
}
|
|
13829
|
+
} else if (inputSource === "dir") {
|
|
13830
|
+
const dirPath = await text({
|
|
13831
|
+
message: "Enter directory path:",
|
|
13832
|
+
validate(value) {
|
|
13833
|
+
if (!value || value.trim().length === 0) return "Please enter a directory path";
|
|
13569
13834
|
}
|
|
13835
|
+
});
|
|
13836
|
+
if (isCancel(dirPath)) {
|
|
13837
|
+
cancel("Cancelled");
|
|
13838
|
+
return false;
|
|
13570
13839
|
}
|
|
13571
|
-
|
|
13840
|
+
const result = await runBatchExtraction(aiexDir, config, aiConfig, schemaName, dirPath, void 0, modelOverride);
|
|
13841
|
+
if (!result.ok) failCommand(result.error);
|
|
13842
|
+
return result.ok && result.failCount === 0;
|
|
13572
13843
|
}
|
|
13573
|
-
|
|
13844
|
+
return false;
|
|
13845
|
+
}
|
|
13846
|
+
function cancel(msg) {
|
|
13847
|
+
consola.info(msg);
|
|
13848
|
+
outro("Cancelled");
|
|
13849
|
+
process.exitCode = 0;
|
|
13850
|
+
}
|
|
13574
13851
|
|
|
13575
13852
|
//#endregion
|
|
13576
13853
|
//#region schemas/table-schema.json
|
|
13577
13854
|
var $id = "https://raw.githubusercontent.com/OSpoon/aiex-cli/main/app/cli/schemas/table-schema.json";
|
|
13578
13855
|
|
|
13579
13856
|
//#endregion
|
|
13580
|
-
//#region src/
|
|
13857
|
+
//#region src/core/schema-runner.ts
|
|
13581
13858
|
const execFileAsync$1 = promisify(execFile);
|
|
13582
|
-
|
|
13583
|
-
|
|
13584
|
-
|
|
13585
|
-
|
|
13586
|
-
|
|
13859
|
+
const EXAMPLE_SCORE_REPORT_SCHEMA = {
|
|
13860
|
+
$schema: $id,
|
|
13861
|
+
title: "ScoreReport",
|
|
13862
|
+
type: "object",
|
|
13863
|
+
table: {
|
|
13864
|
+
name: "score_report",
|
|
13865
|
+
timestamps: true
|
|
13866
|
+
},
|
|
13867
|
+
properties: {
|
|
13868
|
+
name: {
|
|
13869
|
+
type: "string",
|
|
13870
|
+
description: "姓名"
|
|
13871
|
+
},
|
|
13872
|
+
reportNumber: {
|
|
13873
|
+
type: "string",
|
|
13874
|
+
description: "报告编号"
|
|
13875
|
+
},
|
|
13876
|
+
gender: {
|
|
13877
|
+
type: "string",
|
|
13878
|
+
description: "性别"
|
|
13879
|
+
},
|
|
13880
|
+
printDate: {
|
|
13881
|
+
type: "string",
|
|
13882
|
+
format: "date-time",
|
|
13883
|
+
description: "打印日期"
|
|
13884
|
+
},
|
|
13885
|
+
examYear: {
|
|
13886
|
+
type: "integer",
|
|
13887
|
+
description: "考试年份"
|
|
13888
|
+
},
|
|
13889
|
+
examType: {
|
|
13890
|
+
type: "string",
|
|
13891
|
+
description: "考试类型,如全国统考"
|
|
13892
|
+
},
|
|
13893
|
+
examCategory: {
|
|
13894
|
+
type: "string",
|
|
13895
|
+
description: "考试类别,如普通高考"
|
|
13896
|
+
},
|
|
13897
|
+
province: {
|
|
13898
|
+
type: "string",
|
|
13899
|
+
description: "考试省份"
|
|
13900
|
+
},
|
|
13901
|
+
subjectCategory: {
|
|
13902
|
+
type: "string",
|
|
13903
|
+
description: "科类,如艺术(文)"
|
|
13904
|
+
},
|
|
13905
|
+
chinese: {
|
|
13906
|
+
type: "integer",
|
|
13907
|
+
description: "语文成绩"
|
|
13908
|
+
},
|
|
13909
|
+
chineseFull: {
|
|
13910
|
+
type: "integer",
|
|
13911
|
+
description: "语文满分"
|
|
13912
|
+
},
|
|
13913
|
+
math: {
|
|
13914
|
+
type: "integer",
|
|
13915
|
+
description: "数学成绩"
|
|
13916
|
+
},
|
|
13917
|
+
mathFull: {
|
|
13918
|
+
type: "integer",
|
|
13919
|
+
description: "数学满分"
|
|
13920
|
+
},
|
|
13921
|
+
foreignLang: {
|
|
13922
|
+
type: "integer",
|
|
13923
|
+
description: "外语成绩"
|
|
13924
|
+
},
|
|
13925
|
+
foreignLangFull: {
|
|
13926
|
+
type: "integer",
|
|
13927
|
+
description: "外语满分"
|
|
13928
|
+
},
|
|
13929
|
+
comprehensive: {
|
|
13930
|
+
type: "integer",
|
|
13931
|
+
description: "综合成绩"
|
|
13932
|
+
},
|
|
13933
|
+
comprehensiveFull: {
|
|
13934
|
+
type: "integer",
|
|
13935
|
+
description: "综合满分"
|
|
13936
|
+
},
|
|
13937
|
+
totalScore: {
|
|
13938
|
+
type: "integer",
|
|
13939
|
+
description: "总分"
|
|
13940
|
+
},
|
|
13941
|
+
totalFullScore: {
|
|
13942
|
+
type: "integer",
|
|
13943
|
+
description: "总分满分"
|
|
13944
|
+
},
|
|
13945
|
+
batchLineFirst: {
|
|
13946
|
+
type: "integer",
|
|
13947
|
+
description: "本科第一批录取分数线"
|
|
13948
|
+
},
|
|
13949
|
+
batchLineSecond: {
|
|
13950
|
+
type: "integer",
|
|
13951
|
+
description: "本科第二批录取分数线"
|
|
13952
|
+
}
|
|
13953
|
+
},
|
|
13954
|
+
required: [
|
|
13955
|
+
"name",
|
|
13956
|
+
"examYear",
|
|
13957
|
+
"examType",
|
|
13958
|
+
"province",
|
|
13959
|
+
"totalScore"
|
|
13960
|
+
]
|
|
13961
|
+
};
|
|
13587
13962
|
async function writeJsonIfAbsent(filePath, data) {
|
|
13588
13963
|
try {
|
|
13589
13964
|
await fs.writeFile(filePath, `${JSON.stringify(data, null, 2)}\n`, { flag: "wx" });
|
|
@@ -13593,24 +13968,66 @@ async function writeJsonIfAbsent(filePath, data) {
|
|
|
13593
13968
|
throw error;
|
|
13594
13969
|
}
|
|
13595
13970
|
}
|
|
13596
|
-
async function
|
|
13971
|
+
async function initSchemaProject(config) {
|
|
13972
|
+
await fs.mkdir(config.schemaPath, { recursive: true });
|
|
13973
|
+
await fs.mkdir(path.dirname(config.drizzleSchemaPath), { recursive: true });
|
|
13974
|
+
await fs.mkdir(config.migrationsPath, { recursive: true });
|
|
13975
|
+
return { scoreReportStatus: await writeJsonIfAbsent(path.join(config.schemaPath, "score_report.json"), EXAMPLE_SCORE_REPORT_SCHEMA) };
|
|
13976
|
+
}
|
|
13977
|
+
async function listSchemaFiles(schemaDir) {
|
|
13978
|
+
try {
|
|
13979
|
+
return (await fs.readdir(schemaDir)).filter((f) => f.endsWith(".json")).map((f) => path.join(schemaDir, f)).sort();
|
|
13980
|
+
} catch {
|
|
13981
|
+
return [];
|
|
13982
|
+
}
|
|
13983
|
+
}
|
|
13984
|
+
async function generateSchemaFromFiles(schemaFiles, config) {
|
|
13597
13985
|
const result = parseAllSchemas(await Promise.all(schemaFiles.map(async (filePath) => {
|
|
13598
13986
|
return {
|
|
13599
13987
|
filePath,
|
|
13600
13988
|
content: await fs.readFile(filePath, "utf-8")
|
|
13601
13989
|
};
|
|
13602
13990
|
})));
|
|
13603
|
-
if (!result.success) {
|
|
13604
|
-
|
|
13605
|
-
|
|
13606
|
-
|
|
13607
|
-
|
|
13991
|
+
if (!result.success) return {
|
|
13992
|
+
success: false,
|
|
13993
|
+
error: result.error,
|
|
13994
|
+
warnings: [],
|
|
13995
|
+
schemaCount: schemaFiles.length,
|
|
13996
|
+
tables: 0,
|
|
13997
|
+
relations: 0
|
|
13998
|
+
};
|
|
13999
|
+
const { tables, relations, reverseRelations, warnings, drizzleCode } = result.data;
|
|
13608
14000
|
await fs.mkdir(path.dirname(config.drizzleSchemaPath), { recursive: true });
|
|
13609
|
-
await fs.writeFile(config.drizzleSchemaPath,
|
|
13610
|
-
|
|
13611
|
-
|
|
14001
|
+
await fs.writeFile(config.drizzleSchemaPath, drizzleCode);
|
|
14002
|
+
return {
|
|
14003
|
+
success: true,
|
|
14004
|
+
warnings,
|
|
14005
|
+
schemaCount: schemaFiles.length,
|
|
14006
|
+
tables: tables.length,
|
|
14007
|
+
relations: relations.length + reverseRelations.length
|
|
14008
|
+
};
|
|
13612
14009
|
}
|
|
13613
|
-
|
|
14010
|
+
function parseMigrationOutput(stdout, stderr) {
|
|
14011
|
+
try {
|
|
14012
|
+
const jsonLine = stdout.trim().split("\n").find((l) => l.startsWith("{") && l.endsWith("}"));
|
|
14013
|
+
if (!jsonLine) return {
|
|
14014
|
+
success: false,
|
|
14015
|
+
error: "Migration helper did not return valid output"
|
|
14016
|
+
};
|
|
14017
|
+
const result = JSON.parse(jsonLine);
|
|
14018
|
+
if (!result.success) return {
|
|
14019
|
+
success: false,
|
|
14020
|
+
error: result.error || "Migration failed"
|
|
14021
|
+
};
|
|
14022
|
+
return result;
|
|
14023
|
+
} catch {
|
|
14024
|
+
return {
|
|
14025
|
+
success: false,
|
|
14026
|
+
error: stderr || stdout || "Migration helper failed"
|
|
14027
|
+
};
|
|
14028
|
+
}
|
|
14029
|
+
}
|
|
14030
|
+
async function runSchemaMigration(config, migrationName) {
|
|
13614
14031
|
const helperPath = resolveHelperPath();
|
|
13615
14032
|
const helperArgs = [
|
|
13616
14033
|
resolveTsxPath(),
|
|
@@ -13621,28 +14038,56 @@ async function migrate(config, migrationName) {
|
|
|
13621
14038
|
];
|
|
13622
14039
|
if (migrationName) helperArgs.push(migrationName);
|
|
13623
14040
|
try {
|
|
13624
|
-
const { stdout } = await execFileAsync$1(process.execPath, helperArgs, { cwd: process.cwd() });
|
|
13625
|
-
|
|
13626
|
-
if (!result.success) {
|
|
13627
|
-
consola.error("Failed to generate migration");
|
|
13628
|
-
consola.error(result.error);
|
|
13629
|
-
return false;
|
|
13630
|
-
}
|
|
13631
|
-
if (result.changes === 0) {
|
|
13632
|
-
consola.info(pc.gray("No changes detected"));
|
|
13633
|
-
return true;
|
|
13634
|
-
}
|
|
13635
|
-
consola.success(pc.green("Migration files generated"));
|
|
13636
|
-
consola.success(pc.green("Database migrated"));
|
|
13637
|
-
return true;
|
|
14041
|
+
const { stdout, stderr } = await execFileAsync$1(process.execPath, helperArgs, { cwd: process.cwd() });
|
|
14042
|
+
return parseMigrationOutput(stdout, stderr);
|
|
13638
14043
|
} catch (error) {
|
|
13639
|
-
consola.error("Failed to generate migration");
|
|
13640
14044
|
const execError = error;
|
|
13641
|
-
|
|
13642
|
-
|
|
13643
|
-
|
|
14045
|
+
return {
|
|
14046
|
+
success: false,
|
|
14047
|
+
error: execError.stderr || execError.stdout || execError.message || String(error)
|
|
14048
|
+
};
|
|
13644
14049
|
}
|
|
13645
14050
|
}
|
|
14051
|
+
async function runSchemaSync(config, options = {}) {
|
|
14052
|
+
const schemaFiles = await listSchemaFiles(config.schemaPath);
|
|
14053
|
+
if (schemaFiles.length === 0) return {
|
|
14054
|
+
success: false,
|
|
14055
|
+
error: "No schema files found",
|
|
14056
|
+
warnings: [],
|
|
14057
|
+
schemaCount: 0,
|
|
14058
|
+
tables: 0,
|
|
14059
|
+
relations: 0
|
|
14060
|
+
};
|
|
14061
|
+
const generated = await generateSchemaFromFiles(schemaFiles, config);
|
|
14062
|
+
if (!generated.success) return {
|
|
14063
|
+
success: false,
|
|
14064
|
+
error: generated.error,
|
|
14065
|
+
warnings: generated.warnings,
|
|
14066
|
+
schemaCount: generated.schemaCount,
|
|
14067
|
+
tables: generated.tables,
|
|
14068
|
+
relations: generated.relations
|
|
14069
|
+
};
|
|
14070
|
+
if (options.generateOnly) return {
|
|
14071
|
+
success: true,
|
|
14072
|
+
warnings: generated.warnings,
|
|
14073
|
+
schemaCount: generated.schemaCount,
|
|
14074
|
+
tables: generated.tables,
|
|
14075
|
+
relations: generated.relations
|
|
14076
|
+
};
|
|
14077
|
+
const migration = await runSchemaMigration(config, options.migrationName);
|
|
14078
|
+
return {
|
|
14079
|
+
success: migration.success,
|
|
14080
|
+
error: migration.error,
|
|
14081
|
+
warnings: generated.warnings,
|
|
14082
|
+
schemaCount: generated.schemaCount,
|
|
14083
|
+
tables: generated.tables,
|
|
14084
|
+
relations: generated.relations,
|
|
14085
|
+
migration
|
|
14086
|
+
};
|
|
14087
|
+
}
|
|
14088
|
+
|
|
14089
|
+
//#endregion
|
|
14090
|
+
//#region src/commands/schema.ts
|
|
13646
14091
|
const schemaCommand = defineCommand({
|
|
13647
14092
|
meta: {
|
|
13648
14093
|
name: "schema",
|
|
@@ -13670,137 +14115,28 @@ const schemaCommand = defineCommand({
|
|
|
13670
14115
|
intro(pc.inverse(" aiex schema "));
|
|
13671
14116
|
const config = createMigrationConfig(process.cwd());
|
|
13672
14117
|
if (args.init) {
|
|
13673
|
-
await
|
|
13674
|
-
await fs.mkdir(path.dirname(config.drizzleSchemaPath), { recursive: true });
|
|
13675
|
-
await fs.mkdir(config.migrationsPath, { recursive: true });
|
|
13676
|
-
const examReportSchema = {
|
|
13677
|
-
$schema: $id,
|
|
13678
|
-
title: "ScoreReport",
|
|
13679
|
-
type: "object",
|
|
13680
|
-
table: {
|
|
13681
|
-
name: "score_report",
|
|
13682
|
-
timestamps: true
|
|
13683
|
-
},
|
|
13684
|
-
properties: {
|
|
13685
|
-
name: {
|
|
13686
|
-
type: "string",
|
|
13687
|
-
description: "姓名"
|
|
13688
|
-
},
|
|
13689
|
-
reportNumber: {
|
|
13690
|
-
type: "string",
|
|
13691
|
-
description: "报告编号"
|
|
13692
|
-
},
|
|
13693
|
-
gender: {
|
|
13694
|
-
type: "string",
|
|
13695
|
-
description: "性别"
|
|
13696
|
-
},
|
|
13697
|
-
printDate: {
|
|
13698
|
-
type: "string",
|
|
13699
|
-
format: "date-time",
|
|
13700
|
-
description: "打印日期"
|
|
13701
|
-
},
|
|
13702
|
-
examYear: {
|
|
13703
|
-
type: "integer",
|
|
13704
|
-
description: "考试年份"
|
|
13705
|
-
},
|
|
13706
|
-
examType: {
|
|
13707
|
-
type: "string",
|
|
13708
|
-
description: "考试类型,如全国统考"
|
|
13709
|
-
},
|
|
13710
|
-
examCategory: {
|
|
13711
|
-
type: "string",
|
|
13712
|
-
description: "考试类别,如普通高考"
|
|
13713
|
-
},
|
|
13714
|
-
province: {
|
|
13715
|
-
type: "string",
|
|
13716
|
-
description: "考试省份"
|
|
13717
|
-
},
|
|
13718
|
-
subjectCategory: {
|
|
13719
|
-
type: "string",
|
|
13720
|
-
description: "科类,如艺术(文)"
|
|
13721
|
-
},
|
|
13722
|
-
chinese: {
|
|
13723
|
-
type: "integer",
|
|
13724
|
-
description: "语文成绩"
|
|
13725
|
-
},
|
|
13726
|
-
chineseFull: {
|
|
13727
|
-
type: "integer",
|
|
13728
|
-
description: "语文满分"
|
|
13729
|
-
},
|
|
13730
|
-
math: {
|
|
13731
|
-
type: "integer",
|
|
13732
|
-
description: "数学成绩"
|
|
13733
|
-
},
|
|
13734
|
-
mathFull: {
|
|
13735
|
-
type: "integer",
|
|
13736
|
-
description: "数学满分"
|
|
13737
|
-
},
|
|
13738
|
-
foreignLang: {
|
|
13739
|
-
type: "integer",
|
|
13740
|
-
description: "外语成绩"
|
|
13741
|
-
},
|
|
13742
|
-
foreignLangFull: {
|
|
13743
|
-
type: "integer",
|
|
13744
|
-
description: "外语满分"
|
|
13745
|
-
},
|
|
13746
|
-
comprehensive: {
|
|
13747
|
-
type: "integer",
|
|
13748
|
-
description: "综合成绩"
|
|
13749
|
-
},
|
|
13750
|
-
comprehensiveFull: {
|
|
13751
|
-
type: "integer",
|
|
13752
|
-
description: "综合满分"
|
|
13753
|
-
},
|
|
13754
|
-
totalScore: {
|
|
13755
|
-
type: "integer",
|
|
13756
|
-
description: "总分"
|
|
13757
|
-
},
|
|
13758
|
-
totalFullScore: {
|
|
13759
|
-
type: "integer",
|
|
13760
|
-
description: "总分满分"
|
|
13761
|
-
},
|
|
13762
|
-
batchLineFirst: {
|
|
13763
|
-
type: "integer",
|
|
13764
|
-
description: "本科第一批录取分数线"
|
|
13765
|
-
},
|
|
13766
|
-
batchLineSecond: {
|
|
13767
|
-
type: "integer",
|
|
13768
|
-
description: "本科第二批录取分数线"
|
|
13769
|
-
}
|
|
13770
|
-
},
|
|
13771
|
-
required: [
|
|
13772
|
-
"name",
|
|
13773
|
-
"examYear",
|
|
13774
|
-
"examType",
|
|
13775
|
-
"province",
|
|
13776
|
-
"totalScore"
|
|
13777
|
-
]
|
|
13778
|
-
};
|
|
13779
|
-
const examStatus = await writeJsonIfAbsent(path.join(config.schemaPath, "score_report.json"), examReportSchema);
|
|
14118
|
+
const initResult = await initSchemaProject(config);
|
|
13780
14119
|
consola.success(`Initialized ${pc.cyan(".aiex/")} with example schemas`);
|
|
13781
|
-
if (
|
|
14120
|
+
if (initResult.scoreReportStatus === "skipped") consola.warn(`${pc.cyan(".aiex/schema/score_report.json")} already exists, skipped`);
|
|
13782
14121
|
consola.info("Example includes: ScoreReport (college entrance exam score report)");
|
|
13783
14122
|
outro("Run: aiex schema");
|
|
13784
14123
|
return;
|
|
13785
14124
|
}
|
|
13786
|
-
|
|
13787
|
-
try {
|
|
13788
|
-
schemaFiles = await fs.readdir(config.schemaPath);
|
|
13789
|
-
schemaFiles = schemaFiles.filter((f) => f.endsWith(".json")).map((f) => path.join(config.schemaPath, f));
|
|
13790
|
-
} catch {
|
|
13791
|
-
schemaFiles = [];
|
|
13792
|
-
}
|
|
14125
|
+
const schemaFiles = await listSchemaFiles(config.schemaPath);
|
|
13793
14126
|
if (schemaFiles.length === 0) {
|
|
13794
14127
|
consola.info("Use --init to initialize with an example schema");
|
|
13795
|
-
|
|
14128
|
+
failCommand(`No schema files found in ${pc.cyan(".aiex/schema/")}`);
|
|
13796
14129
|
return;
|
|
13797
14130
|
}
|
|
13798
14131
|
const s1 = spinner();
|
|
13799
14132
|
s1.start("Generating Drizzle schema...");
|
|
13800
|
-
const
|
|
13801
|
-
|
|
13802
|
-
if (
|
|
13803
|
-
|
|
14133
|
+
const generated = await generateSchemaFromFiles(schemaFiles, config);
|
|
14134
|
+
for (const warning of generated.warnings) consola.warn(warning);
|
|
14135
|
+
if (generated.success) consola.success(`Generated ${pc.cyan(".aiex/drizzle/schema.ts")} from ${generated.schemaCount} schema file(s)`);
|
|
14136
|
+
else if (generated.error) consola.error(generated.error);
|
|
14137
|
+
s1.stop(generated.success ? "Schema generated" : "Generation failed");
|
|
14138
|
+
if (!generated.success) {
|
|
14139
|
+
failCommand();
|
|
13804
14140
|
return;
|
|
13805
14141
|
}
|
|
13806
14142
|
if (args.generate) {
|
|
@@ -13809,10 +14145,18 @@ const schemaCommand = defineCommand({
|
|
|
13809
14145
|
}
|
|
13810
14146
|
const s2 = spinner();
|
|
13811
14147
|
s2.start("Running migrations...");
|
|
13812
|
-
const
|
|
13813
|
-
|
|
13814
|
-
|
|
13815
|
-
|
|
14148
|
+
const migration = await runSchemaMigration(config, args.name);
|
|
14149
|
+
if (!migration.success) {
|
|
14150
|
+
consola.error("Failed to generate migration");
|
|
14151
|
+
consola.error(migration.error || "Migration failed");
|
|
14152
|
+
} else if (migration.changes === 0) consola.info(pc.gray("No changes detected"));
|
|
14153
|
+
else {
|
|
14154
|
+
consola.success(pc.green("Migration files generated"));
|
|
14155
|
+
consola.success(pc.green("Database migrated"));
|
|
14156
|
+
}
|
|
14157
|
+
s2.stop(migration.success ? "Migrations applied" : "Migration failed");
|
|
14158
|
+
if (!migration.success) {
|
|
14159
|
+
failCommand();
|
|
13816
14160
|
return;
|
|
13817
14161
|
}
|
|
13818
14162
|
outro("Done!");
|
|
@@ -13959,6 +14303,9 @@ function dataRoutes(config) {
|
|
|
13959
14303
|
if (!TABLE_NAME_RE$1.test(tableName)) return c.json({ error: "Invalid table name" }, 400);
|
|
13960
14304
|
const sortField = c.req.query("sortField");
|
|
13961
14305
|
const sortOrder = c.req.query("sortOrder") || "asc";
|
|
14306
|
+
const page = Math.max(1, Number.parseInt(c.req.query("page") || "1", 10) || 1);
|
|
14307
|
+
const pageSize = Math.min(500, Math.max(1, Number.parseInt(c.req.query("pageSize") || "50", 10) || 50));
|
|
14308
|
+
const search = c.req.query("search") || "";
|
|
13962
14309
|
let db;
|
|
13963
14310
|
try {
|
|
13964
14311
|
db = new Database(config.databasePath, { readonly: true });
|
|
@@ -13978,12 +14325,23 @@ function dataRoutes(config) {
|
|
|
13978
14325
|
}));
|
|
13979
14326
|
let orderClause = "";
|
|
13980
14327
|
if (sortField && columns.some((c$1) => c$1.name === sortField)) orderClause = ` ORDER BY \`${sortField}\` ${sortOrder.toLowerCase() === "desc" ? "DESC" : "ASC"}`;
|
|
13981
|
-
|
|
13982
|
-
const
|
|
14328
|
+
let whereClause = "";
|
|
14329
|
+
const queryParams = [];
|
|
14330
|
+
if (search) whereClause = ` WHERE ${columns.map((col) => {
|
|
14331
|
+
queryParams.push(`%${search}%`);
|
|
14332
|
+
return `\`${col.name}\` LIKE ?`;
|
|
14333
|
+
}).join(" OR ")}`;
|
|
14334
|
+
const total = db.prepare(`SELECT COUNT(*) as count FROM \`${tableName}\`${whereClause}`).get(...queryParams).count;
|
|
14335
|
+
const offset = (page - 1) * pageSize;
|
|
14336
|
+
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
|
14337
|
+
const rows = db.prepare(`SELECT * FROM \`${tableName}\`${whereClause}${orderClause} LIMIT ? OFFSET ?`).all(...queryParams, pageSize, offset);
|
|
13983
14338
|
return c.json({
|
|
13984
14339
|
columns,
|
|
13985
14340
|
rows,
|
|
13986
|
-
total
|
|
14341
|
+
total,
|
|
14342
|
+
page,
|
|
14343
|
+
pageSize,
|
|
14344
|
+
totalPages
|
|
13987
14345
|
});
|
|
13988
14346
|
} catch (error) {
|
|
13989
14347
|
return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
@@ -14011,7 +14369,6 @@ function dataRoutes(config) {
|
|
|
14011
14369
|
|
|
14012
14370
|
//#endregion
|
|
14013
14371
|
//#region src/server/routes/schema.ts
|
|
14014
|
-
const execFileAsync = promisify(execFile);
|
|
14015
14372
|
const SCHEMA_FILE_RE = /^[\w.-]+\.json$/;
|
|
14016
14373
|
const TABLE_NAME_RE = /^[a-z][a-z0-9_]*$/;
|
|
14017
14374
|
function resolveSchemaFile(schemaDir, name$1) {
|
|
@@ -14099,59 +14456,21 @@ function schemaRoutes(config) {
|
|
|
14099
14456
|
app.post("/migrate", async (c) => {
|
|
14100
14457
|
try {
|
|
14101
14458
|
await ensureDir();
|
|
14102
|
-
await
|
|
14103
|
-
|
|
14104
|
-
|
|
14105
|
-
success: false,
|
|
14106
|
-
error: "No schema files found"
|
|
14107
|
-
}, 400);
|
|
14108
|
-
const parsedResult = parseAllSchemas(await Promise.all(jsonFiles.map(async (fileName) => {
|
|
14109
|
-
const filePath = path.join(schemaDir, fileName);
|
|
14110
|
-
return {
|
|
14111
|
-
filePath,
|
|
14112
|
-
content: await fs.readFile(filePath, "utf-8")
|
|
14113
|
-
};
|
|
14114
|
-
})));
|
|
14115
|
-
if (!parsedResult.success) return c.json({
|
|
14116
|
-
success: false,
|
|
14117
|
-
error: parsedResult.error
|
|
14118
|
-
}, 400);
|
|
14119
|
-
const { tables, relations, reverseRelations, warnings, drizzleCode } = parsedResult.data;
|
|
14120
|
-
await fs.writeFile(config.drizzleSchemaPath, drizzleCode);
|
|
14121
|
-
const helperPath = resolveHelperPath();
|
|
14122
|
-
const tsxPath = resolveTsxPath();
|
|
14123
|
-
const { stdout, stderr } = await execFileAsync(process.execPath, [
|
|
14124
|
-
tsxPath,
|
|
14125
|
-
helperPath,
|
|
14126
|
-
config.drizzleSchemaPath,
|
|
14127
|
-
config.migrationsPath,
|
|
14128
|
-
config.databasePath
|
|
14129
|
-
], { cwd: process.cwd() });
|
|
14130
|
-
let migrationResult;
|
|
14131
|
-
try {
|
|
14132
|
-
const jsonLine = stdout.trim().split("\n").find((l) => l.startsWith("{") && l.endsWith("}"));
|
|
14133
|
-
if (!jsonLine) return c.json({
|
|
14134
|
-
success: false,
|
|
14135
|
-
error: "Migration helper did not return valid output"
|
|
14136
|
-
}, 500);
|
|
14137
|
-
migrationResult = JSON.parse(jsonLine);
|
|
14138
|
-
} catch {
|
|
14459
|
+
const result = await runSchemaSync(config);
|
|
14460
|
+
if (!result.success) {
|
|
14461
|
+
const status = result.schemaCount === 0 ? 400 : 500;
|
|
14139
14462
|
return c.json({
|
|
14140
14463
|
success: false,
|
|
14141
|
-
error:
|
|
14142
|
-
},
|
|
14464
|
+
error: result.error || "Migration failed"
|
|
14465
|
+
}, status);
|
|
14143
14466
|
}
|
|
14144
|
-
if (!migrationResult.success) return c.json({
|
|
14145
|
-
success: false,
|
|
14146
|
-
error: migrationResult.error || "Migration failed"
|
|
14147
|
-
}, 500);
|
|
14148
14467
|
return c.json({
|
|
14149
14468
|
success: true,
|
|
14150
|
-
changes:
|
|
14151
|
-
tag:
|
|
14152
|
-
tables: tables
|
|
14153
|
-
relations: relations
|
|
14154
|
-
warnings
|
|
14469
|
+
changes: result.migration?.changes ?? 0,
|
|
14470
|
+
tag: result.migration?.tag,
|
|
14471
|
+
tables: result.tables,
|
|
14472
|
+
relations: result.relations,
|
|
14473
|
+
warnings: result.warnings
|
|
14155
14474
|
});
|
|
14156
14475
|
} catch (error) {
|
|
14157
14476
|
return c.json({
|
|
@@ -14199,9 +14518,50 @@ function createApp(config, staticDir) {
|
|
|
14199
14518
|
return app;
|
|
14200
14519
|
}
|
|
14201
14520
|
|
|
14521
|
+
//#endregion
|
|
14522
|
+
//#region src/core/web-runner.ts
|
|
14523
|
+
const execFileAsync = promisify(execFile);
|
|
14524
|
+
function resolveWebStaticDir() {
|
|
14525
|
+
return path.join(resolvePackageRoot(), "dist/web");
|
|
14526
|
+
}
|
|
14527
|
+
async function openBrowser(url) {
|
|
14528
|
+
if (process.platform === "darwin") {
|
|
14529
|
+
await execFileAsync("open", [url]);
|
|
14530
|
+
return;
|
|
14531
|
+
}
|
|
14532
|
+
if (process.platform === "win32") {
|
|
14533
|
+
await execFileAsync("cmd", [
|
|
14534
|
+
"/c",
|
|
14535
|
+
"start",
|
|
14536
|
+
"",
|
|
14537
|
+
url
|
|
14538
|
+
]);
|
|
14539
|
+
return;
|
|
14540
|
+
}
|
|
14541
|
+
await execFileAsync("xdg-open", [url]);
|
|
14542
|
+
}
|
|
14543
|
+
async function startWebServer(input) {
|
|
14544
|
+
const { config, port } = input;
|
|
14545
|
+
const staticDir = input.staticDir ?? resolveWebStaticDir();
|
|
14546
|
+
const url = `http://localhost:${port}`;
|
|
14547
|
+
serve({
|
|
14548
|
+
fetch: createApp(config, staticDir).fetch,
|
|
14549
|
+
port
|
|
14550
|
+
}, () => {
|
|
14551
|
+
input.onStarted?.({
|
|
14552
|
+
url,
|
|
14553
|
+
schemaPath: config.schemaPath
|
|
14554
|
+
});
|
|
14555
|
+
if (input.open === false) return;
|
|
14556
|
+
openBrowser(url).catch(() => {
|
|
14557
|
+
input.onOpenFailed?.(url);
|
|
14558
|
+
});
|
|
14559
|
+
});
|
|
14560
|
+
await new Promise(() => {});
|
|
14561
|
+
}
|
|
14562
|
+
|
|
14202
14563
|
//#endregion
|
|
14203
14564
|
//#region src/commands/web.ts
|
|
14204
|
-
const execAsync = promisify(exec);
|
|
14205
14565
|
const webCommand = defineCommand({
|
|
14206
14566
|
meta: {
|
|
14207
14567
|
name: "web",
|
|
@@ -14218,23 +14578,20 @@ const webCommand = defineCommand({
|
|
|
14218
14578
|
const cwd = process.cwd();
|
|
14219
14579
|
const port = Number(args.port) || 13e3;
|
|
14220
14580
|
const config = createMigrationConfig(cwd);
|
|
14221
|
-
const packageRoot = resolvePackageRoot();
|
|
14222
|
-
const staticDir = path.join(packageRoot, "dist/web");
|
|
14223
14581
|
const s = spinner();
|
|
14224
14582
|
s.start("Starting web server...");
|
|
14225
|
-
|
|
14226
|
-
|
|
14227
|
-
port
|
|
14228
|
-
|
|
14229
|
-
|
|
14230
|
-
|
|
14231
|
-
|
|
14232
|
-
|
|
14233
|
-
|
|
14583
|
+
await startWebServer({
|
|
14584
|
+
config,
|
|
14585
|
+
port,
|
|
14586
|
+
onStarted(info) {
|
|
14587
|
+
s.stop(`Server running at ${pc.cyan(info.url)}`);
|
|
14588
|
+
consola.info(`Schema directory: ${pc.dim(info.schemaPath)}`);
|
|
14589
|
+
consola.info("Press Ctrl+C to stop");
|
|
14590
|
+
},
|
|
14591
|
+
onOpenFailed(url) {
|
|
14234
14592
|
consola.warn(`Could not open browser. Visit ${url} manually.`);
|
|
14235
|
-
}
|
|
14593
|
+
}
|
|
14236
14594
|
});
|
|
14237
|
-
await new Promise(() => {});
|
|
14238
14595
|
}
|
|
14239
14596
|
});
|
|
14240
14597
|
|