mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-21 12:51:17 -05:00
Refactor out file helpers into fs.ts
This commit is contained in:
@@ -42,6 +42,8 @@ import { formatResponse } from "./prompts/responses"
|
|||||||
import { SYSTEM_PROMPT } from "./prompts/system"
|
import { SYSTEM_PROMPT } from "./prompts/system"
|
||||||
import { truncateHalfConversation } from "./sliding-window"
|
import { truncateHalfConversation } from "./sliding-window"
|
||||||
import { ClaudeDevProvider } from "./webview/ClaudeDevProvider"
|
import { ClaudeDevProvider } from "./webview/ClaudeDevProvider"
|
||||||
|
import { calculateApiCost } from "../utils/cost"
|
||||||
|
import { createDirectoriesForFile, fileExistsAtPath } from "../utils/fs"
|
||||||
|
|
||||||
const cwd =
|
const cwd =
|
||||||
vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution
|
vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution
|
||||||
@@ -128,10 +130,7 @@ export class ClaudeDev {
|
|||||||
|
|
||||||
private async getSavedApiConversationHistory(): Promise<Anthropic.MessageParam[]> {
|
private async getSavedApiConversationHistory(): Promise<Anthropic.MessageParam[]> {
|
||||||
const filePath = path.join(await this.ensureTaskDirectoryExists(), "api_conversation_history.json")
|
const filePath = path.join(await this.ensureTaskDirectoryExists(), "api_conversation_history.json")
|
||||||
const fileExists = await fs
|
const fileExists = await fileExistsAtPath(filePath)
|
||||||
.access(filePath)
|
|
||||||
.then(() => true)
|
|
||||||
.catch(() => false)
|
|
||||||
if (fileExists) {
|
if (fileExists) {
|
||||||
return JSON.parse(await fs.readFile(filePath, "utf8"))
|
return JSON.parse(await fs.readFile(filePath, "utf8"))
|
||||||
}
|
}
|
||||||
@@ -160,10 +159,7 @@ export class ClaudeDev {
|
|||||||
|
|
||||||
private async getSavedClaudeMessages(): Promise<ClaudeMessage[]> {
|
private async getSavedClaudeMessages(): Promise<ClaudeMessage[]> {
|
||||||
const filePath = path.join(await this.ensureTaskDirectoryExists(), "claude_messages.json")
|
const filePath = path.join(await this.ensureTaskDirectoryExists(), "claude_messages.json")
|
||||||
const fileExists = await fs
|
const fileExists = await fileExistsAtPath(filePath)
|
||||||
.access(filePath)
|
|
||||||
.then(() => true)
|
|
||||||
.catch(() => false)
|
|
||||||
if (fileExists) {
|
if (fileExists) {
|
||||||
return JSON.parse(await fs.readFile(filePath, "utf8"))
|
return JSON.parse(await fs.readFile(filePath, "utf8"))
|
||||||
}
|
}
|
||||||
@@ -350,6 +346,16 @@ export class ClaudeDev {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sayAndCreateMissingParamError(toolName: ToolName, paramName: string, relPath?: string) {
|
||||||
|
await this.say(
|
||||||
|
"error",
|
||||||
|
`Claude tried to use ${toolName}${
|
||||||
|
relPath ? ` for '${relPath.toPosix()}'` : ""
|
||||||
|
} without value for required parameter '${paramName}'. Retrying...`
|
||||||
|
)
|
||||||
|
return formatResponse.toolError(formatResponse.missingToolParameterError(paramName))
|
||||||
|
}
|
||||||
|
|
||||||
private async startTask(task?: string, images?: string[]): Promise<void> {
|
private async startTask(task?: string, images?: string[]): Promise<void> {
|
||||||
// conversationHistory (for API) and claudeMessages (for webview) need to be in sync
|
// conversationHistory (for API) and claudeMessages (for webview) need to be in sync
|
||||||
// if the extension process were killed, then on restart the claudeMessages might not be empty, so we need to set it to [] when we create a new ClaudeDev client (otherwise webview would show stale messages from previous session)
|
// if the extension process were killed, then on restart the claudeMessages might not be empty, so we need to set it to [] when we create a new ClaudeDev client (otherwise webview would show stale messages from previous session)
|
||||||
@@ -591,28 +597,6 @@ export class ClaudeDev {
|
|||||||
this.urlContentFetcher.closeBrowser()
|
this.urlContentFetcher.closeBrowser()
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateApiCost(
|
|
||||||
inputTokens: number,
|
|
||||||
outputTokens: number,
|
|
||||||
cacheCreationInputTokens?: number,
|
|
||||||
cacheReadInputTokens?: number
|
|
||||||
): number {
|
|
||||||
const modelCacheWritesPrice = this.api.getModel().info.cacheWritesPrice
|
|
||||||
let cacheWritesCost = 0
|
|
||||||
if (cacheCreationInputTokens && modelCacheWritesPrice) {
|
|
||||||
cacheWritesCost = (modelCacheWritesPrice / 1_000_000) * cacheCreationInputTokens
|
|
||||||
}
|
|
||||||
const modelCacheReadsPrice = this.api.getModel().info.cacheReadsPrice
|
|
||||||
let cacheReadsCost = 0
|
|
||||||
if (cacheReadInputTokens && modelCacheReadsPrice) {
|
|
||||||
cacheReadsCost = (modelCacheReadsPrice / 1_000_000) * cacheReadInputTokens
|
|
||||||
}
|
|
||||||
const baseInputCost = (this.api.getModel().info.inputPrice / 1_000_000) * inputTokens
|
|
||||||
const outputCost = (this.api.getModel().info.outputPrice / 1_000_000) * outputTokens
|
|
||||||
const totalCost = cacheWritesCost + cacheReadsCost + baseInputCost + outputCost
|
|
||||||
return totalCost
|
|
||||||
}
|
|
||||||
|
|
||||||
// return is [didUserRejectTool, ToolResponse]
|
// return is [didUserRejectTool, ToolResponse]
|
||||||
async writeToFile(relPath?: string, newContent?: string): Promise<[boolean, ToolResponse]> {
|
async writeToFile(relPath?: string, newContent?: string): Promise<[boolean, ToolResponse]> {
|
||||||
if (relPath === undefined) {
|
if (relPath === undefined) {
|
||||||
@@ -636,10 +620,7 @@ export class ClaudeDev {
|
|||||||
this.consecutiveMistakeCount = 0
|
this.consecutiveMistakeCount = 0
|
||||||
try {
|
try {
|
||||||
const absolutePath = path.resolve(cwd, relPath)
|
const absolutePath = path.resolve(cwd, relPath)
|
||||||
const fileExists = await fs
|
const fileExists = await fileExistsAtPath(absolutePath)
|
||||||
.access(absolutePath)
|
|
||||||
.then(() => true)
|
|
||||||
.catch(() => false)
|
|
||||||
|
|
||||||
// if the file is already open, ensure it's not dirty before getting its contents
|
// if the file is already open, ensure it's not dirty before getting its contents
|
||||||
if (fileExists) {
|
if (fileExists) {
|
||||||
@@ -671,7 +652,7 @@ export class ClaudeDev {
|
|||||||
// for new files, create any necessary directories and keep track of new directories to delete if the user denies the operation
|
// for new files, create any necessary directories and keep track of new directories to delete if the user denies the operation
|
||||||
|
|
||||||
// Keep track of newly created directories
|
// Keep track of newly created directories
|
||||||
const createdDirs: string[] = await this.createDirectoriesForFile(absolutePath)
|
const createdDirs: string[] = await createDirectoriesForFile(absolutePath)
|
||||||
// console.log(`Created directories: ${createdDirs.join(", ")}`)
|
// console.log(`Created directories: ${createdDirs.join(", ")}`)
|
||||||
// make sure the file exists before we open it
|
// make sure the file exists before we open it
|
||||||
if (!fileExists) {
|
if (!fileExists) {
|
||||||
@@ -992,51 +973,6 @@ export class ClaudeDev {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Asynchronously creates all non-existing subdirectories for a given file path
|
|
||||||
* and collects them in an array for later deletion.
|
|
||||||
*
|
|
||||||
* @param filePath - The full path to a file.
|
|
||||||
* @returns A promise that resolves to an array of newly created directories.
|
|
||||||
*/
|
|
||||||
async createDirectoriesForFile(filePath: string): Promise<string[]> {
|
|
||||||
const newDirectories: string[] = []
|
|
||||||
const normalizedFilePath = path.normalize(filePath) // Normalize path for cross-platform compatibility
|
|
||||||
const directoryPath = path.dirname(normalizedFilePath)
|
|
||||||
|
|
||||||
let currentPath = directoryPath
|
|
||||||
const dirsToCreate: string[] = []
|
|
||||||
|
|
||||||
// Traverse up the directory tree and collect missing directories
|
|
||||||
while (!(await this.exists(currentPath))) {
|
|
||||||
dirsToCreate.push(currentPath)
|
|
||||||
currentPath = path.dirname(currentPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create directories from the topmost missing one down to the target directory
|
|
||||||
for (let i = dirsToCreate.length - 1; i >= 0; i--) {
|
|
||||||
await fs.mkdir(dirsToCreate[i])
|
|
||||||
newDirectories.push(dirsToCreate[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
return newDirectories
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to check if a path exists.
|
|
||||||
*
|
|
||||||
* @param path - The path to check.
|
|
||||||
* @returns A promise that resolves to true if the path exists, false otherwise.
|
|
||||||
*/
|
|
||||||
async exists(filePath: string): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
await fs.access(filePath)
|
|
||||||
return true
|
|
||||||
} catch {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createPrettyPatch(filename = "file", oldStr: string, newStr: string) {
|
createPrettyPatch(filename = "file", oldStr: string, newStr: string) {
|
||||||
const patch = diff.createPatch(filename.toPosix(), oldStr, newStr)
|
const patch = diff.createPatch(filename.toPosix(), oldStr, newStr)
|
||||||
const lines = patch.split("\n")
|
const lines = patch.split("\n")
|
||||||
@@ -1404,10 +1340,7 @@ ${this.customInstructions.trim()}
|
|||||||
fileExists = this.isEditingExistingFile
|
fileExists = this.isEditingExistingFile
|
||||||
} else {
|
} else {
|
||||||
const absolutePath = path.resolve(cwd, relPath)
|
const absolutePath = path.resolve(cwd, relPath)
|
||||||
fileExists = await fs
|
fileExists = await fileExistsAtPath(absolutePath)
|
||||||
.access(absolutePath)
|
|
||||||
.then(() => true)
|
|
||||||
.catch(() => false)
|
|
||||||
|
|
||||||
this.isEditingExistingFile = fileExists
|
this.isEditingExistingFile = fileExists
|
||||||
}
|
}
|
||||||
@@ -1440,7 +1373,7 @@ ${this.customInstructions.trim()}
|
|||||||
// for new files, create any necessary directories and keep track of new directories to delete if the user denies the operation
|
// for new files, create any necessary directories and keep track of new directories to delete if the user denies the operation
|
||||||
|
|
||||||
// Keep track of newly created directories
|
// Keep track of newly created directories
|
||||||
this.editFileCreatedDirs = await this.createDirectoriesForFile(absolutePath)
|
this.editFileCreatedDirs = await createDirectoriesForFile(absolutePath)
|
||||||
// console.log(`Created directories: ${createdDirs.join(", ")}`)
|
// console.log(`Created directories: ${createdDirs.join(", ")}`)
|
||||||
// make sure the file exists before we open it
|
// make sure the file exists before we open it
|
||||||
if (!fileExists) {
|
if (!fileExists) {
|
||||||
@@ -1528,7 +1461,7 @@ ${this.customInstructions.trim()}
|
|||||||
// for new files, create any necessary directories and keep track of new directories to delete if the user denies the operation
|
// for new files, create any necessary directories and keep track of new directories to delete if the user denies the operation
|
||||||
|
|
||||||
// Keep track of newly created directories
|
// Keep track of newly created directories
|
||||||
this.editFileCreatedDirs = await this.createDirectoriesForFile(absolutePath)
|
this.editFileCreatedDirs = await createDirectoriesForFile(absolutePath)
|
||||||
// console.log(`Created directories: ${createdDirs.join(", ")}`)
|
// console.log(`Created directories: ${createdDirs.join(", ")}`)
|
||||||
// make sure the file exists before we open it
|
// make sure the file exists before we open it
|
||||||
if (!fileExists) {
|
if (!fileExists) {
|
||||||
@@ -2438,7 +2371,15 @@ ${this.customInstructions.trim()}
|
|||||||
tokensOut: outputTokens,
|
tokensOut: outputTokens,
|
||||||
cacheWrites: cacheWriteTokens,
|
cacheWrites: cacheWriteTokens,
|
||||||
cacheReads: cacheReadTokens,
|
cacheReads: cacheReadTokens,
|
||||||
cost: totalCost ?? this.calculateApiCost(inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens),
|
cost:
|
||||||
|
totalCost ??
|
||||||
|
calculateApiCost(
|
||||||
|
this.api.getModel().info,
|
||||||
|
inputTokens,
|
||||||
|
outputTokens,
|
||||||
|
cacheWriteTokens,
|
||||||
|
cacheReadTokens
|
||||||
|
),
|
||||||
})
|
})
|
||||||
await this.saveClaudeMessages()
|
await this.saveClaudeMessages()
|
||||||
await this.providerRef.deref()?.postStateToWebview()
|
await this.providerRef.deref()?.postStateToWebview()
|
||||||
@@ -2666,14 +2607,4 @@ ${this.customInstructions.trim()}
|
|||||||
|
|
||||||
return `<environment_details>\n${details.trim()}\n</environment_details>`
|
return `<environment_details>\n${details.trim()}\n</environment_details>`
|
||||||
}
|
}
|
||||||
|
|
||||||
async sayAndCreateMissingParamError(toolName: ToolName, paramName: string, relPath?: string) {
|
|
||||||
await this.say(
|
|
||||||
"error",
|
|
||||||
`Claude tried to use ${toolName}${
|
|
||||||
relPath ? ` for '${relPath.toPosix()}'` : ""
|
|
||||||
} without value for required parameter '${paramName}'. Retrying...`
|
|
||||||
)
|
|
||||||
return formatResponse.toolError(formatResponse.missingToolParameterError(paramName))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { getTheme } from "../../integrations/theme/getTheme"
|
|||||||
import { openFile, openImage } from "../../integrations/misc/open-file"
|
import { openFile, openImage } from "../../integrations/misc/open-file"
|
||||||
import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker"
|
import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker"
|
||||||
import { openMention } from "../mentions"
|
import { openMention } from "../mentions"
|
||||||
|
import { fileExistsAtPath } from "../../utils/fs"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||||
@@ -505,10 +506,7 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
|||||||
const taskDirPath = path.join(this.context.globalStorageUri.fsPath, "tasks", id)
|
const taskDirPath = path.join(this.context.globalStorageUri.fsPath, "tasks", id)
|
||||||
const apiConversationHistoryFilePath = path.join(taskDirPath, "api_conversation_history.json")
|
const apiConversationHistoryFilePath = path.join(taskDirPath, "api_conversation_history.json")
|
||||||
const claudeMessagesFilePath = path.join(taskDirPath, "claude_messages.json")
|
const claudeMessagesFilePath = path.join(taskDirPath, "claude_messages.json")
|
||||||
const fileExists = await fs
|
const fileExists = await fileExistsAtPath(apiConversationHistoryFilePath)
|
||||||
.access(apiConversationHistoryFilePath)
|
|
||||||
.then(() => true)
|
|
||||||
.catch(() => false)
|
|
||||||
if (fileExists) {
|
if (fileExists) {
|
||||||
const apiConversationHistory = JSON.parse(await fs.readFile(apiConversationHistoryFilePath, "utf8"))
|
const apiConversationHistory = JSON.parse(await fs.readFile(apiConversationHistoryFilePath, "utf8"))
|
||||||
return {
|
return {
|
||||||
@@ -547,17 +545,11 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
|||||||
const { taskDirPath, apiConversationHistoryFilePath, claudeMessagesFilePath } = await this.getTaskWithId(id)
|
const { taskDirPath, apiConversationHistoryFilePath, claudeMessagesFilePath } = await this.getTaskWithId(id)
|
||||||
|
|
||||||
// Delete the task files
|
// Delete the task files
|
||||||
const apiConversationHistoryFileExists = await fs
|
const apiConversationHistoryFileExists = await fileExistsAtPath(apiConversationHistoryFilePath)
|
||||||
.access(apiConversationHistoryFilePath)
|
|
||||||
.then(() => true)
|
|
||||||
.catch(() => false)
|
|
||||||
if (apiConversationHistoryFileExists) {
|
if (apiConversationHistoryFileExists) {
|
||||||
await fs.unlink(apiConversationHistoryFilePath)
|
await fs.unlink(apiConversationHistoryFilePath)
|
||||||
}
|
}
|
||||||
const claudeMessagesFileExists = await fs
|
const claudeMessagesFileExists = await fileExistsAtPath(claudeMessagesFilePath)
|
||||||
.access(claudeMessagesFilePath)
|
|
||||||
.then(() => true)
|
|
||||||
.catch(() => false)
|
|
||||||
if (claudeMessagesFileExists) {
|
if (claudeMessagesFileExists) {
|
||||||
await fs.unlink(claudeMessagesFilePath)
|
await fs.unlink(claudeMessagesFilePath)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import TurndownService from "turndown"
|
|||||||
import PCR from "puppeteer-chromium-resolver"
|
import PCR from "puppeteer-chromium-resolver"
|
||||||
import pWaitFor from "p-wait-for"
|
import pWaitFor from "p-wait-for"
|
||||||
import delay from "delay"
|
import delay from "delay"
|
||||||
|
import { fileExistsAtPath } from "../../utils/fs"
|
||||||
|
|
||||||
interface PCRStats {
|
interface PCRStats {
|
||||||
puppeteer: { launch: typeof launch }
|
puppeteer: { launch: typeof launch }
|
||||||
@@ -30,10 +31,7 @@ export class UrlContentFetcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const puppeteerDir = path.join(globalStoragePath, "puppeteer")
|
const puppeteerDir = path.join(globalStoragePath, "puppeteer")
|
||||||
const dirExists = await fs
|
const dirExists = await fileExistsAtPath(puppeteerDir)
|
||||||
.access(puppeteerDir)
|
|
||||||
.then(() => true)
|
|
||||||
.catch(() => false)
|
|
||||||
if (!dirExists) {
|
if (!dirExists) {
|
||||||
await fs.mkdir(puppeteerDir, { recursive: true })
|
await fs.mkdir(puppeteerDir, { recursive: true })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,12 @@ import * as fs from "fs/promises"
|
|||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { listFiles } from "../glob/list-files"
|
import { listFiles } from "../glob/list-files"
|
||||||
import { LanguageParser, loadRequiredLanguageParsers } from "./languageParser"
|
import { LanguageParser, loadRequiredLanguageParsers } from "./languageParser"
|
||||||
|
import { fileExistsAtPath } from "../../utils/fs"
|
||||||
|
|
||||||
// TODO: implement caching behavior to avoid having to keep analyzing project for new tasks.
|
// TODO: implement caching behavior to avoid having to keep analyzing project for new tasks.
|
||||||
export async function parseSourceCodeForDefinitionsTopLevel(dirPath: string): Promise<string> {
|
export async function parseSourceCodeForDefinitionsTopLevel(dirPath: string): Promise<string> {
|
||||||
// check if the path exists
|
// check if the path exists
|
||||||
const dirExists = await fs
|
const dirExists = await fileExistsAtPath(path.resolve(dirPath))
|
||||||
.access(path.resolve(dirPath))
|
|
||||||
.then(() => true)
|
|
||||||
.catch(() => false)
|
|
||||||
if (!dirExists) {
|
if (!dirExists) {
|
||||||
return "This directory does not exist or you do not have permission to access it."
|
return "This directory does not exist or you do not have permission to access it."
|
||||||
}
|
}
|
||||||
|
|||||||
24
src/utils/cost.ts
Normal file
24
src/utils/cost.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { ModelInfo } from "../shared/api"
|
||||||
|
|
||||||
|
export function calculateApiCost(
|
||||||
|
modelInfo: ModelInfo,
|
||||||
|
inputTokens: number,
|
||||||
|
outputTokens: number,
|
||||||
|
cacheCreationInputTokens?: number,
|
||||||
|
cacheReadInputTokens?: number
|
||||||
|
): number {
|
||||||
|
const modelCacheWritesPrice = modelInfo.cacheWritesPrice
|
||||||
|
let cacheWritesCost = 0
|
||||||
|
if (cacheCreationInputTokens && modelCacheWritesPrice) {
|
||||||
|
cacheWritesCost = (modelCacheWritesPrice / 1_000_000) * cacheCreationInputTokens
|
||||||
|
}
|
||||||
|
const modelCacheReadsPrice = modelInfo.cacheReadsPrice
|
||||||
|
let cacheReadsCost = 0
|
||||||
|
if (cacheReadInputTokens && modelCacheReadsPrice) {
|
||||||
|
cacheReadsCost = (modelCacheReadsPrice / 1_000_000) * cacheReadInputTokens
|
||||||
|
}
|
||||||
|
const baseInputCost = (modelInfo.inputPrice / 1_000_000) * inputTokens
|
||||||
|
const outputCost = (modelInfo.outputPrice / 1_000_000) * outputTokens
|
||||||
|
const totalCost = cacheWritesCost + cacheReadsCost + baseInputCost + outputCost
|
||||||
|
return totalCost
|
||||||
|
}
|
||||||
47
src/utils/fs.ts
Normal file
47
src/utils/fs.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import fs from "fs/promises"
|
||||||
|
import * as path from "path"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronously creates all non-existing subdirectories for a given file path
|
||||||
|
* and collects them in an array for later deletion.
|
||||||
|
*
|
||||||
|
* @param filePath - The full path to a file.
|
||||||
|
* @returns A promise that resolves to an array of newly created directories.
|
||||||
|
*/
|
||||||
|
export async function createDirectoriesForFile(filePath: string): Promise<string[]> {
|
||||||
|
const newDirectories: string[] = []
|
||||||
|
const normalizedFilePath = path.normalize(filePath) // Normalize path for cross-platform compatibility
|
||||||
|
const directoryPath = path.dirname(normalizedFilePath)
|
||||||
|
|
||||||
|
let currentPath = directoryPath
|
||||||
|
const dirsToCreate: string[] = []
|
||||||
|
|
||||||
|
// Traverse up the directory tree and collect missing directories
|
||||||
|
while (!(await fileExistsAtPath(currentPath))) {
|
||||||
|
dirsToCreate.push(currentPath)
|
||||||
|
currentPath = path.dirname(currentPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create directories from the topmost missing one down to the target directory
|
||||||
|
for (let i = dirsToCreate.length - 1; i >= 0; i--) {
|
||||||
|
await fs.mkdir(dirsToCreate[i])
|
||||||
|
newDirectories.push(dirsToCreate[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return newDirectories
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to check if a path exists.
|
||||||
|
*
|
||||||
|
* @param path - The path to check.
|
||||||
|
* @returns A promise that resolves to true if the path exists, false otherwise.
|
||||||
|
*/
|
||||||
|
export async function fileExistsAtPath(filePath: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await fs.access(filePath)
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user