| 1 | import fs from 'fs' |
| 2 | import path from 'path' |
| 3 | import { isPathInside } from './template-paths' |
| 4 | |
| 5 | const EXCLUDED_NAMES = new Set([ |
| 6 | '.git', |
| 7 | '.gitignore', |
| 8 | 'node_modules', |
| 9 | 'docs', |
| 10 | 'tmp', |
| 11 | 'history', |
| 12 | '.DS_Store' |
| 13 | ]) |
| 14 | |
| 15 | function shouldExclude(name: string, extraExclude?: Set<string>): boolean { |
| 16 | return EXCLUDED_NAMES.has(name) || Boolean(extraExclude?.has(name)) || name.endsWith('.log') |
| 17 | } |
| 18 | |
| 19 | export async function copyDirExcluding( |
| 20 | sourceDir: string, |
| 21 | targetDir: string, |
| 22 | options?: { exclude?: string[] } |
| 23 | ): Promise<void> { |
| 24 | if (!fs.existsSync(sourceDir)) return |
| 25 | const sourceRoot = await fs.promises.realpath(sourceDir) |
| 26 | const extraExclude = new Set(options?.exclude || []) |
| 27 | await fs.promises.mkdir(targetDir, { recursive: true }) |
| 28 | |
| 29 | const copyEntry = async (sourcePath: string, targetPath: string): Promise<void> => { |
| 30 | const name = path.basename(sourcePath) |
| 31 | if (shouldExclude(name, extraExclude)) return |
| 32 | |
| 33 | const realSource = await fs.promises.realpath(sourcePath).catch(() => sourcePath) |
| 34 | if (!isPathInside(realSource, sourceRoot)) return |
| 35 | |
| 36 | const stat = await fs.promises.stat(sourcePath) |
| 37 | if (stat.isDirectory()) { |
| 38 | await fs.promises.mkdir(targetPath, { recursive: true }) |
| 39 | const entries = await fs.promises.readdir(sourcePath) |
| 40 | await Promise.all( |
| 41 | entries.map((entry) => copyEntry(path.join(sourcePath, entry), path.join(targetPath, entry))) |
| 42 | ) |
| 43 | return |
| 44 | } |
| 45 | |
| 46 | if (stat.isFile()) { |
| 47 | await fs.promises.mkdir(path.dirname(targetPath), { recursive: true }) |
| 48 | await fs.promises.copyFile(sourcePath, targetPath) |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | const entries = await fs.promises.readdir(sourceDir) |
| 53 | await Promise.all(entries.map((entry) => copyEntry(path.join(sourceDir, entry), path.join(targetDir, entry)))) |
| 54 | } |
| 55 |