mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
fix(config): preserve unmanaged fields when saving profiles
This commit is contained in:
@@ -44,6 +44,7 @@
|
||||
- `config list` 标识所有 Profile 与当前激活项。
|
||||
- `config show`、`auth status` 只输出本次最终选择的 `config` 和 `config_file`,不重复携带激活状态。
|
||||
- `config ui` 从持久化元数据读取激活项,提供显式激活操作,并在删除激活项后刷新为 `default`。
|
||||
- `config ui` 保存时只替换 UI 管理的字段;Profile 中未展示但仍属于 `ConfigFile` 的合法字段必须保留,不能因打开并保存 UI 而丢失。
|
||||
- 同步 E2E topic routes、Skill setup 和自动生成 reference。
|
||||
|
||||
## 6. 最小测试矩阵
|
||||
@@ -57,6 +58,7 @@
|
||||
- 登录、退出、`config set` 分别覆盖“当前激活项”和“显式不存在名称成功后创建”。
|
||||
- Console token 自动刷新不从其他 Profile 借用 AK/SK,也不把新 token 写入其他 Profile。
|
||||
- `config list/show/use/ui`、`auth status` 和依赖默认模型的消费命令覆盖对应 E2E。
|
||||
- `config ui` 覆盖保存时保留未管理字段,并继续允许空值清除 UI 管理字段。
|
||||
|
||||
## 7. 完成检查
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
BailianError,
|
||||
ExitCode,
|
||||
normalizeConfigName,
|
||||
readConfigFile,
|
||||
writeConfigFile,
|
||||
deleteConfigProfile,
|
||||
type ConfigStore,
|
||||
@@ -72,6 +73,19 @@ function buildProfilePatch(data: Record<string, unknown>): Record<string, string
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
/** Preserve valid Config fields that the UI does not expose or manage. */
|
||||
function mergeUnmanagedProfileFields(
|
||||
existing: Record<string, unknown>,
|
||||
managedPatch: Record<string, string | number>,
|
||||
): Record<string, unknown> {
|
||||
const managedKeys = new Set<string>(VALID_KEYS);
|
||||
const merged: Record<string, unknown> = {};
|
||||
for (const [key, value] of Object.entries(existing)) {
|
||||
if (!managedKeys.has(key)) merged[key] = value;
|
||||
}
|
||||
return { ...merged, ...managedPatch };
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the config-UI http server. Exported for tests. The handler enforces:
|
||||
* - Host header must be a loopback name (anti DNS-rebinding).
|
||||
@@ -158,8 +172,10 @@ export function createConfigUiServer(token: string, configStore: ConfigStore): h
|
||||
sendJson(res, 400, { error: errMessage(err) });
|
||||
return;
|
||||
}
|
||||
await writeConfigFile(cleaned, normalized);
|
||||
sendJson(res, 200, { saved: cleaned });
|
||||
const existing = readConfigFile(normalized) as Record<string, unknown>;
|
||||
const saved = mergeUnmanagedProfileFields(existing, cleaned);
|
||||
await writeConfigFile(saved, normalized);
|
||||
sendJson(res, 200, { saved });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,6 +128,47 @@ test("POST /api/profile 写命名 profile(timeout 强制为 number),空串
|
||||
});
|
||||
});
|
||||
|
||||
test("POST /api/profile 保留 UI 未管理字段,同时替换 UI 管理字段", async () => {
|
||||
await withServer(async (port) => {
|
||||
await writeConfigFile(
|
||||
{
|
||||
api_key: "sk-old",
|
||||
output: "json",
|
||||
console_site: "international",
|
||||
console_region: "ap-southeast-1",
|
||||
console_switch_agent: 42,
|
||||
telemetry: false,
|
||||
},
|
||||
"stage",
|
||||
);
|
||||
|
||||
const save = await httpJson(port, "POST", `/api/profile?token=${TOKEN}`, {
|
||||
body: { name: "stage", data: { api_key: "sk-new" } },
|
||||
});
|
||||
expect(save.status).toBe(200);
|
||||
|
||||
const profile = readConfigFile("stage");
|
||||
expect(profile).toMatchObject({
|
||||
api_key: "sk-new",
|
||||
console_site: "international",
|
||||
console_region: "ap-southeast-1",
|
||||
console_switch_agent: 42,
|
||||
telemetry: false,
|
||||
});
|
||||
expect(profile.output).toBeUndefined();
|
||||
|
||||
const rawConfig = JSON.parse(readFileSync(getConfigPath(), "utf8"));
|
||||
expect(rawConfig.stage).toMatchObject({
|
||||
api_key: "sk-new",
|
||||
console_site: "international",
|
||||
console_region: "ap-southeast-1",
|
||||
console_switch_agent: 42,
|
||||
telemetry: false,
|
||||
});
|
||||
expect(rawConfig.stage.output).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
test("New profile 立即保存空 Profile,其他配置读取可以看到", async () => {
|
||||
await withServer(async (port) => {
|
||||
const create = await httpJson(port, "POST", `/api/profile?token=${TOKEN}`, {
|
||||
|
||||
Reference in New Issue
Block a user