fix(bash): resolved nested internal URLs
- Tracked quote context independently inside command substitutions. - Covered unquoted skill URLs nested under outer double quotes. Fixes #5535
This commit is contained in:
@@ -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" },
|
||||
|
||||
Reference in New Issue
Block a user