mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-18 05:50:30 -07:00
refactor(core): extract and centralize sandbox path utilities (#25305)
Co-authored-by: David Pierce <davidapierce@google.com>
This commit is contained in:
@@ -16,6 +16,8 @@ import {
|
||||
normalizePath,
|
||||
resolveToRealPath,
|
||||
makeRelative,
|
||||
deduplicateAbsolutePaths,
|
||||
toPathKey,
|
||||
} from './paths.js';
|
||||
|
||||
vi.mock('node:fs', async (importOriginal) => {
|
||||
@@ -702,4 +704,62 @@ describe('normalizePath', () => {
|
||||
expect(result).toBe('/usr/local/bin');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deduplicateAbsolutePaths', () => {
|
||||
it('should return an empty array if no paths are provided', () => {
|
||||
expect(deduplicateAbsolutePaths(undefined)).toEqual([]);
|
||||
expect(deduplicateAbsolutePaths(null)).toEqual([]);
|
||||
expect(deduplicateAbsolutePaths([])).toEqual([]);
|
||||
});
|
||||
|
||||
it('should deduplicate paths using their normalized identity', () => {
|
||||
const paths = ['/workspace/foo', '/workspace/foo/'];
|
||||
expect(deduplicateAbsolutePaths(paths)).toEqual(['/workspace/foo']);
|
||||
});
|
||||
|
||||
it('should handle case-insensitivity on Windows and macOS', () => {
|
||||
mockPlatform('win32');
|
||||
const paths = ['/workspace/foo', '/Workspace/Foo'];
|
||||
expect(deduplicateAbsolutePaths(paths)).toEqual(['/workspace/foo']);
|
||||
|
||||
mockPlatform('darwin');
|
||||
const macPaths = ['/tmp/foo', '/Tmp/Foo'];
|
||||
expect(deduplicateAbsolutePaths(macPaths)).toEqual(['/tmp/foo']);
|
||||
|
||||
mockPlatform('linux');
|
||||
const linuxPaths = ['/tmp/foo', '/tmp/FOO'];
|
||||
expect(deduplicateAbsolutePaths(linuxPaths)).toEqual([
|
||||
'/tmp/foo',
|
||||
'/tmp/FOO',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should throw an error if a path is not absolute', () => {
|
||||
const paths = ['relative/path'];
|
||||
expect(() => deduplicateAbsolutePaths(paths)).toThrow(
|
||||
'Path must be absolute: relative/path',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toPathKey', () => {
|
||||
it('should normalize paths and strip trailing slashes', () => {
|
||||
expect(toPathKey('/foo/bar//baz/')).toBe(path.normalize('/foo/bar/baz'));
|
||||
});
|
||||
|
||||
it('should convert paths to lowercase on Windows and macOS', () => {
|
||||
mockPlatform('win32');
|
||||
expect(toPathKey('/Workspace/Foo')).toBe(
|
||||
path.normalize('/workspace/foo'),
|
||||
);
|
||||
// Ensure drive roots are preserved
|
||||
expect(toPathKey('C:\\')).toBe('c:\\');
|
||||
|
||||
mockPlatform('darwin');
|
||||
expect(toPathKey('/Tmp/Foo')).toBe(path.normalize('/tmp/foo'));
|
||||
|
||||
mockPlatform('linux');
|
||||
expect(toPathKey('/Tmp/Foo')).toBe(path.normalize('/Tmp/Foo'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -454,3 +454,45 @@ function robustRealpath(p: string, visited = new Set<string>()): string {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deduplicates an array of paths and ensures all paths are absolute.
|
||||
*/
|
||||
export function deduplicateAbsolutePaths(paths?: string[] | null): string[] {
|
||||
if (!paths || paths.length === 0) return [];
|
||||
|
||||
const uniquePathsMap = new Map<string, string>();
|
||||
for (const p of paths) {
|
||||
if (!path.isAbsolute(p)) {
|
||||
throw new Error(`Path must be absolute: ${p}`);
|
||||
}
|
||||
|
||||
const key = toPathKey(p);
|
||||
if (!uniquePathsMap.has(key)) {
|
||||
uniquePathsMap.set(key, p);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(uniquePathsMap.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stable string key for a path to be used in comparisons or Map lookups.
|
||||
*/
|
||||
export function toPathKey(p: string): string {
|
||||
// Normalize path segments
|
||||
let norm = path.normalize(p);
|
||||
|
||||
// Strip trailing slashes (except for root paths)
|
||||
if (norm.length > 1 && (norm.endsWith('/') || norm.endsWith('\\'))) {
|
||||
// On Windows, don't strip the slash from a drive root (e.g., "C:\\")
|
||||
if (!/^[a-zA-Z]:[\\/]$/.test(norm)) {
|
||||
norm = norm.slice(0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to lowercase on case-insensitive platforms
|
||||
const platform = process.platform;
|
||||
const isCaseInsensitive = platform === 'win32' || platform === 'darwin';
|
||||
return isCaseInsensitive ? norm.toLowerCase() : norm;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user