Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ jobs:

- name: Build
run: bun run build

- name: Tests
run: bun run test
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"scripts": {
"build": "tsup",
"dev": "tsx src/index.ts",
"test": "tsx tests/fs.test.ts",
"prepublishOnly": "npm run build",
"check:lint": "biome check . --diagnostic-level=error",
"check:unsafe": "biome check . --write --unsafe --diagnostic-level=error",
Expand Down
61 changes: 59 additions & 2 deletions src/utils/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
readFileSync,
readlinkSync,
renameSync,
rmdirSync,
statSync,
symlinkSync,
unlinkSync,
Expand Down Expand Up @@ -85,6 +86,17 @@ export function copyFile(src: string, dest: string): void {
copyFileSync(src, dest);
}

// Directories to skip when copying (version control, package managers, caches)
const SKIP_DIRECTORIES = new Set([
".git",
".svn",
".hg",
"node_modules",
".cache",
"__pycache__",
".DS_Store",
]);

export function copyDir(src: string, dest: string): void {
ensureDir(dest);
const entries = readdirSync(src, { withFileTypes: true });
Expand All @@ -93,10 +105,56 @@ export function copyDir(src: string, dest: string): void {
const srcPath = join(src, entry.name);
const destPath = join(dest, entry.name);

// Skip entries in the skip list
if (SKIP_DIRECTORIES.has(entry.name)) {
continue;
}

// Handle symlinks - preserve them rather than following
if (entry.isSymbolicLink()) {
try {
const linkTarget = readlinkSync(srcPath);
// Check if target exists to avoid broken symlinks
if (existsSync(srcPath)) {
// Recreate the symlink at destination
ensureParentDir(destPath);
try {
// Remove existing file/symlink if present
unlinkSync(destPath);
} catch {
// Ignore if doesn't exist
}
symlinkSync(linkTarget, destPath);
}
// Skip broken symlinks silently
} catch {
// Skip symlinks that can't be read
}
continue;
}

if (entry.isDirectory()) {
copyDir(srcPath, destPath);
} else {
copyFileSync(srcPath, destPath);
// Skip special file types
if (
entry.isSocket() ||
entry.isFIFO() ||
entry.isCharacterDevice() ||
entry.isBlockDevice()
) {
continue;
}
try {
copyFileSync(srcPath, destPath);
} catch (error) {
// Skip files that can't be copied (permission issues, etc.)
// but don't fail the entire operation
const nodeError = error as NodeJS.ErrnoException;
if (nodeError.code !== "ENOENT" && nodeError.code !== "EACCES") {
throw error;
}
}
}
}
}
Expand All @@ -115,7 +173,6 @@ export function removeDir(path: string): void {
}

// Remove the directory itself
const { rmdirSync } = require("node:fs");
rmdirSync(path);
}

Expand Down
Loading
Loading