mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:10:44 +00:00
fix(config): preserve authored tilde paths on write
This commit is contained in:
@@ -1089,6 +1089,71 @@ type ConfigReadResolution = {
|
||||
envWarnings: EnvSubstitutionWarning[];
|
||||
};
|
||||
|
||||
const TILDE_PATH_VALUE_RE = /^~(?=$|[\\/])/;
|
||||
const PATH_LIKE_CONFIG_KEY_RE = /(dir|path|paths|file|root|workspace)$/i;
|
||||
const PATH_LIKE_CONFIG_LIST_KEYS = new Set(["paths", "pathPrepend"]);
|
||||
|
||||
function isPathLikeConfigKey(key: string | undefined): boolean {
|
||||
return Boolean(key && (PATH_LIKE_CONFIG_KEY_RE.test(key) || PATH_LIKE_CONFIG_LIST_KEYS.has(key)));
|
||||
}
|
||||
|
||||
function expandAuthoredTildePath(value: string, home: string): string {
|
||||
const suffix = value.slice(1);
|
||||
if (!suffix) {
|
||||
return home;
|
||||
}
|
||||
if (suffix.startsWith("/") || suffix.startsWith("\\")) {
|
||||
return path.join(home, suffix.slice(1));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function restoreAuthoredTildePathsForWrite(
|
||||
next: unknown,
|
||||
authored: unknown,
|
||||
key: string | undefined,
|
||||
home: string,
|
||||
): unknown {
|
||||
if (
|
||||
typeof next === "string" &&
|
||||
typeof authored === "string" &&
|
||||
isPathLikeConfigKey(key) &&
|
||||
TILDE_PATH_VALUE_RE.test(authored.trim()) &&
|
||||
path.normalize(next) === path.normalize(expandAuthoredTildePath(authored.trim(), home))
|
||||
) {
|
||||
return authored;
|
||||
}
|
||||
|
||||
if (Array.isArray(next) && Array.isArray(authored)) {
|
||||
const normalizeChildren = isPathLikeConfigKey(key);
|
||||
return next.map((entry, index) =>
|
||||
restoreAuthoredTildePathsForWrite(
|
||||
entry,
|
||||
authored[index],
|
||||
normalizeChildren ? key : undefined,
|
||||
home,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (!isRecord(next) || !isRecord(authored)) {
|
||||
return next;
|
||||
}
|
||||
|
||||
const out: Record<string, unknown> = { ...next };
|
||||
for (const [childKey, childValue] of Object.entries(out)) {
|
||||
if (Object.prototype.hasOwnProperty.call(authored, childKey)) {
|
||||
out[childKey] = restoreAuthoredTildePathsForWrite(
|
||||
childValue,
|
||||
authored[childKey],
|
||||
childKey,
|
||||
home,
|
||||
);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function resolveConfigIncludesForRead(
|
||||
parsed: unknown,
|
||||
configPath: string,
|
||||
@@ -2069,7 +2134,13 @@ export function createConfigIO(
|
||||
envRefMap && changedPaths
|
||||
? (restoreEnvRefsFromMap(cfgToWrite, "", envRefMap, changedPaths) as OpenClawConfig)
|
||||
: cfgToWrite;
|
||||
const outputConfig = applyUnsetPathsForWrite(outputConfigBase, unsetPaths);
|
||||
const tildeRestoredOutputConfig = restoreAuthoredTildePathsForWrite(
|
||||
outputConfigBase,
|
||||
snapshot.parsed,
|
||||
undefined,
|
||||
deps.homedir(),
|
||||
) as OpenClawConfig;
|
||||
const outputConfig = applyUnsetPathsForWrite(tildeRestoredOutputConfig, unsetPaths);
|
||||
// Do NOT apply runtime defaults when writing - user config should only contain
|
||||
// explicitly set values. Runtime defaults are applied when loading (issue #6070).
|
||||
const stampedOutputConfig = stampConfigVersion(outputConfig);
|
||||
|
||||
@@ -1182,4 +1182,38 @@ describe("config io write", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves authored tilde paths when runtime-shaped writes hand back absolute paths", async () => {
|
||||
await withSuiteHome(async (home) => {
|
||||
const configPath = path.join(home, ".openclaw", "openclaw.json");
|
||||
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
||||
await fs.writeFile(
|
||||
configPath,
|
||||
`${JSON.stringify(
|
||||
{
|
||||
logging: { file: "~/openclaw-upgrade-survivor/gateway.jsonl" },
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
"utf-8",
|
||||
);
|
||||
const io = createFastConfigIO(home);
|
||||
const snapshot = await io.readConfigFileSnapshot();
|
||||
|
||||
await io.writeConfigFile(
|
||||
{
|
||||
logging: {
|
||||
file: path.join(home, "openclaw-upgrade-survivor", "gateway.jsonl"),
|
||||
level: "debug",
|
||||
},
|
||||
},
|
||||
{ baseSnapshot: snapshot },
|
||||
);
|
||||
|
||||
const persisted = JSON.parse(await fs.readFile(configPath, "utf-8")) as OpenClawConfig;
|
||||
expect(persisted.logging?.file).toBe("~/openclaw-upgrade-survivor/gateway.jsonl");
|
||||
expect(persisted.logging?.level).toBe("debug");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user