111 lines
3.1 KiB
TypeScript
111 lines
3.1 KiB
TypeScript
import chalk from 'chalk'
|
|
import { stat } from 'fs/promises'
|
|
import { dirname, resolve } from 'path'
|
|
import type { ToolPermissionContext } from '../../Tool.js'
|
|
import { getErrnoCode } from '../../utils/errors.js'
|
|
import { expandPath } from '../../utils/path.js'
|
|
import {
|
|
allWorkingDirectories,
|
|
pathInWorkingPath,
|
|
} from '../../utils/permissions/filesystem.js'
|
|
|
|
export type AddDirectoryResult =
|
|
| {
|
|
resultType: 'success'
|
|
absolutePath: string
|
|
}
|
|
| {
|
|
resultType: 'emptyPath'
|
|
}
|
|
| {
|
|
resultType: 'pathNotFound' | 'notADirectory'
|
|
directoryPath: string
|
|
absolutePath: string
|
|
}
|
|
| {
|
|
resultType: 'alreadyInWorkingDirectory'
|
|
directoryPath: string
|
|
workingDir: string
|
|
}
|
|
|
|
export async function validateDirectoryForWorkspace(
|
|
directoryPath: string,
|
|
permissionContext: ToolPermissionContext,
|
|
): Promise<AddDirectoryResult> {
|
|
if (!directoryPath) {
|
|
return {
|
|
resultType: 'emptyPath',
|
|
}
|
|
}
|
|
|
|
// resolve() strips the trailing slash expandPath can leave on absolute
|
|
// inputs, so /foo and /foo/ map to the same storage key (CC-33).
|
|
const absolutePath = resolve(expandPath(directoryPath))
|
|
|
|
// Check if path exists and is a directory (single syscall)
|
|
try {
|
|
const stats = await stat(absolutePath)
|
|
if (!stats.isDirectory()) {
|
|
return {
|
|
resultType: 'notADirectory',
|
|
directoryPath,
|
|
absolutePath,
|
|
}
|
|
}
|
|
} catch (e: unknown) {
|
|
const code = getErrnoCode(e)
|
|
// Match prior existsSync() semantics: treat any of these as "not found"
|
|
// rather than re-throwing. EACCES/EPERM in particular must not crash
|
|
// startup when a settings-configured additional directory is inaccessible.
|
|
if (
|
|
code === 'ENOENT' ||
|
|
code === 'ENOTDIR' ||
|
|
code === 'EACCES' ||
|
|
code === 'EPERM'
|
|
) {
|
|
return {
|
|
resultType: 'pathNotFound',
|
|
directoryPath,
|
|
absolutePath,
|
|
}
|
|
}
|
|
throw e
|
|
}
|
|
|
|
// Get current permission context
|
|
const currentWorkingDirs = allWorkingDirectories(permissionContext)
|
|
|
|
// Check if already within an existing working directory
|
|
for (const workingDir of currentWorkingDirs) {
|
|
if (pathInWorkingPath(absolutePath, workingDir)) {
|
|
return {
|
|
resultType: 'alreadyInWorkingDirectory',
|
|
directoryPath,
|
|
workingDir,
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
resultType: 'success',
|
|
absolutePath,
|
|
}
|
|
}
|
|
|
|
export function addDirHelpMessage(result: AddDirectoryResult): string {
|
|
switch (result.resultType) {
|
|
case 'emptyPath':
|
|
return 'Please provide a directory path.'
|
|
case 'pathNotFound':
|
|
return `Path ${chalk.bold(result.absolutePath)} was not found.`
|
|
case 'notADirectory': {
|
|
const parentDir = dirname(result.absolutePath)
|
|
return `${chalk.bold(result.directoryPath)} is not a directory. Did you mean to add the parent directory ${chalk.bold(parentDir)}?`
|
|
}
|
|
case 'alreadyInWorkingDirectory':
|
|
return `${chalk.bold(result.directoryPath)} is already accessible within the existing working directory ${chalk.bold(result.workingDir)}.`
|
|
case 'success':
|
|
return `Added ${chalk.bold(result.absolutePath)} as a working directory.`
|
|
}
|
|
}
|