Merge remote-tracking branch 'origin/farm/43648db8/resolve-internal-uri-in-command-substitution'

This commit is contained in:
can1357
2026-07-15 00:33:03 +02:00
3 changed files with 41 additions and 1 deletions
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Fixed Bash internal URLs remaining unresolved when used as unquoted arguments inside command substitutions ([#5535](https://github.com/can1357/oh-my-pi/issues/5535)).
## [16.5.2] - 2026-07-14
### Breaking Changes
@@ -142,7 +142,14 @@ function unquoteToken(token: string): string {
}
function isInsideShellQuote(command: string, index: number): boolean {
let quote: "'" | '"' | undefined;
type ShellQuote = "'" | '"' | undefined;
interface CommandSubstitution {
outerQuote: ShellQuote;
depth: number;
}
let quote: ShellQuote;
const substitutions: CommandSubstitution[] = [];
for (let i = 0; i < index; i++) {
const char = command[i];
if (char === "\\" && quote !== "'") {
@@ -155,6 +162,25 @@ function isInsideShellQuote(command: string, index: number): boolean {
}
if (char === '"' && quote !== "'") {
quote = quote === '"' ? undefined : '"';
continue;
}
if (char === "$" && command[i + 1] === "(" && quote !== "'") {
substitutions.push({ outerQuote: quote, depth: 1 });
quote = undefined;
i++;
continue;
}
if (quote !== undefined) continue;
const substitution = substitutions.at(-1);
if (!substitution) continue;
if (char === "(") {
substitution.depth++;
} else if (char === ")") {
substitution.depth--;
if (substitution.depth === 0) {
quote = substitutions.pop()?.outerQuote;
}
}
}
return quote !== undefined;
@@ -206,6 +206,16 @@ describe("expandInternalUrls", () => {
);
});
it("expands an unquoted URL inside a double-quoted command substitution", async () => {
const skills = [createSkill("valid-skill", "/tmp/skills/valid-skill")];
const command = 'echo "$(realpath skill://valid-skill/SKILL.md 2>&1)"';
const expectedPath = path.join(skills[0].baseDir, "SKILL.md");
await expect(expandInternalUrls(command, { skills })).resolves.toBe(
`echo "$(realpath ${shellEscape(expectedPath)} 2>&1)"`,
);
});
it("leaves literal internal URLs embedded in quoted text unchanged", async () => {
const router = createInternalRouter({
"memory://root/summary.md": { sourcePath: "/tmp/memories/summary.md" },