@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 YG Toolkit contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# DataPull
|
|
2
|
+
|
|
3
|
+
DataPull 是一款面向人类交互、同时提供稳定 Agent 自动化接口的数据库结构文件拉取 CLI。它将 MySQL、PostgreSQL 或 SQL Server 的 DDL 按对象保存到当前项目根目录的 `.database-schema/`,不拉取业务数据、用户、角色或授权。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install --global @yg-toolkit/datapull
|
|
9
|
+
datapull
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
需要 Node.js `>=22.12`。只执行 `datapull` 会进入中文引导流程;NPM 安装生命周期不会启动交互、访问数据库或安装 Agent Skill。
|
|
13
|
+
|
|
14
|
+
## 常用命令
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# 进入完整中文向导
|
|
18
|
+
datapull
|
|
19
|
+
|
|
20
|
+
# 查看脱敏连接
|
|
21
|
+
datapull connection list
|
|
22
|
+
|
|
23
|
+
# 拉取表和视图;连接必须已经登记
|
|
24
|
+
datapull pull --connection main --database app --include table,view --yes
|
|
25
|
+
|
|
26
|
+
# Agent 友好的机器可读输出
|
|
27
|
+
datapull pull --connection main --database app --include table,view --yes --json
|
|
28
|
+
|
|
29
|
+
# 为使用自签名证书的 SQL Server 保存显式信任设置
|
|
30
|
+
datapull connection update --alias sqlserver-dev --trust-server-certificate --yes
|
|
31
|
+
|
|
32
|
+
# 恢复严格证书验证
|
|
33
|
+
datapull connection update --alias sqlserver-dev --verify-server-certificate --yes
|
|
34
|
+
|
|
35
|
+
# 诊断环境,不自动安装
|
|
36
|
+
datapull doctor --json
|
|
37
|
+
|
|
38
|
+
# 为 Codex 用户级目录安装内置 Skill
|
|
39
|
+
datapull skill install --target codex:user --yes
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
完整命令族包括 `connection add|list|show|update|remove|test`、`database list|favorite add|favorite remove`、`pull`、`skill install|list|status|sync`、`config path|validate` 和 `doctor`。
|
|
43
|
+
|
|
44
|
+
## 配置和秘密
|
|
45
|
+
|
|
46
|
+
登记连接是当前操作系统用户的全局配置:
|
|
47
|
+
|
|
48
|
+
- Windows:`%APPDATA%/datapull/`
|
|
49
|
+
- macOS:`$HOME/Library/Application Support/datapull/`
|
|
50
|
+
- Linux:`${XDG_CONFIG_HOME:-$HOME/.config}/datapull/`
|
|
51
|
+
|
|
52
|
+
非秘密连接信息保存在 `config.json`,密码或完整含秘密 URL 保存在权限受限的 `credentials.env`。秘密只能通过交互式隐藏输入或用户手工编辑该文件写入;不要把密码放入命令参数。
|
|
53
|
+
|
|
54
|
+
SQL Server 始终启用传输加密并默认严格验证服务器证书。自签名或内部 CA 环境可在用户明确确认后,仅本次使用 `--trust-server-certificate --yes`,或把该设置保存到登记连接。该模式不会关闭加密,但无法验证服务器身份,适合已确认风险的开发、测试或受控内网;生产连接优先安装可信 CA,并保持严格验证。
|
|
55
|
+
|
|
56
|
+
## 覆盖规则
|
|
57
|
+
|
|
58
|
+
本次选择的对象类型作为一个文件事务整体更新;未选择类型原样保留。任一所选类型读取、生成、校验或提交失败时,所有所选类型保留上一次结果。进程异常终止后,下一次访问同一目标时会先执行持久化事务恢复。
|
|
59
|
+
|
|
60
|
+
SQL 文件默认可纳入 Git。DataPull 只在 `.database-schema/.gitignore` 中忽略临时目录、锁、事务日志和提交期备份,不修改项目根 `.gitignore`。
|
|
61
|
+
|
|
62
|
+
## 开发验证
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm install --ignore-scripts
|
|
66
|
+
npm run check
|
|
67
|
+
npm test
|
|
68
|
+
npm run build
|
|
69
|
+
npm run skill:check
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`npm run release:check` 验证 `resources/compatibility-matrix.json` 是否已经包含三种数据库、五类操作系统、NPM 安装及八个 Skill 目标路径的真实通过记录。开发包的空矩阵会阻止公开发布,不能用本地单元测试替代真实数据库或跨平台验收。Agent 实际发现采用独立兼容性认证;未认证组合必须保留原因并标为计划兼容,不得宣称已支持。
|
|
73
|
+
|
|
74
|
+
跨平台验收使用仓库的“DataPull 兼容性验收”GitHub Actions 工作流。远程数据库、Actions Secrets、自托管运行器和取证步骤见 [兼容性 CI 说明](https://github.com/phoben/agent-skills/blob/main/cli/datapull/docs/release-ci.md)。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../../src/cli/program.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAQ5C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AA+BjD,wBAAgB,YAAY,CAAC,KAAK,cAAoB,GAAG,OAAO,CA+B/D"}
|
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
import { confirm, password } from "@inquirer/prompts";
|
|
2
|
+
import { Command, Option } from "commander";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { ConnectionService, connectionSecurityWarnings, createConnection, redactedConnection, } from "../connections/service.js";
|
|
5
|
+
import { ConfigStore } from "../config/store.js";
|
|
6
|
+
import { DataPullError } from "../core/errors.js";
|
|
7
|
+
import { Output } from "../core/output.js";
|
|
8
|
+
import { runDoctor } from "../doctor/service.js";
|
|
9
|
+
import { exporterFor } from "../exporters/factory.js";
|
|
10
|
+
import { promptAndAddConnection } from "../interactive/connections.js";
|
|
11
|
+
import { runWizard } from "../interactive/wizard.js";
|
|
12
|
+
import { PullService } from "../pull/service.js";
|
|
13
|
+
import { SkillInstaller } from "../skills/installer.js";
|
|
14
|
+
import { allSkillTargets, parseSkillTarget } from "../skills/targets.js";
|
|
15
|
+
import { discoverProjectRoot, validatePathSegment } from "../utils/path.js";
|
|
16
|
+
import { ToolManager } from "../tools/manager.js";
|
|
17
|
+
const packageJson = createRequire(import.meta.url)("../../package.json");
|
|
18
|
+
export function buildProgram(store = new ConfigStore()) {
|
|
19
|
+
const program = new Command();
|
|
20
|
+
program
|
|
21
|
+
.name("datapull")
|
|
22
|
+
.description("交互式拉取 MySQL、PostgreSQL 与 SQL Server 数据库结构文件")
|
|
23
|
+
.version(packageJson.version)
|
|
24
|
+
.option("--json", "仅在结束时向 stdout 输出一个 JSON 文档")
|
|
25
|
+
.option("--yes", "确认当前命令声明的写入或安装操作")
|
|
26
|
+
.option("--no-color", "禁用 ANSI 色彩")
|
|
27
|
+
.showHelpAfterError()
|
|
28
|
+
.exitOverride();
|
|
29
|
+
program.action(async (_options, command) => {
|
|
30
|
+
const global = globals(command);
|
|
31
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY || global.json === true) {
|
|
32
|
+
throw new DataPullError("INTERACTIVE_TTY_REQUIRED", "无参数 datapull 需要交互式终端;自动化场景请使用子命令和 --json。", 2);
|
|
33
|
+
}
|
|
34
|
+
await runWizard(store);
|
|
35
|
+
});
|
|
36
|
+
registerConnectionCommands(program, store);
|
|
37
|
+
registerDatabaseCommands(program, store);
|
|
38
|
+
registerPullCommand(program, store);
|
|
39
|
+
registerSkillCommands(program);
|
|
40
|
+
registerConfigCommands(program, store);
|
|
41
|
+
registerDoctorCommand(program, store);
|
|
42
|
+
return program;
|
|
43
|
+
}
|
|
44
|
+
function registerConnectionCommands(program, store) {
|
|
45
|
+
const connection = program.command("connection").description("管理用户级数据库连接");
|
|
46
|
+
connection
|
|
47
|
+
.command("add")
|
|
48
|
+
.description("登记数据库连接")
|
|
49
|
+
.option("--alias <alias>", "连接别名")
|
|
50
|
+
.addOption(new Option("--engine <engine>", "数据库类型").choices(["mysql", "postgresql", "sqlserver"]))
|
|
51
|
+
.addOption(new Option("--auth-mode <mode>", "认证方式").choices(["password", "url", "integrated"]))
|
|
52
|
+
.option("--host <host>", "数据库主机")
|
|
53
|
+
.option("--port <port>", "端口", numberParser)
|
|
54
|
+
.option("--username <username>", "用户名")
|
|
55
|
+
.option("--credential-ref <name>", "密码变量名")
|
|
56
|
+
.option("--url-ref <name>", "连接 URL 变量名")
|
|
57
|
+
.option("--ssl-mode <mode>", "MySQL/PostgreSQL TLS 模式")
|
|
58
|
+
.option("--trust-server-certificate", "保持加密但不验证 SQL Server 身份;仅用于明确接受风险的连接")
|
|
59
|
+
.action(async (options, command) => {
|
|
60
|
+
const global = globals(command);
|
|
61
|
+
await initializeForWrite(store, global.yes === true);
|
|
62
|
+
const service = new ConnectionService(store);
|
|
63
|
+
let created;
|
|
64
|
+
if (process.stdin.isTTY && global.json !== true) {
|
|
65
|
+
created = await promptAndAddConnection(service, options);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
requireYes(global);
|
|
69
|
+
const requiredOptions = requireConnectionCreateOptions(options);
|
|
70
|
+
if (requiredOptions.authMode === "integrated" && process.platform !== "win32") {
|
|
71
|
+
throw new DataPullError("INVALID_ARGUMENT", "Windows 集成认证只支持 Windows。", 2);
|
|
72
|
+
}
|
|
73
|
+
const secretReference = requiredOptions.authMode === "url"
|
|
74
|
+
? requiredOptions.urlRef
|
|
75
|
+
: requiredOptions.authMode === "password"
|
|
76
|
+
? requiredOptions.credentialRef
|
|
77
|
+
: undefined;
|
|
78
|
+
if (secretReference !== undefined)
|
|
79
|
+
await store.resolveSecret(secretReference);
|
|
80
|
+
created = await service.add(createConnection(requiredOptions));
|
|
81
|
+
}
|
|
82
|
+
output(command, "connection add").success(redactedConnection(created), successMessage(`已登记连接:${created.alias}`, created));
|
|
83
|
+
});
|
|
84
|
+
connection
|
|
85
|
+
.command("list")
|
|
86
|
+
.description("列出脱敏连接")
|
|
87
|
+
.action(async (_options, command) => {
|
|
88
|
+
const service = new ConnectionService(store);
|
|
89
|
+
const registered = await service.list();
|
|
90
|
+
const connections = registered.map(redactedConnection);
|
|
91
|
+
output(command, "connection list").success({ connections }, connections.length === 0
|
|
92
|
+
? "尚未登记数据库连接。"
|
|
93
|
+
: registered
|
|
94
|
+
.map((item) => `${item.alias} · ${item.engine}${connectionSecurityWarnings(item).length > 0 ? " · ⚠ 未验证服务器身份" : ""}`)
|
|
95
|
+
.join("\n"));
|
|
96
|
+
});
|
|
97
|
+
connection
|
|
98
|
+
.command("show")
|
|
99
|
+
.description("查看单个脱敏连接")
|
|
100
|
+
.requiredOption("--alias <alias>", "连接别名")
|
|
101
|
+
.action(async (options, command) => {
|
|
102
|
+
const item = await new ConnectionService(store).get(requiredString(options.alias, "--alias"));
|
|
103
|
+
output(command, "connection show").success(redactedConnection(item), JSON.stringify(redactedConnection(item), null, 2));
|
|
104
|
+
});
|
|
105
|
+
connection
|
|
106
|
+
.command("update")
|
|
107
|
+
.description("更新连接或凭证引用")
|
|
108
|
+
.requiredOption("--alias <alias>", "连接别名")
|
|
109
|
+
.addOption(new Option("--engine <engine>").choices(["mysql", "postgresql", "sqlserver"]))
|
|
110
|
+
.addOption(new Option("--auth-mode <mode>").choices(["password", "url", "integrated"]))
|
|
111
|
+
.option("--host <host>")
|
|
112
|
+
.option("--port <port>", "端口", numberParser)
|
|
113
|
+
.option("--username <username>")
|
|
114
|
+
.option("--credential-ref <name>")
|
|
115
|
+
.option("--url-ref <name>")
|
|
116
|
+
.option("--ssl-mode <mode>")
|
|
117
|
+
.addOption(new Option("--trust-server-certificate", "保持加密但不验证 SQL Server 身份").conflicts("verifyServerCertificate"))
|
|
118
|
+
.addOption(new Option("--verify-server-certificate", "恢复严格验证 SQL Server 证书").conflicts("trustServerCertificate"))
|
|
119
|
+
.option("--refresh-credential", "在交互终端隐藏输入新凭证")
|
|
120
|
+
.action(async (options, command) => {
|
|
121
|
+
const global = globals(command);
|
|
122
|
+
await requireWriteConfirmation(global, options.trustServerCertificate === true
|
|
123
|
+
? "确认保存“信任服务器证书”?连接仍加密,但 SQL Server 身份不再验证。"
|
|
124
|
+
: "确认更新该登记连接?");
|
|
125
|
+
const service = new ConnectionService(store);
|
|
126
|
+
const alias = requiredString(options.alias, "--alias");
|
|
127
|
+
const existing = await service.get(alias);
|
|
128
|
+
if (options.refreshCredential === true) {
|
|
129
|
+
if (!process.stdin.isTTY || global.json === true) {
|
|
130
|
+
throw new DataPullError("INTERACTIVE_TTY_REQUIRED", "更新凭证值需要交互式隐藏输入;非交互模式只能切换凭证引用。", 2);
|
|
131
|
+
}
|
|
132
|
+
const reference = existing.authMode === "url" ? existing.urlRef : existing.credentialRef;
|
|
133
|
+
if (reference === undefined) {
|
|
134
|
+
throw new DataPullError("INVALID_ARGUMENT", "当前连接没有可刷新的凭证引用。", 2);
|
|
135
|
+
}
|
|
136
|
+
await store.setCredential(reference, await password({ message: `更新 ${reference}(输入不会回显):`, mask: "*" }));
|
|
137
|
+
}
|
|
138
|
+
const changes = compactConnectionChanges(options, existing);
|
|
139
|
+
if (Object.keys(changes).length === 0 && options.refreshCredential !== true) {
|
|
140
|
+
throw new DataPullError("INVALID_ARGUMENT", "至少提供一个待修改选项。", 2);
|
|
141
|
+
}
|
|
142
|
+
for (const reference of [changes.credentialRef, changes.urlRef]) {
|
|
143
|
+
if (reference !== undefined)
|
|
144
|
+
await store.resolveSecret(reference);
|
|
145
|
+
}
|
|
146
|
+
const updated = Object.keys(changes).length === 0 ? existing : await service.update(alias, changes);
|
|
147
|
+
output(command, "connection update").success(redactedConnection(updated), successMessage(`已更新连接:${alias}`, updated));
|
|
148
|
+
});
|
|
149
|
+
connection
|
|
150
|
+
.command("remove")
|
|
151
|
+
.description("删除登记连接,不删除项目结构文件")
|
|
152
|
+
.requiredOption("--alias <alias>", "连接别名")
|
|
153
|
+
.option("--delete-credential", "同时删除没有被其他连接引用的凭证变量")
|
|
154
|
+
.action(async (options, command) => {
|
|
155
|
+
const global = globals(command);
|
|
156
|
+
await requireWriteConfirmation(global, "确认删除登记连接?项目结构文件将保留。");
|
|
157
|
+
const service = new ConnectionService(store);
|
|
158
|
+
const removed = await service.remove(requiredString(options.alias, "--alias"));
|
|
159
|
+
let credentialRemoved = false;
|
|
160
|
+
if (options.deleteCredential === true) {
|
|
161
|
+
const reference = removed.authMode === "url" ? removed.urlRef : removed.credentialRef;
|
|
162
|
+
if (reference !== undefined)
|
|
163
|
+
credentialRemoved = await store.removeCredentialIfUnused(reference);
|
|
164
|
+
}
|
|
165
|
+
output(command, "connection remove").success({ connectionAlias: removed.alias, removed: true, credentialRemoved }, `已删除登记连接:${removed.alias};项目结构文件未删除。`);
|
|
166
|
+
});
|
|
167
|
+
connection
|
|
168
|
+
.command("test")
|
|
169
|
+
.description("只读校验连接或目标数据库")
|
|
170
|
+
.requiredOption("--alias <alias>", "连接别名")
|
|
171
|
+
.option("--database <database>", "目标数据库")
|
|
172
|
+
.option("--trust-server-certificate", "仅本次保持加密但不验证 SQL Server 身份")
|
|
173
|
+
.action(async (options, command) => {
|
|
174
|
+
const service = new ConnectionService(store);
|
|
175
|
+
const alias = requiredString(options.alias, "--alias");
|
|
176
|
+
const configured = await service.get(alias);
|
|
177
|
+
if (options.trustServerCertificate === true && configured.engine !== "sqlserver") {
|
|
178
|
+
throw new DataPullError("INVALID_ARGUMENT", "--trust-server-certificate 只适用于 SQL Server。", 2);
|
|
179
|
+
}
|
|
180
|
+
if (options.trustServerCertificate === true) {
|
|
181
|
+
await requireRiskConfirmation(globals(command));
|
|
182
|
+
}
|
|
183
|
+
if (options.database !== undefined)
|
|
184
|
+
validatePathSegment(options.database, "数据库名");
|
|
185
|
+
const connection = await service.resolve(alias, options.database, {
|
|
186
|
+
...(options.trustServerCertificate === true
|
|
187
|
+
? { trustServerCertificate: true }
|
|
188
|
+
: {}),
|
|
189
|
+
});
|
|
190
|
+
await new ToolManager().ensure(connection.engine, false, false);
|
|
191
|
+
const testResult = await exporterFor(connection.engine).test(connection, options.database);
|
|
192
|
+
output(command, "connection test").success({
|
|
193
|
+
connectionAlias: alias,
|
|
194
|
+
database: options.database ?? null,
|
|
195
|
+
reachable: true,
|
|
196
|
+
serverVersion: testResult.serverVersion,
|
|
197
|
+
...(connectionSecurityWarnings(connection).length === 0
|
|
198
|
+
? {}
|
|
199
|
+
: { securityWarnings: connectionSecurityWarnings(connection) }),
|
|
200
|
+
}, successMessage(`连接校验通过:${alias}${options.database === undefined ? "" : ` / ${options.database}`}`, connection));
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
function registerDatabaseCommands(program, store) {
|
|
204
|
+
const database = program.command("database").description("查看和收藏目标数据库");
|
|
205
|
+
database
|
|
206
|
+
.command("list")
|
|
207
|
+
.requiredOption("--connection <alias>", "连接别名")
|
|
208
|
+
.option("--trust-server-certificate", "仅本次保持加密但不验证 SQL Server 身份")
|
|
209
|
+
.action(async (options, command) => {
|
|
210
|
+
const service = new ConnectionService(store);
|
|
211
|
+
const connection = await service.get(options.connection);
|
|
212
|
+
if (options.trustServerCertificate === true && connection.engine !== "sqlserver") {
|
|
213
|
+
throw new DataPullError("INVALID_ARGUMENT", "--trust-server-certificate 只适用于 SQL Server。", 2);
|
|
214
|
+
}
|
|
215
|
+
if (options.trustServerCertificate === true) {
|
|
216
|
+
await requireRiskConfirmation(globals(command));
|
|
217
|
+
}
|
|
218
|
+
const effectiveConnection = options.trustServerCertificate === true
|
|
219
|
+
? {
|
|
220
|
+
...connection,
|
|
221
|
+
tls: { encrypt: true, trustServerCertificate: true },
|
|
222
|
+
}
|
|
223
|
+
: connection;
|
|
224
|
+
let enumerated = [];
|
|
225
|
+
let enumerationError;
|
|
226
|
+
try {
|
|
227
|
+
enumerated = await exporterFor(connection.engine).listDatabases(await service.resolve(connection.alias, undefined, {
|
|
228
|
+
...(options.trustServerCertificate === true
|
|
229
|
+
? { trustServerCertificate: true }
|
|
230
|
+
: {}),
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
const normalized = error instanceof DataPullError ? error : new DataPullError("DATABASE_CLIENT_FAILED", String(error), 1);
|
|
235
|
+
enumerationError = { code: normalized.code, message: normalized.message };
|
|
236
|
+
}
|
|
237
|
+
const values = [...new Set([...connection.favoriteDatabases, ...connection.recentDatabases, ...enumerated])];
|
|
238
|
+
output(command, "database list").success({
|
|
239
|
+
connectionAlias: connection.alias,
|
|
240
|
+
databases: values.map((name) => ({
|
|
241
|
+
name,
|
|
242
|
+
favorite: connection.favoriteDatabases.includes(name),
|
|
243
|
+
recent: connection.recentDatabases.includes(name),
|
|
244
|
+
enumerated: enumerated.includes(name),
|
|
245
|
+
})),
|
|
246
|
+
manualInputAllowed: true,
|
|
247
|
+
...(connectionSecurityWarnings(effectiveConnection).length > 0
|
|
248
|
+
? { securityWarnings: connectionSecurityWarnings(effectiveConnection) }
|
|
249
|
+
: {}),
|
|
250
|
+
...(enumerationError === undefined ? {} : { enumerationError }),
|
|
251
|
+
}, successMessage(values.length > 0
|
|
252
|
+
? values.join("\n")
|
|
253
|
+
: "未枚举到数据库,可在拉取时手工指定名称。", effectiveConnection));
|
|
254
|
+
});
|
|
255
|
+
const favorite = database.command("favorite").description("维护数据库收藏");
|
|
256
|
+
for (const action of ["add", "remove"]) {
|
|
257
|
+
favorite
|
|
258
|
+
.command(action)
|
|
259
|
+
.requiredOption("--connection <alias>", "连接别名")
|
|
260
|
+
.requiredOption("--database <database>", "目标数据库")
|
|
261
|
+
.action(async (options, command) => {
|
|
262
|
+
await requireWriteConfirmation(globals(command), `确认${action === "add" ? "收藏" : "取消收藏"}该数据库?`);
|
|
263
|
+
await new ConnectionService(store).setFavorite(options.connection, options.database, action === "add");
|
|
264
|
+
output(command, `database favorite ${action}`).success({ connectionAlias: options.connection, database: options.database, favorite: action === "add" }, action === "add" ? "已收藏数据库。" : "已取消收藏。");
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function registerPullCommand(program, store) {
|
|
269
|
+
program
|
|
270
|
+
.command("pull")
|
|
271
|
+
.description("拉取目标数据库结构文件")
|
|
272
|
+
.requiredOption("--connection <alias>", "连接别名")
|
|
273
|
+
.requiredOption("--database <database>", "目标数据库")
|
|
274
|
+
.option("--include <types>", "逗号分隔的对象类型")
|
|
275
|
+
.option("--install-missing", "安装缺少的官方数据库工具")
|
|
276
|
+
.option("--trust-server-certificate", "仅本次保持加密但不验证 SQL Server 身份")
|
|
277
|
+
.action(async (options, command) => {
|
|
278
|
+
const global = globals(command);
|
|
279
|
+
if (global.yes !== true && options.installMissing === true) {
|
|
280
|
+
const connection = await new ConnectionService(store).get(options.connection);
|
|
281
|
+
const tools = new ToolManager();
|
|
282
|
+
const missing = await tools.missing(connection.engine);
|
|
283
|
+
if (missing.length > 0) {
|
|
284
|
+
throw new DataPullError("CONFIRMATION_REQUIRED", "自动安装数据库工具需要显式确认。", 2, { actionPlan: { installation: tools.plans(missing) }, manualRequired: missing.filter((tool) => !tools.plans(missing).some((plan) => plan.tool === tool)) });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
requireYes(global);
|
|
288
|
+
const result = await new PullService(new ConnectionService(store)).execute({
|
|
289
|
+
connectionAlias: options.connection,
|
|
290
|
+
database: options.database,
|
|
291
|
+
include: options.include?.split(",").map((value) => value.trim()).filter(Boolean),
|
|
292
|
+
installMissing: options.installMissing === true,
|
|
293
|
+
confirmed: global.yes === true,
|
|
294
|
+
trustServerCertificate: options.trustServerCertificate === true,
|
|
295
|
+
onStage: (stage) => {
|
|
296
|
+
if (global.json === true)
|
|
297
|
+
process.stderr.write(`${stage}\n`);
|
|
298
|
+
else
|
|
299
|
+
process.stdout.write(`• ${stage}\n`);
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
output(command, "pull").success(result, [
|
|
303
|
+
`结构文件已写入:${result.outputPath}`,
|
|
304
|
+
...(result.warnings ?? []).map((warning) => `警告:${warning}`),
|
|
305
|
+
].join("\n"));
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
function registerSkillCommands(program) {
|
|
309
|
+
const skill = program.command("skill").description("安装和同步 DataPull Skill");
|
|
310
|
+
skill.command("list").action(async (_options, command) => {
|
|
311
|
+
const projectRoot = await discoverProjectRoot();
|
|
312
|
+
const targets = allSkillTargets(projectRoot);
|
|
313
|
+
output(command, "skill list").success({ targets }, targets.map((target) => `${target.agent}:${target.scope} → ${target.path}`).join("\n"));
|
|
314
|
+
});
|
|
315
|
+
skill
|
|
316
|
+
.command("status")
|
|
317
|
+
.option("--target <target>", "目标 agent:scope,可重复", collect, [])
|
|
318
|
+
.action(async (options, command) => {
|
|
319
|
+
const projectRoot = await discoverProjectRoot();
|
|
320
|
+
const targets = options.target.length === 0
|
|
321
|
+
? allSkillTargets(projectRoot)
|
|
322
|
+
: options.target.map((target) => parseTarget(target, projectRoot));
|
|
323
|
+
const installer = new SkillInstaller();
|
|
324
|
+
const statuses = await Promise.all(targets.map(async (target) => installer.status(target)));
|
|
325
|
+
output(command, "skill status").success({ targets: statuses }, statuses.map((item) => `${item.agent}:${item.scope} · ${item.status} · ${item.path}`).join("\n"));
|
|
326
|
+
});
|
|
327
|
+
skill
|
|
328
|
+
.command("install")
|
|
329
|
+
.option("--target <target>", "目标 agent:scope,可重复", collect, [])
|
|
330
|
+
.action(async (options, command) => {
|
|
331
|
+
if (options.target.length === 0)
|
|
332
|
+
throw new DataPullError("INVALID_ARGUMENT", "至少提供一个 --target。", 2);
|
|
333
|
+
await requireWriteConfirmation(globals(command), "确认创建目录并安装所选 DataPull Skill?");
|
|
334
|
+
const projectRoot = await discoverProjectRoot();
|
|
335
|
+
const installer = new SkillInstaller();
|
|
336
|
+
const targets = [];
|
|
337
|
+
for (const value of options.target) {
|
|
338
|
+
const target = parseTarget(value, projectRoot);
|
|
339
|
+
try {
|
|
340
|
+
targets.push(await installer.install(target, true));
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
targets.push({
|
|
344
|
+
agent: target.agent,
|
|
345
|
+
scope: target.scope,
|
|
346
|
+
path: target.path,
|
|
347
|
+
status: "failed",
|
|
348
|
+
modified: false,
|
|
349
|
+
action: "none",
|
|
350
|
+
error: error instanceof Error ? error.message : String(error),
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (targets.some((target) => target.status === "failed")) {
|
|
355
|
+
throw new DataPullError("SKILL_PARTIAL_FAILURE", "部分 Skill 目标安装失败。", 1, { targets });
|
|
356
|
+
}
|
|
357
|
+
output(command, "skill install").success({ targets }, "DataPull Skill 安装完成。\n" + targets.map((item) => item.path).join("\n"));
|
|
358
|
+
});
|
|
359
|
+
skill
|
|
360
|
+
.command("sync")
|
|
361
|
+
.option("--target <target>", "目标 agent:scope,可重复", collect, [])
|
|
362
|
+
.option("--force", "备份后覆盖用户修改")
|
|
363
|
+
.action(async (options, command) => {
|
|
364
|
+
if (options.target.length === 0)
|
|
365
|
+
throw new DataPullError("INVALID_ARGUMENT", "skill sync 必须明确提供 --target。", 2);
|
|
366
|
+
const global = globals(command);
|
|
367
|
+
if (options.force === true && global.yes !== true) {
|
|
368
|
+
throw new DataPullError("CONFIRMATION_REQUIRED", "覆盖用户修改必须同时提供明确目标、--force 和 --yes。", 2);
|
|
369
|
+
}
|
|
370
|
+
await requireWriteConfirmation(global, "确认同步所选 DataPull Skill?");
|
|
371
|
+
const projectRoot = await discoverProjectRoot();
|
|
372
|
+
const installer = new SkillInstaller();
|
|
373
|
+
const targets = [];
|
|
374
|
+
for (const value of options.target) {
|
|
375
|
+
const target = parseTarget(value, projectRoot);
|
|
376
|
+
try {
|
|
377
|
+
targets.push(await installer.sync(target, true, options.force === true));
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
targets.push({
|
|
381
|
+
agent: target.agent,
|
|
382
|
+
scope: target.scope,
|
|
383
|
+
path: target.path,
|
|
384
|
+
status: "failed",
|
|
385
|
+
modified: false,
|
|
386
|
+
action: "none",
|
|
387
|
+
error: error instanceof Error ? error.message : String(error),
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (targets.some((target) => target.status === "failed")) {
|
|
392
|
+
throw new DataPullError("SKILL_PARTIAL_FAILURE", "部分 Skill 目标同步失败。", 1, { targets });
|
|
393
|
+
}
|
|
394
|
+
output(command, "skill sync").success({ targets }, targets.map((item) => `${item.path} · ${item.status}`).join("\n"));
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
function registerConfigCommands(program, store) {
|
|
398
|
+
const config = program.command("config").description("查看和校验用户级配置");
|
|
399
|
+
config.command("path").action((_options, command) => {
|
|
400
|
+
output(command, "config path").success({ configRoot: store.paths.root, configPath: store.paths.config, credentialsPath: store.paths.credentials }, `配置:${store.paths.config}\n凭证:${store.paths.credentials}`);
|
|
401
|
+
});
|
|
402
|
+
config.command("validate").action(async (_options, command) => {
|
|
403
|
+
const parsed = await store.read();
|
|
404
|
+
await store.readCredentials();
|
|
405
|
+
output(command, "config validate").success({ valid: true, version: parsed.version, connectionCount: parsed.connections.length }, `配置有效:${parsed.connections.length} 个登记连接。`);
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
function registerDoctorCommand(program, store) {
|
|
409
|
+
program.command("doctor").description("诊断运行环境,不自动安装").action(async (_options, command) => {
|
|
410
|
+
const checks = await runDoctor(store);
|
|
411
|
+
output(command, "doctor").success({ healthy: checks.every((check) => check.ok), checks }, checks.map((check) => `${check.ok ? "✓" : "✗"} ${check.name}:${check.detail}`).join("\n"));
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
function globals(command) {
|
|
415
|
+
return command.optsWithGlobals();
|
|
416
|
+
}
|
|
417
|
+
function output(command, name) {
|
|
418
|
+
return new Output({ command: name, json: globals(command).json === true });
|
|
419
|
+
}
|
|
420
|
+
async function initializeForWrite(store, confirmed) {
|
|
421
|
+
if (await store.exists()) {
|
|
422
|
+
await store.initialize();
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (!confirmed && !process.stdin.isTTY) {
|
|
426
|
+
throw new DataPullError("CONFIRMATION_REQUIRED", "初始化用户级配置需要 --yes。", 2, {
|
|
427
|
+
configRoot: store.paths.root,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
if (!confirmed) {
|
|
431
|
+
process.stdout.write(`将创建用户级配置:${store.paths.root}\n`);
|
|
432
|
+
if (!(await confirm({ message: "确认初始化?", default: true }))) {
|
|
433
|
+
throw new DataPullError("OPERATION_CANCELLED", "已取消初始化。", 1);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
await store.initialize();
|
|
437
|
+
}
|
|
438
|
+
async function requireWriteConfirmation(global, message) {
|
|
439
|
+
if (global.yes === true)
|
|
440
|
+
return;
|
|
441
|
+
if (!process.stdin.isTTY || global.json === true) {
|
|
442
|
+
throw new DataPullError("CONFIRMATION_REQUIRED", `${message} 非交互模式请添加 --yes。`, 2);
|
|
443
|
+
}
|
|
444
|
+
if (!(await confirm({ message, default: false }))) {
|
|
445
|
+
throw new DataPullError("OPERATION_CANCELLED", "操作已取消。", 1);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
function requireYes(global) {
|
|
449
|
+
if (global.yes !== true) {
|
|
450
|
+
throw new DataPullError("CONFIRMATION_REQUIRED", "命令模式拉取会写入项目目录,请添加 --yes。", 2);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function requireConnectionCreateOptions(options) {
|
|
454
|
+
const alias = requiredString(options.alias, "--alias");
|
|
455
|
+
const engine = requiredString(options.engine, "--engine");
|
|
456
|
+
const authMode = requiredString(options.authMode, "--auth-mode");
|
|
457
|
+
if (authMode === "password") {
|
|
458
|
+
requiredString(options.host, "--host");
|
|
459
|
+
requiredString(options.username, "--username");
|
|
460
|
+
requiredString(options.credentialRef, "--credential-ref");
|
|
461
|
+
}
|
|
462
|
+
else if (authMode === "url")
|
|
463
|
+
requiredString(options.urlRef, "--url-ref");
|
|
464
|
+
else
|
|
465
|
+
requiredString(options.host, "--host");
|
|
466
|
+
if (options.trustServerCertificate === true && engine !== "sqlserver") {
|
|
467
|
+
throw new DataPullError("INVALID_ARGUMENT", "--trust-server-certificate 只适用于 SQL Server。", 2);
|
|
468
|
+
}
|
|
469
|
+
return {
|
|
470
|
+
alias,
|
|
471
|
+
engine,
|
|
472
|
+
authMode,
|
|
473
|
+
...(options.host === undefined ? {} : { host: options.host }),
|
|
474
|
+
...(options.port === undefined ? {} : { port: options.port }),
|
|
475
|
+
...(options.username === undefined ? {} : { username: options.username }),
|
|
476
|
+
...(options.credentialRef === undefined ? {} : { credentialRef: options.credentialRef }),
|
|
477
|
+
...(options.urlRef === undefined ? {} : { urlRef: options.urlRef }),
|
|
478
|
+
...(options.sslMode === undefined ? {} : { sslMode: options.sslMode }),
|
|
479
|
+
...(options.trustServerCertificate === undefined
|
|
480
|
+
? {}
|
|
481
|
+
: { trustServerCertificate: options.trustServerCertificate }),
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
function compactConnectionChanges(options, existing) {
|
|
485
|
+
const fields = ["engine", "authMode", "host", "port", "username", "credentialRef", "urlRef", "sslMode"];
|
|
486
|
+
const changes = Object.fromEntries(fields.flatMap((field) => (options[field] === undefined ? [] : [[field, options[field]]])));
|
|
487
|
+
const targetEngine = options.engine ?? existing.engine;
|
|
488
|
+
if ((options.trustServerCertificate === true || options.verifyServerCertificate === true) &&
|
|
489
|
+
targetEngine !== "sqlserver") {
|
|
490
|
+
throw new DataPullError("INVALID_ARGUMENT", "服务器证书信任选项只适用于 SQL Server。", 2);
|
|
491
|
+
}
|
|
492
|
+
if (options.trustServerCertificate === true) {
|
|
493
|
+
changes.tls = { encrypt: true, trustServerCertificate: true };
|
|
494
|
+
}
|
|
495
|
+
else if (options.verifyServerCertificate === true) {
|
|
496
|
+
changes.tls = { encrypt: true, trustServerCertificate: false };
|
|
497
|
+
}
|
|
498
|
+
return changes;
|
|
499
|
+
}
|
|
500
|
+
async function requireRiskConfirmation(global) {
|
|
501
|
+
if (global.yes === true)
|
|
502
|
+
return;
|
|
503
|
+
const message = "信任服务器证书会保留加密,但不验证 SQL Server 身份,可能受到中间人攻击。";
|
|
504
|
+
if (!process.stdin.isTTY || global.json === true) {
|
|
505
|
+
throw new DataPullError("CONFIRMATION_REQUIRED", `${message} 如确认仅本次使用,请添加 --yes。`, 2);
|
|
506
|
+
}
|
|
507
|
+
if (!(await confirm({ message: `${message} 是否继续?`, default: false }))) {
|
|
508
|
+
throw new DataPullError("OPERATION_CANCELLED", "操作已取消。", 1);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function requiredString(value, option) {
|
|
512
|
+
if (value !== undefined && value.trim().length > 0)
|
|
513
|
+
return value.trim();
|
|
514
|
+
throw new DataPullError("INVALID_ARGUMENT", `缺少必需选项 ${option}。`, 2);
|
|
515
|
+
}
|
|
516
|
+
function successMessage(message, connection) {
|
|
517
|
+
return [
|
|
518
|
+
message,
|
|
519
|
+
...connectionSecurityWarnings(connection).map((warning) => `警告:${warning}`),
|
|
520
|
+
].join("\n");
|
|
521
|
+
}
|
|
522
|
+
function numberParser(value) {
|
|
523
|
+
const parsed = Number.parseInt(value, 10);
|
|
524
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65_535) {
|
|
525
|
+
throw new DataPullError("INVALID_ARGUMENT", `端口无效:${value}`, 2);
|
|
526
|
+
}
|
|
527
|
+
return parsed;
|
|
528
|
+
}
|
|
529
|
+
function collect(value, previous) {
|
|
530
|
+
return [...previous, value];
|
|
531
|
+
}
|
|
532
|
+
function parseTarget(value, projectRoot) {
|
|
533
|
+
try {
|
|
534
|
+
return parseSkillTarget(value, projectRoot);
|
|
535
|
+
}
|
|
536
|
+
catch (error) {
|
|
537
|
+
throw new DataPullError("INVALID_ARGUMENT", error instanceof Error ? error.message : String(error), 2);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
//# sourceMappingURL=program.js.map
|