mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 01:29:56 +00:00
fix(cli): tool summaries show wrong paths when HOME=/ or the home is a path prefix (#162483)
Fixes #162438. `shortenHomeInString` now replaces the home directory only at a path boundary. With HOME=/home/al, `/home/alice/...` is left alone; with HOME=/, slashes are no longer turned into `~`. `/home/al/x` still becomes `~/x` and the exact home path still becomes `~`. Proof: real CLI in secretless Docker (`openclaw reset --scope config --dry-run`). main printed `~ice/p162438.json` and `~p162438~root.json`; this PR prints the correct paths. Regression test fails on main and passes here. Co-authored-by: Ayaan Zaidi <hi@obviy.us>
This commit is contained in:
parent
a9074f2454
commit
e93bc05733
2 changed files with 45 additions and 6 deletions
|
|
@ -227,6 +227,29 @@ describe("shortenHomeInString", () => {
|
|||
},
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === "win32")("replaces only whole home path components", () => {
|
||||
withEnv({ OPENCLAW_HOME: undefined, HOME: "/home/al" }, () => {
|
||||
expect(shortenHomeInString("/home/al")).toBe("~");
|
||||
expect(shortenHomeInString("/home/al/x")).toBe("~/x");
|
||||
expect(
|
||||
shortenHomeInString("open /home/al/notes, /home/al, /home/alice/x and /mnt/home/al/x"),
|
||||
).toBe("open ~/notes, ~, /home/alice/x and /mnt/home/al/x");
|
||||
expect(shortenHomeInString("Stored in /home/al. Backup: /home/al.bak/x")).toBe(
|
||||
"Stored in ~. Backup: /home/al.bak/x",
|
||||
);
|
||||
const siblingPaths =
|
||||
"/home/al+old, /home/al./notes, /home/al,old/x, /home/al:old/x, /home/al\\ice/x, /mnt+/home/al/x";
|
||||
expect(shortenHomeInString(siblingPaths)).toBe(siblingPaths);
|
||||
expect(
|
||||
shortenHomeInString('PATH=/home/al:/usr/bin:/home/al/bin; dirs=/tmp;/home/al ("/home/al")'),
|
||||
).toBe('PATH=~:/usr/bin:~/bin; dirs=/tmp;~ ("~")');
|
||||
expect(shortenHomeInString("at file:///home/al/app.js:5:3")).toBe("at file://~/app.js:5:3");
|
||||
});
|
||||
withEnv({ OPENCLAW_HOME: undefined, HOME: "/" }, () => {
|
||||
expect(shortenHomeInString("see /var/log/openclaw.log")).toBe("see /var/log/openclaw.log");
|
||||
});
|
||||
});
|
||||
|
||||
it.skipIf(process.platform !== "win32")(
|
||||
"shortens real Windows home casing aliases inside diagnostic text",
|
||||
async () => {
|
||||
|
|
|
|||
28
src/utils.ts
28
src/utils.ts
|
|
@ -1,5 +1,6 @@
|
|||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { normalizeHomeDirValue } from "@openclaw/normalization-core/home-dir";
|
||||
import { resolveConfigDir } from "./infra/config-dir.js";
|
||||
import { resolveEffectiveHomeDir, resolveUserPath } from "./infra/home-dir.js";
|
||||
|
|
@ -60,6 +61,13 @@ export function resolveHomeDir(): string | undefined {
|
|||
return resolveEffectiveHomeDir(process.env, os.homedir);
|
||||
}
|
||||
|
||||
// Stack traces print ESM paths as file:// URLs, so the URL scheme also starts a path.
|
||||
const HOME_TEXT_START = String.raw`(?<=^|[\s"'\x60(\[{<=:;]|file://)`;
|
||||
const HOME_TEXT_DELIMITER = String.raw`[\s"'\x60)\]}>]`;
|
||||
|
||||
// A PATH-style list continues with another absolute path, home prefix, or drive letter.
|
||||
const HOME_TEXT_LIST_NEXT = String.raw`[:;](?=[/\\~$]|[A-Za-z]:)`;
|
||||
|
||||
function resolveHomeDisplayPrefix(): { home: string; prefix: string } | undefined {
|
||||
const home = resolveHomeDir();
|
||||
if (!home) {
|
||||
|
|
@ -81,19 +89,27 @@ export function shortenHomePath(input: string): string {
|
|||
return shortenPathWithHome(input, display);
|
||||
}
|
||||
|
||||
/** Replaces all effective-home occurrences inside a diagnostic string. */
|
||||
/** Replaces effective-home path occurrences inside a diagnostic string. */
|
||||
export function shortenHomeInString(input: string): string {
|
||||
if (!input) {
|
||||
return input;
|
||||
}
|
||||
const display = resolveHomeDisplayPrefix();
|
||||
if (!display) {
|
||||
// A filesystem-root home such as "/" would turn every path separator into the prefix.
|
||||
if (!display || path.parse(display.home).root === display.home) {
|
||||
return input;
|
||||
}
|
||||
if (process.platform === "win32") {
|
||||
return input.replace(new RegExp(escapeRegExpValue(display.home), "giu"), display.prefix);
|
||||
}
|
||||
return input.split(display.home).join(display.prefix);
|
||||
// Diagnostics delimit paths with whitespace, quotes, brackets, `=`, and PATH list separators.
|
||||
// Replace the home only between those delimiters so /home/al+old, /mnt/home/al, and
|
||||
// /home/al.bak stay exact. Trailing `.`, `,`, `;`, or `:` ends the home only when a
|
||||
// delimiter, the end of the text, or the next PATH entry follows it.
|
||||
// POSIX file names may contain backslashes, so only Windows treats them as separators.
|
||||
const pathSeparator = process.platform === "win32" ? String.raw`[\\/]` : "/";
|
||||
const homePattern = new RegExp(
|
||||
`${HOME_TEXT_START}${escapeRegExpValue(display.home)}(?=$|${pathSeparator}|${HOME_TEXT_DELIMITER}|[.,;:](?:$|${HOME_TEXT_DELIMITER})|${HOME_TEXT_LIST_NEXT})`,
|
||||
process.platform === "win32" ? "giu" : "gu",
|
||||
);
|
||||
return input.replace(homePattern, display.prefix);
|
||||
}
|
||||
|
||||
/** Shortens a path for display without changing non-home paths. */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue