Files
claude-engineering-plugin/src/utils/files.ts
Matt Van Horn 1886c747d0 refactor: extract shared resolveCommandPath helper for colon-splitting
Deduplicate colon-separated command name logic across all 4 targets
(opencode, droid, gemini, qwen) into a single resolveCommandPath()
helper in utils/files.ts.

Addresses review feedback on PR #251.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 07:08:07 -07:00

107 lines
3.4 KiB
TypeScript

import { promises as fs } from "fs"
import path from "path"
export async function backupFile(filePath: string): Promise<string | null> {
if (!(await pathExists(filePath))) return null
try {
const timestamp = new Date().toISOString().replace(/[:.]/g, "-")
const backupPath = `${filePath}.bak.${timestamp}`
await fs.copyFile(filePath, backupPath)
return backupPath
} catch {
return null
}
}
export async function pathExists(filePath: string): Promise<boolean> {
try {
await fs.access(filePath)
return true
} catch {
return false
}
}
export async function ensureDir(dirPath: string): Promise<void> {
await fs.mkdir(dirPath, { recursive: true })
}
export async function readText(filePath: string): Promise<string> {
return fs.readFile(filePath, "utf8")
}
export async function readJson<T>(filePath: string): Promise<T> {
const raw = await readText(filePath)
return JSON.parse(raw) as T
}
export async function writeText(filePath: string, content: string): Promise<void> {
await ensureDir(path.dirname(filePath))
await fs.writeFile(filePath, content, "utf8")
}
export async function writeTextSecure(filePath: string, content: string): Promise<void> {
await ensureDir(path.dirname(filePath))
await fs.writeFile(filePath, content, { encoding: "utf8", mode: 0o600 })
await fs.chmod(filePath, 0o600)
}
export async function writeJson(filePath: string, data: unknown): Promise<void> {
const content = JSON.stringify(data, null, 2)
await writeText(filePath, content + "\n")
}
/** Write JSON with restrictive permissions (0o600) for files containing secrets */
export async function writeJsonSecure(filePath: string, data: unknown): Promise<void> {
const content = JSON.stringify(data, null, 2)
await ensureDir(path.dirname(filePath))
await fs.writeFile(filePath, content + "\n", { encoding: "utf8", mode: 0o600 })
await fs.chmod(filePath, 0o600)
}
export async function walkFiles(root: string): Promise<string[]> {
const entries = await fs.readdir(root, { withFileTypes: true })
const results: string[] = []
for (const entry of entries) {
const fullPath = path.join(root, entry.name)
if (entry.isDirectory()) {
const nested = await walkFiles(fullPath)
results.push(...nested)
} else if (entry.isFile()) {
results.push(fullPath)
}
}
return results
}
/**
* Resolve a colon-separated command name into a filesystem path.
* e.g. resolveCommandPath("/commands", "ce:plan", ".md") -> "/commands/ce/plan.md"
* Creates intermediate directories as needed.
*/
export async function resolveCommandPath(dir: string, name: string, ext: string): Promise<string> {
const parts = name.split(":")
if (parts.length > 1) {
const nestedDir = path.join(dir, ...parts.slice(0, -1))
await ensureDir(nestedDir)
return path.join(nestedDir, `${parts[parts.length - 1]}${ext}`)
}
return path.join(dir, `${name}${ext}`)
}
export async function copyDir(sourceDir: string, targetDir: string): Promise<void> {
await ensureDir(targetDir)
const entries = await fs.readdir(sourceDir, { withFileTypes: true })
for (const entry of entries) {
const sourcePath = path.join(sourceDir, entry.name)
const targetPath = path.join(targetDir, entry.name)
if (entry.isDirectory()) {
await copyDir(sourcePath, targetPath)
} else if (entry.isFile()) {
await ensureDir(path.dirname(targetPath))
await fs.copyFile(sourcePath, targetPath)
}
}
}