@yg-toolkit/datapull 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +74 -0
- package/dist/cli/program.d.ts +4 -0
- package/dist/cli/program.d.ts.map +1 -0
- package/dist/cli/program.js +540 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/config/dotenv.d.ts +4 -0
- package/dist/config/dotenv.d.ts.map +1 -0
- package/dist/config/dotenv.js +47 -0
- package/dist/config/dotenv.js.map +1 -0
- package/dist/config/paths.d.ts +8 -0
- package/dist/config/paths.d.ts.map +1 -0
- package/dist/config/paths.js +24 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/schema.d.ts +59 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +73 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/security.d.ts +12 -0
- package/dist/config/security.d.ts.map +1 -0
- package/dist/config/security.js +86 -0
- package/dist/config/security.js.map +1 -0
- package/dist/config/store.d.ts +19 -0
- package/dist/config/store.d.ts.map +1 -0
- package/dist/config/store.js +120 -0
- package/dist/config/store.js.map +1 -0
- package/dist/connections/service.d.ts +34 -0
- package/dist/connections/service.d.ts.map +1 -0
- package/dist/connections/service.js +220 -0
- package/dist/connections/service.js.map +1 -0
- package/dist/core/errors.d.ts +9 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +29 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/output.d.ts +16 -0
- package/dist/core/output.d.ts.map +1 -0
- package/dist/core/output.js +91 -0
- package/dist/core/output.js.map +1 -0
- package/dist/core/runtime.d.ts +3 -0
- package/dist/core/runtime.d.ts.map +1 -0
- package/dist/core/runtime.js +14 -0
- package/dist/core/runtime.js.map +1 -0
- package/dist/doctor/service.d.ts +9 -0
- package/dist/doctor/service.d.ts.map +1 -0
- package/dist/doctor/service.js +81 -0
- package/dist/doctor/service.js.map +1 -0
- package/dist/exporters/exporter.d.ts +10 -0
- package/dist/exporters/exporter.d.ts.map +1 -0
- package/dist/exporters/exporter.js +2 -0
- package/dist/exporters/exporter.js.map +1 -0
- package/dist/exporters/factory.d.ts +4 -0
- package/dist/exporters/factory.d.ts.map +1 -0
- package/dist/exporters/factory.js +11 -0
- package/dist/exporters/factory.js.map +1 -0
- package/dist/exporters/mysql.d.ts +16 -0
- package/dist/exporters/mysql.d.ts.map +1 -0
- package/dist/exporters/mysql.js +177 -0
- package/dist/exporters/mysql.js.map +1 -0
- package/dist/exporters/objects.d.ts +4 -0
- package/dist/exporters/objects.d.ts.map +1 -0
- package/dist/exporters/objects.js +32 -0
- package/dist/exporters/objects.js.map +1 -0
- package/dist/exporters/postgresql.d.ts +16 -0
- package/dist/exporters/postgresql.d.ts.map +1 -0
- package/dist/exporters/postgresql.js +228 -0
- package/dist/exporters/postgresql.js.map +1 -0
- package/dist/exporters/sql.d.ts +5 -0
- package/dist/exporters/sql.d.ts.map +1 -0
- package/dist/exporters/sql.js +14 -0
- package/dist/exporters/sql.js.map +1 -0
- package/dist/exporters/sqlserver.d.ts +11 -0
- package/dist/exporters/sqlserver.d.ts.map +1 -0
- package/dist/exporters/sqlserver.js +126 -0
- package/dist/exporters/sqlserver.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -0
- package/dist/interactive/connections.d.ts +16 -0
- package/dist/interactive/connections.d.ts.map +1 -0
- package/dist/interactive/connections.js +97 -0
- package/dist/interactive/connections.js.map +1 -0
- package/dist/interactive/wizard.d.ts +4 -0
- package/dist/interactive/wizard.d.ts.map +1 -0
- package/dist/interactive/wizard.js +284 -0
- package/dist/interactive/wizard.js.map +1 -0
- package/dist/output/transaction.d.ts +39 -0
- package/dist/output/transaction.d.ts.map +1 -0
- package/dist/output/transaction.js +329 -0
- package/dist/output/transaction.js.map +1 -0
- package/dist/process/runner.d.ts +14 -0
- package/dist/process/runner.d.ts.map +1 -0
- package/dist/process/runner.js +43 -0
- package/dist/process/runner.js.map +1 -0
- package/dist/pull/service.d.ts +24 -0
- package/dist/pull/service.d.ts.map +1 -0
- package/dist/pull/service.js +102 -0
- package/dist/pull/service.js.map +1 -0
- package/dist/skills/installer.d.ts +25 -0
- package/dist/skills/installer.d.ts.map +1 -0
- package/dist/skills/installer.js +177 -0
- package/dist/skills/installer.js.map +1 -0
- package/dist/skills/targets.d.ts +12 -0
- package/dist/skills/targets.d.ts.map +1 -0
- package/dist/skills/targets.js +37 -0
- package/dist/skills/targets.js.map +1 -0
- package/dist/tools/catalog.d.ts +17 -0
- package/dist/tools/catalog.d.ts.map +1 -0
- package/dist/tools/catalog.js +161 -0
- package/dist/tools/catalog.js.map +1 -0
- package/dist/tools/manager.d.ts +21 -0
- package/dist/tools/manager.d.ts.map +1 -0
- package/dist/tools/manager.js +110 -0
- package/dist/tools/manager.js.map +1 -0
- package/dist/types.d.ts +51 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/fs.d.ts +3 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +43 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/path.d.ts +4 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +47 -0
- package/dist/utils/path.js.map +1 -0
- package/package.json +68 -0
- package/resources/ci/mysql-seed.sql +36 -0
- package/resources/ci/postgresql-seed.sql +45 -0
- package/resources/ci/sqlserver-seed.sql +57 -0
- package/resources/compatibility-matrix.json +315 -0
- package/resources/export-sqlserver.ps1 +257 -0
- package/skill/SKILL.md +42 -0
- package/skill/agents/openai.yaml +6 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { DataPullError } from "../core/errors.js";
|
|
6
|
+
import { runProcess } from "../process/runner.js";
|
|
7
|
+
export class SqlServerExporter {
|
|
8
|
+
async listDatabases(connection) {
|
|
9
|
+
const result = await this.sqlcmd(connection, "master", "SET NOCOUNT ON; SELECT name FROM sys.databases WHERE state = 0 AND HAS_DBACCESS(name) = 1 ORDER BY name;");
|
|
10
|
+
return result
|
|
11
|
+
.split(/\r?\n/u)
|
|
12
|
+
.map((value) => value.trim())
|
|
13
|
+
.filter(Boolean);
|
|
14
|
+
}
|
|
15
|
+
async test(connection, database) {
|
|
16
|
+
const value = await this.sqlcmd(connection, database ?? "master", "SET NOCOUNT ON; SELECT CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(128));");
|
|
17
|
+
const serverVersion = value.split(/\r?\n/u).map((line) => line.trim()).find(Boolean);
|
|
18
|
+
if (serverVersion === undefined) {
|
|
19
|
+
throw new DataPullError("DATABASE_CLIENT_FAILED", "SQL Server 未返回服务端版本。", 1);
|
|
20
|
+
}
|
|
21
|
+
return { serverVersion };
|
|
22
|
+
}
|
|
23
|
+
async exportObjects(connection, database, objectTypes) {
|
|
24
|
+
await this.test(connection, database);
|
|
25
|
+
const temporary = await mkdtemp(join(tmpdir(), "datapull-sqlserver-"));
|
|
26
|
+
try {
|
|
27
|
+
const script = fileURLToPath(new URL("../../resources/export-sqlserver.ps1", import.meta.url));
|
|
28
|
+
await runProcess("pwsh", ["-NoProfile", "-NonInteractive", "-File", script, "-OutputDirectory", temporary], {
|
|
29
|
+
env: {
|
|
30
|
+
...process.env,
|
|
31
|
+
DATAPULL_SQLSERVER_HOST: required(connection.host, "host"),
|
|
32
|
+
DATAPULL_SQLSERVER_PORT: String(connection.port ?? 1433),
|
|
33
|
+
DATAPULL_SQLSERVER_DATABASE: database,
|
|
34
|
+
DATAPULL_SQLSERVER_AUTH: connection.authMode,
|
|
35
|
+
DATAPULL_SQLSERVER_USER: connection.username ?? "",
|
|
36
|
+
DATAPULL_SQLSERVER_PASSWORD: connection.password ?? "",
|
|
37
|
+
DATAPULL_SQLSERVER_TYPES: JSON.stringify(objectTypes),
|
|
38
|
+
DATAPULL_SQLSERVER_ENCRYPT: "true",
|
|
39
|
+
DATAPULL_SQLSERVER_TRUST_CERT: connection.tls?.trustServerCertificate === true ? "true" : "false",
|
|
40
|
+
},
|
|
41
|
+
timeoutMs: sqlServerExportTimeoutMs(process.env.DATAPULL_SQLSERVER_EXPORT_TIMEOUT_MS),
|
|
42
|
+
secrets: [connection.password ?? "", connection.secretUrl ?? ""],
|
|
43
|
+
});
|
|
44
|
+
const records = JSON.parse(await readFile(join(temporary, "objects.json"), "utf8"));
|
|
45
|
+
const objects = [];
|
|
46
|
+
for (const record of records) {
|
|
47
|
+
const ddl = (await readFile(join(temporary, record.file), "utf8"))
|
|
48
|
+
.replaceAll("\r\n", "\n")
|
|
49
|
+
.trim();
|
|
50
|
+
if (ddl.length === 0) {
|
|
51
|
+
throw new DataPullError("DATABASE_OBJECT_READ_FAILED", `SQL Server 对象 ${record.identity} 的 DDL 为空。`, 1);
|
|
52
|
+
}
|
|
53
|
+
objects.push({
|
|
54
|
+
type: record.objectType,
|
|
55
|
+
...(record.schema === undefined || record.schema === null
|
|
56
|
+
? {}
|
|
57
|
+
: { schema: record.schema }),
|
|
58
|
+
name: record.name,
|
|
59
|
+
identity: record.identity,
|
|
60
|
+
ddl: `${ddl}\n`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return objects;
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
await rm(temporary, { force: true, recursive: true });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async sqlcmd(connection, database, query) {
|
|
70
|
+
const args = [
|
|
71
|
+
"-S",
|
|
72
|
+
`${required(connection.host, "host")},${connection.port ?? 1433}`,
|
|
73
|
+
"-d",
|
|
74
|
+
database,
|
|
75
|
+
"-N",
|
|
76
|
+
"-b",
|
|
77
|
+
"-h",
|
|
78
|
+
"-1",
|
|
79
|
+
"-W",
|
|
80
|
+
"-Q",
|
|
81
|
+
query,
|
|
82
|
+
];
|
|
83
|
+
args.push(...sqlcmdTrustArguments(connection));
|
|
84
|
+
if (connection.authMode === "integrated")
|
|
85
|
+
args.push("-E");
|
|
86
|
+
else
|
|
87
|
+
args.push("-U", connection.username ?? "");
|
|
88
|
+
try {
|
|
89
|
+
const result = await runProcess("sqlcmd", args, {
|
|
90
|
+
env: {
|
|
91
|
+
...process.env,
|
|
92
|
+
...(connection.password === undefined
|
|
93
|
+
? {}
|
|
94
|
+
: { SQLCMDPASSWORD: connection.password }),
|
|
95
|
+
},
|
|
96
|
+
timeoutMs: 120_000,
|
|
97
|
+
secrets: [connection.password ?? "", connection.secretUrl ?? ""],
|
|
98
|
+
});
|
|
99
|
+
return result.stdout;
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error instanceof DataPullError &&
|
|
103
|
+
/certificate|证书|certificate chain|unable to verify/iu.test(JSON.stringify(error.details ?? {}))) {
|
|
104
|
+
throw new DataPullError("SQLSERVER_TLS_CERTIFICATE_UNTRUSTED", "SQL Server 证书链无法验证。请安装可信 CA 链或修复服务器证书。", 1);
|
|
105
|
+
}
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export function sqlcmdTrustArguments(connection) {
|
|
111
|
+
return connection.tls?.trustServerCertificate === true ? ["-C"] : [];
|
|
112
|
+
}
|
|
113
|
+
export function sqlServerExportTimeoutMs(value) {
|
|
114
|
+
if (value === undefined || value.trim().length === 0)
|
|
115
|
+
return 900_000;
|
|
116
|
+
const timeoutMs = Number(value);
|
|
117
|
+
if (Number.isSafeInteger(timeoutMs) && timeoutMs > 0)
|
|
118
|
+
return timeoutMs;
|
|
119
|
+
throw new DataPullError("CONFIG_INVALID", "DATAPULL_SQLSERVER_EXPORT_TIMEOUT_MS 必须是正整数毫秒值。", 3);
|
|
120
|
+
}
|
|
121
|
+
function required(value, label) {
|
|
122
|
+
if (value !== undefined && value.length > 0)
|
|
123
|
+
return value;
|
|
124
|
+
throw new DataPullError("CONFIG_INVALID", `SQL Server 连接缺少 ${label}。`, 3);
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=sqlserver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlserver.js","sourceRoot":"","sources":["../../src/exporters/sqlserver.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { CommanderError } from "commander";
|
|
3
|
+
import { buildProgram } from "./cli/program.js";
|
|
4
|
+
import { asDataPullError, DataPullError } from "./core/errors.js";
|
|
5
|
+
import { Output } from "./core/output.js";
|
|
6
|
+
import { assertSupportedNode } from "./core/runtime.js";
|
|
7
|
+
const argv = process.argv.slice(2);
|
|
8
|
+
const json = argv.includes("--json");
|
|
9
|
+
if (argv.includes("--no-color"))
|
|
10
|
+
process.env.NO_COLOR = "1";
|
|
11
|
+
const program = buildProgram();
|
|
12
|
+
program.configureOutput({
|
|
13
|
+
writeOut: (value) => {
|
|
14
|
+
if (!json)
|
|
15
|
+
process.stdout.write(value);
|
|
16
|
+
},
|
|
17
|
+
writeErr: (value) => {
|
|
18
|
+
if (!json)
|
|
19
|
+
process.stderr.write(value);
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
try {
|
|
23
|
+
assertSupportedNode();
|
|
24
|
+
await program.parseAsync(process.argv);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error instanceof CommanderError && error.exitCode === 0)
|
|
28
|
+
process.exitCode = 0;
|
|
29
|
+
else {
|
|
30
|
+
const normalized = normalizeCliError(error);
|
|
31
|
+
new Output({ command: commandName(argv), json }).failure(normalized, errorContext(argv, normalized));
|
|
32
|
+
process.exitCode = normalized.exitCode;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function normalizeCliError(error) {
|
|
36
|
+
if (error instanceof CommanderError) {
|
|
37
|
+
return new DataPullError(error.code === "commander.help" ? "INVALID_ARGUMENT" : "INVALID_ARGUMENT", error.message, 2);
|
|
38
|
+
}
|
|
39
|
+
return asDataPullError(error);
|
|
40
|
+
}
|
|
41
|
+
function commandName(args) {
|
|
42
|
+
const positional = args.filter((value, index) => {
|
|
43
|
+
if (value.startsWith("-"))
|
|
44
|
+
return false;
|
|
45
|
+
const previous = args[index - 1];
|
|
46
|
+
return previous === undefined || previous.startsWith("-") === false;
|
|
47
|
+
});
|
|
48
|
+
return positional.slice(0, 3).join(" ") || "datapull";
|
|
49
|
+
}
|
|
50
|
+
function errorContext(args, error) {
|
|
51
|
+
const details = error.details ?? {};
|
|
52
|
+
const context = {};
|
|
53
|
+
if (error.code === "OPERATION_CANCELLED")
|
|
54
|
+
context.outputFilesChanged = false;
|
|
55
|
+
for (const key of ["actionPlan", "installation", "manualRequired", "outputFilesChanged", "targets"]) {
|
|
56
|
+
if (details[key] !== undefined)
|
|
57
|
+
context[key] = details[key];
|
|
58
|
+
}
|
|
59
|
+
const connection = optionValue(args, "--connection");
|
|
60
|
+
const database = optionValue(args, "--database");
|
|
61
|
+
if (connection !== undefined)
|
|
62
|
+
context.connectionAlias = connection;
|
|
63
|
+
if (database !== undefined)
|
|
64
|
+
context.database = database;
|
|
65
|
+
return context;
|
|
66
|
+
}
|
|
67
|
+
function optionValue(args, option) {
|
|
68
|
+
const index = args.indexOf(option);
|
|
69
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ConnectionService } from "../connections/service.js";
|
|
2
|
+
import type { AuthMode, ConnectionConfig, Engine } from "../types.js";
|
|
3
|
+
export interface ConnectionInput {
|
|
4
|
+
alias?: string | undefined;
|
|
5
|
+
engine?: Engine | undefined;
|
|
6
|
+
authMode?: AuthMode | undefined;
|
|
7
|
+
host?: string | undefined;
|
|
8
|
+
port?: number | undefined;
|
|
9
|
+
username?: string | undefined;
|
|
10
|
+
credentialRef?: string | undefined;
|
|
11
|
+
urlRef?: string | undefined;
|
|
12
|
+
sslMode?: string | undefined;
|
|
13
|
+
trustServerCertificate?: boolean | undefined;
|
|
14
|
+
}
|
|
15
|
+
export declare function promptAndAddConnection(service: ConnectionService, initial?: ConnectionInput): Promise<ConnectionConfig>;
|
|
16
|
+
//# sourceMappingURL=connections.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connections.d.ts","sourceRoot":"","sources":["../../src/interactive/connections.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAoB,MAAM,2BAA2B,CAAC;AAEhF,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAEtE,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,sBAAsB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC9C;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,iBAAiB,EAC1B,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,gBAAgB,CAAC,CAsG3B"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { confirm, input, password, select } from "@inquirer/prompts";
|
|
2
|
+
import { ConnectionService, createConnection } from "../connections/service.js";
|
|
3
|
+
import { DataPullError } from "../core/errors.js";
|
|
4
|
+
export async function promptAndAddConnection(service, initial = {}) {
|
|
5
|
+
const alias = initial.alias ?? (await input({ message: "连接别名:", required: true }));
|
|
6
|
+
if ((await service.list()).some((connection) => connection.alias.toLocaleLowerCase("en-US") === alias.toLocaleLowerCase("en-US"))) {
|
|
7
|
+
throw new DataPullError("CONNECTION_ALIAS_CONFLICT", `连接别名已存在:${alias}`, 2);
|
|
8
|
+
}
|
|
9
|
+
const engine = initial.engine ??
|
|
10
|
+
(await select({
|
|
11
|
+
message: "数据库类型:",
|
|
12
|
+
choices: [
|
|
13
|
+
{ name: "MySQL", value: "mysql" },
|
|
14
|
+
{ name: "PostgreSQL", value: "postgresql" },
|
|
15
|
+
{ name: "SQL Server", value: "sqlserver" },
|
|
16
|
+
],
|
|
17
|
+
}));
|
|
18
|
+
const authMode = initial.authMode ??
|
|
19
|
+
(await select({
|
|
20
|
+
message: "认证方式:",
|
|
21
|
+
choices: [
|
|
22
|
+
{ name: "用户名和密码", value: "password" },
|
|
23
|
+
{ name: "完整连接 URL", value: "url" },
|
|
24
|
+
...(engine === "sqlserver" && process.platform === "win32"
|
|
25
|
+
? [{ name: "Windows 集成认证", value: "integrated" }]
|
|
26
|
+
: []),
|
|
27
|
+
],
|
|
28
|
+
}));
|
|
29
|
+
if (authMode === "integrated" && (engine !== "sqlserver" || process.platform !== "win32")) {
|
|
30
|
+
throw new DataPullError("INVALID_ARGUMENT", "Windows 集成认证只支持 Windows 上的 SQL Server。", 2);
|
|
31
|
+
}
|
|
32
|
+
let host = initial.host;
|
|
33
|
+
let port = initial.port;
|
|
34
|
+
let username = initial.username;
|
|
35
|
+
let credentialRef = initial.credentialRef;
|
|
36
|
+
let urlRef = initial.urlRef;
|
|
37
|
+
let pendingSecret;
|
|
38
|
+
if (authMode !== "url") {
|
|
39
|
+
host ??= await input({ message: "数据库主机:", required: true });
|
|
40
|
+
if (port === undefined) {
|
|
41
|
+
const defaultPort = engine === "mysql" ? 3306 : engine === "postgresql" ? 5432 : 1433;
|
|
42
|
+
const value = await input({ message: "端口:", default: String(defaultPort) });
|
|
43
|
+
port = Number(value);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (authMode === "password") {
|
|
47
|
+
username ??= await input({ message: "用户名:", required: true });
|
|
48
|
+
credentialRef ??= await input({
|
|
49
|
+
message: "密码变量名:",
|
|
50
|
+
default: defaultReference(alias, "PASSWORD"),
|
|
51
|
+
});
|
|
52
|
+
pendingSecret = await password({ message: "数据库密码(输入不会回显):", mask: "*" });
|
|
53
|
+
}
|
|
54
|
+
else if (authMode === "url") {
|
|
55
|
+
urlRef ??= await input({
|
|
56
|
+
message: "连接 URL 变量名:",
|
|
57
|
+
default: defaultReference(alias, "URL"),
|
|
58
|
+
});
|
|
59
|
+
pendingSecret = await password({ message: "完整连接 URL(输入不会回显):", mask: "*" });
|
|
60
|
+
}
|
|
61
|
+
const connection = createConnection({
|
|
62
|
+
alias,
|
|
63
|
+
engine,
|
|
64
|
+
authMode,
|
|
65
|
+
...(host === undefined ? {} : { host }),
|
|
66
|
+
...(port === undefined ? {} : { port }),
|
|
67
|
+
...(username === undefined ? {} : { username }),
|
|
68
|
+
...(credentialRef === undefined ? {} : { credentialRef }),
|
|
69
|
+
...(urlRef === undefined ? {} : { urlRef }),
|
|
70
|
+
...(initial.sslMode === undefined ? {} : { sslMode: initial.sslMode }),
|
|
71
|
+
...(initial.trustServerCertificate === undefined
|
|
72
|
+
? {}
|
|
73
|
+
: { trustServerCertificate: initial.trustServerCertificate }),
|
|
74
|
+
});
|
|
75
|
+
if (connection.tls?.trustServerCertificate === true) {
|
|
76
|
+
process.stderr.write("警告:该连接将保持加密,但不会验证 SQL Server 的身份,可能受到中间人攻击。\n");
|
|
77
|
+
}
|
|
78
|
+
const approved = await confirm({
|
|
79
|
+
message: `确认登记连接 ${connection.alias}(${connection.engine} / ${connection.authMode})?`,
|
|
80
|
+
default: true,
|
|
81
|
+
});
|
|
82
|
+
if (!approved)
|
|
83
|
+
throw new DataPullError("OPERATION_CANCELLED", "已取消登记连接。", 1);
|
|
84
|
+
const reference = authMode === "url" ? urlRef : credentialRef;
|
|
85
|
+
if (pendingSecret !== undefined && reference !== undefined) {
|
|
86
|
+
await service.store.setCredential(reference, pendingSecret);
|
|
87
|
+
}
|
|
88
|
+
return service.add(connection);
|
|
89
|
+
}
|
|
90
|
+
function defaultReference(alias, suffix) {
|
|
91
|
+
const stem = alias
|
|
92
|
+
.toUpperCase()
|
|
93
|
+
.replace(/[^A-Z0-9_]/gu, "_")
|
|
94
|
+
.replace(/^[^A-Z_]/u, "_$&");
|
|
95
|
+
return `DATAPULL_${stem}_${suffix}`;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=connections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connections.js","sourceRoot":"","sources":["../../src/interactive/connections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAChF,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAgBlD,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,OAA0B,EAC1B,OAAO,GAAoB,EAAE;IAE7B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACnF,IACE,CAAC,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CACzB,CAAC,UAAU,EAAE,EAAE,CACb,UAAU,CAAC,KAAK,CAAC,iBAAiB,CAAC,OAAO,CAAC,KAAK,KAAK,CAAC,iBAAiB,CAAC,OAAO,CAAC,CACnF,EACD,CAAC;QACD,MAAM,IAAI,aAAa,CACrB,2BAA2B,EAC3B,WAAW,KAAK,EAAE,EAClB,CAAC,CACF,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GACV,OAAO,CAAC,MAAM;QACd,CAAC,MAAM,MAAM,CAAS;YACpB,OAAO,EAAE,QAAQ;YACjB,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE;gBACjC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE;gBAC3C,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE;aAC3C;SACF,CAAC,CAAC,CAAC;IACN,MAAM,QAAQ,GACZ,OAAO,CAAC,QAAQ;QAChB,CAAC,MAAM,MAAM,CAAW;YACtB,OAAO,EAAE,OAAO;YAChB,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE;gBACrC,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE;gBAClC,GAAG,CAAC,MAAM,KAAK,WAAW,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;oBACxD,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,YAAqB,EAAE,CAAC;oBAC1D,CAAC,CAAC,EAAE,CAAC;aACR;SACF,CAAC,CAAC,CAAC;IACN,IAAI,QAAQ,KAAK,YAAY,IAAI,CAAC,MAAM,KAAK,WAAW,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,EAAE,CAAC;QAC1F,MAAM,IAAI,aAAa,CACrB,kBAAkB,EAClB,wCAAwC,EACxC,CAAC,CACF,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IACxB,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IACxB,IAAI,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAChC,IAAI,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;IAC1C,IAAI,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAC5B,IAAI,aAAiC,CAAC;IACtC,IAAI,QAAQ,KAAK,KAAK,EAAE,CAAC;QACvB,IAAI,KAAK,MAAM,KAAK,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,MAAM,WAAW,GAAG,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;YACtF,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YAC5E,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IACD,IAAI,QAAQ,KAAK,UAAU,EAAE,CAAC;QAC5B,QAAQ,KAAK,MAAM,KAAK,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9D,aAAa,KAAK,MAAM,KAAK,CAAC;YAC5B,OAAO,EAAE,QAAQ;YACjB,OAAO,EAAE,gBAAgB,CAAC,KAAK,EAAE,UAAU,CAAC;SAC7C,CAAC,CAAC;QACH,aAAa,GAAG,MAAM,QAAQ,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IAC3E,CAAC;SAAM,IAAI,QAAQ,KAAK,KAAK,EAAE,CAAC;QAC9B,MAAM,KAAK,MAAM,KAAK,CAAC;YACrB,OAAO,EAAE,aAAa;YACtB,OAAO,EAAE,gBAAgB,CAAC,KAAK,EAAE,KAAK,CAAC;SACxC,CAAC,CAAC;QACH,aAAa,GAAG,MAAM,QAAQ,CAAC,EAAE,OAAO,EAAE,mBAAmB,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9E,CAAC;IAED,MAAM,UAAU,GAAG,gBAAgB,CAAC;QAClC,KAAK;QACL,MAAM;QACN,QAAQ;QACR,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;QACvC,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;QACvC,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;QAC/C,GAAG,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC;QACzD,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;QAC3C,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;QACtE,GAAG,CAAC,OAAO,CAAC,sBAAsB,KAAK,SAAS;YAC9C,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,sBAAsB,EAAE,OAAO,CAAC,sBAAsB,EAAE,CAAC;KAChE,CAAC,CAAC;IACH,IAAI,UAAU,CAAC,GAAG,EAAE,sBAAsB,KAAK,IAAI,EAAE,CAAC;QACpD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,+CAA+C,CAChD,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC;QAC7B,OAAO,EAAE,UAAU,UAAU,CAAC,KAAK,IAAI,UAAU,CAAC,MAAM,MAAM,UAAU,CAAC,QAAQ,IAAI;QACrF,OAAO,EAAE,IAAI;KACd,CAAC,CAAC;IACH,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,aAAa,CAAC,qBAAqB,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;IAC7E,MAAM,SAAS,GAAG,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC;IAC9D,IAAI,aAAa,KAAK,SAAS,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC3D,MAAM,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;AACjC,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAa,EAAE,MAAc;IACrD,MAAM,IAAI,GAAG,KAAK;SACf,WAAW,EAAE;SACb,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC;SAC5B,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IAC/B,OAAO,YAAY,IAAI,IAAI,MAAM,EAAE,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wizard.d.ts","sourceRoot":"","sources":["../../src/interactive/wizard.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAIjD,OAAO,EAAe,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAQ3E,wBAAsB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAyGhF"}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { checkbox, confirm, input, password, select } from "@inquirer/prompts";
|
|
2
|
+
import { Listr } from "listr2";
|
|
3
|
+
import { ConnectionService } from "../connections/service.js";
|
|
4
|
+
import { ConfigStore } from "../config/store.js";
|
|
5
|
+
import { DataPullError, asDataPullError } from "../core/errors.js";
|
|
6
|
+
import { exporterFor } from "../exporters/factory.js";
|
|
7
|
+
import { commonObjectTypes, OBJECT_TYPES } from "../exporters/objects.js";
|
|
8
|
+
import { PullService } from "../pull/service.js";
|
|
9
|
+
import { SkillInstaller } from "../skills/installer.js";
|
|
10
|
+
import { AGENTS, parseSkillTarget } from "../skills/targets.js";
|
|
11
|
+
import { ToolManager } from "../tools/manager.js";
|
|
12
|
+
import { discoverProjectRoot, validatePathSegment } from "../utils/path.js";
|
|
13
|
+
import { promptAndAddConnection } from "./connections.js";
|
|
14
|
+
export async function runWizard(store) {
|
|
15
|
+
const created = !(await store.exists());
|
|
16
|
+
if (created) {
|
|
17
|
+
process.stdout.write(`DataPull 将创建用户级配置:${store.paths.root}\n`);
|
|
18
|
+
const approved = await confirm({ message: "继续初始化?", default: true });
|
|
19
|
+
if (!approved)
|
|
20
|
+
throw new DataPullError("OPERATION_CANCELLED", "已取消初始化。", 1);
|
|
21
|
+
}
|
|
22
|
+
await store.initialize();
|
|
23
|
+
const connections = new ConnectionService(store);
|
|
24
|
+
await promptSkillSetup(store);
|
|
25
|
+
let connection = await chooseConnection(connections);
|
|
26
|
+
let trustServerCertificateOnce = false;
|
|
27
|
+
while (true) {
|
|
28
|
+
const validation = await validateConnectionWithRecovery(connections, connection);
|
|
29
|
+
if (validation !== undefined) {
|
|
30
|
+
connection = validation.connection;
|
|
31
|
+
trustServerCertificateOnce = validation.trustServerCertificateOnce;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
connection = await chooseConnection(connections);
|
|
35
|
+
}
|
|
36
|
+
const resolved = await connections.resolve(connection.alias, undefined, {
|
|
37
|
+
...(trustServerCertificateOnce ? { trustServerCertificate: true } : {}),
|
|
38
|
+
});
|
|
39
|
+
const exporter = exporterFor(connection.engine);
|
|
40
|
+
let enumerated = [];
|
|
41
|
+
try {
|
|
42
|
+
enumerated = await exporter.listDatabases(resolved);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
const normalized = asDataPullError(error);
|
|
46
|
+
process.stderr.write(`无法枚举数据库 [${normalized.code}],仍可手工输入。\n`);
|
|
47
|
+
}
|
|
48
|
+
const database = await chooseDatabase(connection, enumerated);
|
|
49
|
+
validatePathSegment(database, "数据库名");
|
|
50
|
+
await exporter.test(await connections.resolve(connection.alias, database, {
|
|
51
|
+
...(trustServerCertificateOnce ? { trustServerCertificate: true } : {}),
|
|
52
|
+
}), database);
|
|
53
|
+
const common = new Set(commonObjectTypes(connection.engine));
|
|
54
|
+
const selectedTypes = await checkbox({
|
|
55
|
+
message: "选择要获取的数据库对象(默认全选):",
|
|
56
|
+
required: true,
|
|
57
|
+
choices: OBJECT_TYPES[connection.engine].map((type) => ({
|
|
58
|
+
name: `${common.has(type) ? "常用" : "高级"} · ${type}`,
|
|
59
|
+
value: type,
|
|
60
|
+
checked: true,
|
|
61
|
+
})),
|
|
62
|
+
});
|
|
63
|
+
const projectRoot = await discoverProjectRoot();
|
|
64
|
+
process.stdout.write([
|
|
65
|
+
"\n本次操作:",
|
|
66
|
+
` 连接:${connection.alias}`,
|
|
67
|
+
` 数据库:${database}`,
|
|
68
|
+
` 对象类型:${selectedTypes.join("、")}`,
|
|
69
|
+
` 项目根:${projectRoot}`,
|
|
70
|
+
...(connection.tls?.trustServerCertificate === true || trustServerCertificateOnce
|
|
71
|
+
? [" SQL Server TLS:连接已加密,但不验证服务器身份"]
|
|
72
|
+
: []),
|
|
73
|
+
" 覆盖规则:所选类型整体更新;未选类型保留;任一所选类型失败则均不更新。",
|
|
74
|
+
].join("\n") + "\n");
|
|
75
|
+
const approved = await confirm({ message: "开始获取结构文件?", default: true });
|
|
76
|
+
if (!approved)
|
|
77
|
+
throw new DataPullError("OPERATION_CANCELLED", "已取消结构文件拉取。", 1);
|
|
78
|
+
const pull = new PullService(connections);
|
|
79
|
+
let result;
|
|
80
|
+
const tasks = new Listr([
|
|
81
|
+
{
|
|
82
|
+
title: "获取数据库结构文件",
|
|
83
|
+
task: async (_context, task) => {
|
|
84
|
+
result = await pull.execute({
|
|
85
|
+
connectionAlias: connection.alias,
|
|
86
|
+
database,
|
|
87
|
+
include: selectedTypes,
|
|
88
|
+
installMissing: true,
|
|
89
|
+
confirmed: true,
|
|
90
|
+
trustServerCertificate: trustServerCertificateOnce,
|
|
91
|
+
projectRoot,
|
|
92
|
+
onStage: (stage) => {
|
|
93
|
+
task.output = stage;
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
], { concurrent: false });
|
|
99
|
+
await tasks.run();
|
|
100
|
+
if (result === undefined) {
|
|
101
|
+
throw new DataPullError("UNEXPECTED_ERROR", "拉取任务未返回结果。", 1);
|
|
102
|
+
}
|
|
103
|
+
const finalResult = result;
|
|
104
|
+
process.stdout.write(`\n获取成功:${Object.values(finalResult.objectCounts).reduce((sum, count) => sum + count, 0)} 个对象\n` +
|
|
105
|
+
`输出目录:${finalResult.outputPath}\n` +
|
|
106
|
+
`已更新:${finalResult.updatedTypes.join("、")}\n` +
|
|
107
|
+
`已保留:${finalResult.preservedTypes.join("、") || "无"}\n`);
|
|
108
|
+
return finalResult;
|
|
109
|
+
}
|
|
110
|
+
async function chooseConnection(service) {
|
|
111
|
+
const connections = await service.list();
|
|
112
|
+
const value = await select({
|
|
113
|
+
message: "选择登记连接:",
|
|
114
|
+
choices: [
|
|
115
|
+
...connections.map((connection) => ({
|
|
116
|
+
name: `${connection.alias} · ${connection.engine}${connection.tls?.trustServerCertificate === true ? " · ⚠ 未验证服务器身份" : ""}`,
|
|
117
|
+
value: connection.alias,
|
|
118
|
+
})),
|
|
119
|
+
{ name: "+ 添加新数据库连接", value: "__add__" },
|
|
120
|
+
],
|
|
121
|
+
});
|
|
122
|
+
return value === "__add__" ? promptAndAddConnection(service) : service.get(value);
|
|
123
|
+
}
|
|
124
|
+
async function validateConnectionWithRecovery(service, connection) {
|
|
125
|
+
const tools = new ToolManager();
|
|
126
|
+
let current = connection;
|
|
127
|
+
let trustServerCertificateOnce = false;
|
|
128
|
+
while (true) {
|
|
129
|
+
try {
|
|
130
|
+
const missing = await tools.missing(current.engine);
|
|
131
|
+
if (missing.length > 0) {
|
|
132
|
+
const plans = tools.plans(missing);
|
|
133
|
+
process.stdout.write("\n检测到缺少数据库工具:\n");
|
|
134
|
+
for (const plan of plans) {
|
|
135
|
+
process.stdout.write(`- ${plan.tool}\n 来源:${plan.source}\n 官方说明:${plan.sourceUrl}\n 命令:${plan.command} ${plan.args.join(" ")}\n 权限:${plan.permission}\n 影响:${plan.downloadImpact}\n`);
|
|
136
|
+
}
|
|
137
|
+
const install = await confirm({ message: "确认自动安装以上工具?", default: false });
|
|
138
|
+
if (!install) {
|
|
139
|
+
throw new DataPullError("DATABASE_TOOL_MISSING", "缺少数据库工具,请按以上命令手工安装后重试。", 4);
|
|
140
|
+
}
|
|
141
|
+
await tools.ensure(current.engine, true, true);
|
|
142
|
+
}
|
|
143
|
+
const resolved = await service.resolve(current.alias, undefined, {
|
|
144
|
+
...(trustServerCertificateOnce ? { trustServerCertificate: true } : {}),
|
|
145
|
+
});
|
|
146
|
+
await exporterFor(current.engine).test(resolved);
|
|
147
|
+
process.stdout.write(`连接 ${current.alias} 校验通过。\n`);
|
|
148
|
+
return { connection: current, trustServerCertificateOnce };
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
const normalized = asDataPullError(error);
|
|
152
|
+
process.stderr.write(`连接校验失败 [${normalized.code}]:${normalized.message}\n`);
|
|
153
|
+
if (current.engine === "sqlserver" &&
|
|
154
|
+
normalized.code === "SQLSERVER_TLS_CERTIFICATE_UNTRUSTED") {
|
|
155
|
+
const tlsAction = await select({
|
|
156
|
+
message: "服务器证书不受信任。信任后连接仍加密,但无法验证服务器身份,可能受到中间人攻击:",
|
|
157
|
+
choices: [
|
|
158
|
+
{ name: "仅本次信任并继续", value: "once" },
|
|
159
|
+
{ name: "保存到该连接并继续", value: "save" },
|
|
160
|
+
{ name: "返回连接选择", value: "back" },
|
|
161
|
+
{ name: "退出", value: "exit" },
|
|
162
|
+
],
|
|
163
|
+
});
|
|
164
|
+
if (tlsAction === "once") {
|
|
165
|
+
trustServerCertificateOnce = true;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (tlsAction === "save") {
|
|
169
|
+
const approved = await confirm({
|
|
170
|
+
message: "确认保存该风险设置?可稍后使用 connection update 恢复证书验证。",
|
|
171
|
+
default: false,
|
|
172
|
+
});
|
|
173
|
+
if (!approved)
|
|
174
|
+
continue;
|
|
175
|
+
current = await service.update(current.alias, {
|
|
176
|
+
tls: { encrypt: true, trustServerCertificate: true },
|
|
177
|
+
});
|
|
178
|
+
trustServerCertificateOnce = false;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (tlsAction === "back")
|
|
182
|
+
return undefined;
|
|
183
|
+
throw new DataPullError("OPERATION_CANCELLED", "已退出向导。", 1);
|
|
184
|
+
}
|
|
185
|
+
const action = await select({
|
|
186
|
+
message: "请选择恢复操作:",
|
|
187
|
+
choices: [
|
|
188
|
+
{ name: "重试", value: "retry" },
|
|
189
|
+
{ name: "更新凭证", value: "credential" },
|
|
190
|
+
{ name: "返回连接选择", value: "back" },
|
|
191
|
+
{ name: "退出", value: "exit" },
|
|
192
|
+
],
|
|
193
|
+
});
|
|
194
|
+
if (action === "retry")
|
|
195
|
+
continue;
|
|
196
|
+
if (action === "back")
|
|
197
|
+
return undefined;
|
|
198
|
+
if (action === "exit")
|
|
199
|
+
throw new DataPullError("OPERATION_CANCELLED", "已退出向导。", 1);
|
|
200
|
+
const reference = current.authMode === "url" ? current.urlRef : current.credentialRef;
|
|
201
|
+
if (reference === undefined) {
|
|
202
|
+
process.stderr.write("该连接没有可更新的凭证引用。\n");
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const value = await password({ message: `更新 ${reference}(输入不会回显):`, mask: "*" });
|
|
206
|
+
await service.store.setCredential(reference, value);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async function chooseDatabase(connection, enumerated) {
|
|
211
|
+
const values = unique([
|
|
212
|
+
...connection.favoriteDatabases,
|
|
213
|
+
...connection.recentDatabases,
|
|
214
|
+
...enumerated,
|
|
215
|
+
]);
|
|
216
|
+
const manual = "__manual__";
|
|
217
|
+
const chosen = await select({
|
|
218
|
+
message: "选择目标数据库:",
|
|
219
|
+
choices: [
|
|
220
|
+
...values.map((database) => ({
|
|
221
|
+
name: `${connection.favoriteDatabases.includes(database) ? "★ " : ""}${database}`,
|
|
222
|
+
value: database,
|
|
223
|
+
})),
|
|
224
|
+
{ name: "手工输入数据库名", value: manual },
|
|
225
|
+
],
|
|
226
|
+
});
|
|
227
|
+
return chosen === manual
|
|
228
|
+
? input({ message: "目标数据库名:", required: true })
|
|
229
|
+
: chosen;
|
|
230
|
+
}
|
|
231
|
+
async function promptSkillSetup(store) {
|
|
232
|
+
const config = await store.read();
|
|
233
|
+
if (config.onboarding.skillPrompted)
|
|
234
|
+
return;
|
|
235
|
+
const install = await confirm({
|
|
236
|
+
message: "是否为 Agent 安装 DataPull Skill?稍后也可运行 datapull skill install。",
|
|
237
|
+
default: true,
|
|
238
|
+
});
|
|
239
|
+
if (install) {
|
|
240
|
+
const agents = await checkbox({
|
|
241
|
+
message: "选择 Agent 工具(不检测本机是否已安装):",
|
|
242
|
+
required: true,
|
|
243
|
+
choices: AGENTS.map((agent) => ({ name: agentLabel(agent), value: agent })),
|
|
244
|
+
});
|
|
245
|
+
const projectRoot = await discoverProjectRoot();
|
|
246
|
+
const targets = [];
|
|
247
|
+
for (const agent of agents) {
|
|
248
|
+
const scopes = await checkbox({
|
|
249
|
+
message: `${agentLabel(agent)} 的安装位置:`,
|
|
250
|
+
required: true,
|
|
251
|
+
choices: [
|
|
252
|
+
{ name: "用户级", value: "user", checked: true },
|
|
253
|
+
{ name: "项目级", value: "project" },
|
|
254
|
+
],
|
|
255
|
+
});
|
|
256
|
+
for (const scope of scopes)
|
|
257
|
+
targets.push(parseSkillTarget(`${agent}:${scope}`, projectRoot));
|
|
258
|
+
}
|
|
259
|
+
process.stdout.write(`\n将写入以下 Skill 目标:\n${targets.map((target) => `- ${target.agent}:${target.scope} → ${target.path}`).join("\n")}\n`);
|
|
260
|
+
if (await confirm({ message: "确认创建以上目录并安装 Skill?", default: true })) {
|
|
261
|
+
const installer = new SkillInstaller();
|
|
262
|
+
for (const target of targets) {
|
|
263
|
+
const status = await installer.status(target);
|
|
264
|
+
if (status.status === "missing")
|
|
265
|
+
await installer.install(target, true);
|
|
266
|
+
else
|
|
267
|
+
process.stdout.write(`跳过已有目标:${target.path}(${status.status})\n`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
await store.write({ ...config, onboarding: { skillPrompted: true } });
|
|
272
|
+
}
|
|
273
|
+
function unique(values) {
|
|
274
|
+
return [...new Set(values)];
|
|
275
|
+
}
|
|
276
|
+
function agentLabel(agent) {
|
|
277
|
+
return {
|
|
278
|
+
codex: "Codex",
|
|
279
|
+
claude: "Claude Code",
|
|
280
|
+
cursor: "Cursor",
|
|
281
|
+
trae: "Trae IDE",
|
|
282
|
+
}[agent];
|
|
283
|
+
}
|
|
284
|
+
//# sourceMappingURL=wizard.js.map
|