mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-19 03:14:46 +00:00
fix(config): preserve numeric patch object keys (#81999)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1973,6 +1973,43 @@ describe("config cli", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps numeric config patch object keys as object keys", async () => {
|
||||
const resolved = {
|
||||
channels: {
|
||||
discord: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
setSnapshot(resolved, resolved);
|
||||
|
||||
const pathname = writeTempJson5File("openclaw-config-patch-numeric-object-key", {
|
||||
channels: {
|
||||
discord: {
|
||||
guilds: {
|
||||
"123456789012345678": {
|
||||
token: { source: "env", provider: "default", id: "DISCORD_BOT_TOKEN" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
try {
|
||||
await runConfigCommand(["config", "patch", "--file", pathname]);
|
||||
} finally {
|
||||
fs.rmSync(pathname, { force: true });
|
||||
}
|
||||
|
||||
const written = firstWrittenConfig() as {
|
||||
channels?: { discord?: { guilds?: unknown } };
|
||||
};
|
||||
expect(written.channels?.discord?.guilds).toEqual({
|
||||
"123456789012345678": {
|
||||
token: { source: "env", provider: "default", id: "DISCORD_BOT_TOKEN" },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("dry-runs config patch and resolves changed SecretRefs", async () => {
|
||||
const resolved = {
|
||||
secrets: {
|
||||
|
||||
@@ -453,12 +453,19 @@ function getAtPath(root: unknown, path: PathSegment[]): { found: boolean; value?
|
||||
return { found: true, value: current };
|
||||
}
|
||||
|
||||
function setAtPath(root: Record<string, unknown>, path: PathSegment[], value: unknown): void {
|
||||
type SetAtPathOptions = { numericObjectKeys?: boolean };
|
||||
|
||||
function setAtPath(
|
||||
root: Record<string, unknown>,
|
||||
path: PathSegment[],
|
||||
value: unknown,
|
||||
options?: SetAtPathOptions,
|
||||
): void {
|
||||
let current: unknown = root;
|
||||
for (let i = 0; i < path.length - 1; i += 1) {
|
||||
const segment = path[i];
|
||||
const next = path[i + 1];
|
||||
const nextIsIndex = Boolean(next && isIndexSegment(next));
|
||||
const nextIsIndex = !options?.numericObjectKeys && Boolean(next && isIndexSegment(next));
|
||||
if (Array.isArray(current)) {
|
||||
if (!isIndexSegment(segment)) {
|
||||
throw new Error(`Expected numeric index for array segment "${segment}"`);
|
||||
@@ -554,13 +561,18 @@ function mergeConfigValue(existing: unknown, patch: unknown, path: PathSegment[]
|
||||
throw new Error(`Cannot merge ${toDotPath(path)}; use --replace to replace intentionally.`);
|
||||
}
|
||||
|
||||
function mergeAtPath(root: Record<string, unknown>, path: PathSegment[], value: unknown): void {
|
||||
function mergeAtPath(
|
||||
root: Record<string, unknown>,
|
||||
path: PathSegment[],
|
||||
value: unknown,
|
||||
options?: SetAtPathOptions,
|
||||
): void {
|
||||
const existing = getAtPath(root, path);
|
||||
if (!existing.found) {
|
||||
setAtPath(root, path, value);
|
||||
setAtPath(root, path, value, options);
|
||||
return;
|
||||
}
|
||||
setAtPath(root, path, mergeConfigValue(existing.value, value, path));
|
||||
setAtPath(root, path, mergeConfigValue(existing.value, value, path), options);
|
||||
}
|
||||
|
||||
function isProviderModelListPath(path: PathSegment[]): boolean {
|
||||
@@ -1675,7 +1687,9 @@ async function runConfigOperations(params: {
|
||||
}
|
||||
explicitSetPaths.push(operation.setPath);
|
||||
if (operation.mutation === "merge" || (options.merge && operation.mutation !== "replace")) {
|
||||
mergeAtPath(next, operation.setPath, operation.value);
|
||||
mergeAtPath(next, operation.setPath, operation.value, {
|
||||
numericObjectKeys: params.successMode === "patch",
|
||||
});
|
||||
} else {
|
||||
assertNonDestructiveReplacement({
|
||||
root: next,
|
||||
@@ -1683,7 +1697,9 @@ async function runConfigOperations(params: {
|
||||
value: operation.value,
|
||||
allowReplace: options.replace || operation.mutation === "replace",
|
||||
});
|
||||
setAtPath(next, operation.setPath, operation.value);
|
||||
setAtPath(next, operation.setPath, operation.value, {
|
||||
numericObjectKeys: params.successMode === "patch",
|
||||
});
|
||||
}
|
||||
}
|
||||
const removedGatewayAuthPaths = pruneInactiveGatewayAuthCredentials({
|
||||
|
||||
Reference in New Issue
Block a user