refactor(cli): use standardized hydrateString for custom command prompts

This replaces the manual regex for ${extensionPath} with the standardized
hydrateString utility, automatically enabling support for other variables
like ${workspacePath} and ${/} in custom extension commands.

Added a test case to verify ${workspacePath} hydration.
This commit is contained in:
Jerop Kipruto
2026-03-17 11:43:46 -04:00
parent 39361bce1f
commit 5e22ebe30e
2 changed files with 66 additions and 7 deletions

View File

@@ -955,6 +955,64 @@ describe('FileCommandLoader', () => {
assert.fail('Incorrect action type');
}
});
it('correctly injects ${workspacePath} into extension commands', async () => {
const workspaceDir = process.cwd();
const extensionDir = path.join(
workspaceDir,
GEMINI_DIR,
'extensions',
'ws-test-ext',
);
mock({
[extensionDir]: {
'gemini-extension.json': JSON.stringify({
name: 'ws-test-ext',
version: '1.0.0',
}),
commands: {
'ws-cmd.toml': 'prompt = "WS: ${workspacePath}"',
},
},
});
const mockConfig = {
getProjectRoot: vi.fn(() => workspaceDir),
getExtensions: vi.fn(() => [
{
name: 'ws-test-ext',
version: '1.0.0',
isActive: true,
path: extensionDir,
},
]),
getFolderTrust: vi.fn(() => false),
isTrustedFolder: vi.fn(() => false),
} as unknown as Config;
const loader = new FileCommandLoader(mockConfig);
const commands = await loader.loadCommands(signal);
expect(commands).toHaveLength(1);
const command = commands[0];
const result = await command.action?.(
createMockCommandContext({
invocation: {
raw: '/ws-cmd',
name: 'ws-cmd',
args: '',
},
}),
'',
);
if (result?.type === 'submit_prompt') {
expect(result.content).toEqual([{ text: `WS: ${workspaceDir}` }]);
} else {
assert.fail('Incorrect action type');
}
});
});
describe('Argument Handling Integration (via ShellProcessor)', () => {

View File

@@ -33,6 +33,7 @@ import {
} from './prompt-processors/shellProcessor.js';
import { AtFileProcessor } from './prompt-processors/atFileProcessor.js';
import { sanitizeForDisplay } from '../ui/utils/textUtils.js';
import { hydrateString } from '../config/extensions/variables.js';
interface CommandDirectory {
path: string;
@@ -239,13 +240,13 @@ export class FileCommandLoader implements ICommandLoader {
const validDef = validationResult.data;
// Hydrate extensionPath if this is an extension command
if (extensionPath) {
validDef.prompt = validDef.prompt.replace(
/\$\{extensionPath\}/g,
() => extensionPath,
);
}
// Hydrate variables in the prompt
validDef.prompt = hydrateString(validDef.prompt, {
extensionPath,
workspacePath: this.projectRoot,
'/': path.sep,
pathSeparator: path.sep,
});
const relativePathWithExt = path.relative(baseDir, filePath);
const relativePath = relativePathWithExt.substring(