knowhub
Version:
Synchronize AI coding–agent knowledge files (rules, templates, guidelines) across your project.
157 lines • 5.05 kB
JavaScript
import { copyFile as fsCopyFile, mkdir, readFile, readdir, stat, symlink, writeFile, } from "node:fs/promises";
import { dirname, join, relative } from "node:path";
import { FileOperationError } from "./errors.js";
const SymlinkType = {
File: "file",
Dir: "dir",
Junction: "junction",
};
const Platform = {
Win32: "win32",
Darwin: "darwin",
Linux: "linux",
};
const ErrorCode = {
Enoent: "ENOENT",
Eacces: "EACCES",
Eperm: "EPERM",
Eexist: "EEXIST",
};
export async function copyFile(src, dest, overwrite) {
const exists = await pathExists(dest);
if (exists && !overwrite) {
return { created: false, updated: false };
}
if (exists && overwrite) {
const srcContent = await readFile(src);
const destContent = await readFile(dest);
if (srcContent.equals(destContent)) {
return { created: false, updated: false };
}
}
await ensureDirectoryExists(dirname(dest));
await fsCopyFile(src, dest);
if (exists && overwrite) {
return { created: false, updated: true };
}
return { created: true, updated: false };
}
export async function copyDirectory(srcDir, destDir, overwrite) {
let created = 0;
let skipped = 0;
await ensureDirectoryExists(destDir);
const entries = await readdir(srcDir, { withFileTypes: true });
for (const entry of entries) {
const srcPath = join(srcDir, entry.name);
const destPath = join(destDir, entry.name);
if (entry.isDirectory()) {
const result = await copyDirectory(srcPath, destPath, overwrite);
created += result.created;
skipped += result.skipped;
}
else if (entry.isFile()) {
const result = await copyFile(srcPath, destPath, overwrite);
if (result.created || result.updated) {
created++;
}
else {
skipped++;
}
}
}
return { created, skipped };
}
export async function symlinkOrCopy(src, dest, overwrite, isDir) {
const exists = await pathExists(dest);
if (exists && !overwrite) {
return { created: false, updated: false, usedSymlink: false };
}
await ensureDirectoryExists(dirname(dest));
try {
const symlinkType = isDir
? process.platform === Platform.Win32
? SymlinkType.Junction
: SymlinkType.Dir
: SymlinkType.File;
const relativeSrc = relative(dirname(dest), src);
await symlink(relativeSrc, dest, symlinkType);
if (exists && overwrite) {
return { created: false, updated: true, usedSymlink: true };
}
return { created: true, updated: false, usedSymlink: true };
}
catch (error) {
const nodeError = error;
console.warn(`Symlink failed (${nodeError.code}), falling back to copy: ${dest}`);
try {
if (isDir) {
const result = await copyDirectory(src, dest, overwrite);
return {
created: result.created > 0 && !exists,
updated: result.created > 0 && exists,
usedSymlink: false,
};
}
const result = await copyFile(src, dest, overwrite);
return {
created: result.created,
updated: result.updated,
usedSymlink: false,
};
}
catch (fallbackError) {
throw new FileOperationError(`Both symlink and copy operations failed for: ${dest}`, dest, "symlink or copy");
}
}
}
export async function writeTextFile(dest, content, overwrite) {
const exists = await pathExists(dest);
if (exists && !overwrite) {
return { created: false, updated: false };
}
if (exists && overwrite) {
try {
const existingContent = await readFile(dest, "utf8");
if (existingContent === content) {
return { created: false, updated: false };
}
}
catch { }
}
await ensureDirectoryExists(dirname(dest));
await writeFile(dest, content, "utf8");
if (exists && overwrite) {
return { created: false, updated: true };
}
return { created: true, updated: false };
}
async function ensureDirectoryExists(dirPath) {
try {
await mkdir(dirPath, { recursive: true });
}
catch (error) {
const nodeError = error;
if (nodeError.code !== ErrorCode.Eexist) {
throw new FileOperationError(`Failed to create directory: ${dirPath}`, dirPath, "create directory");
}
}
}
export async function isDirectory(path) {
try {
const stats = await stat(path);
return stats.isDirectory();
}
catch {
return false;
}
}
export async function pathExists(path) {
try {
await stat(path);
return true;
}
catch {
return false;
}
}
//# sourceMappingURL=file.js.map