UNPKG

kist

Version:

Lightweight Package Pipeline Processor with Plugin Architecture

143 lines 5.3 kB
import fs from "fs"; import path from "path"; import { pipeline } from "stream/promises"; import { Action } from "../../core/pipeline/Action.js"; import { FileCache } from "../../core/cache/FileCache.js"; const STREAMING_THRESHOLD = 5 * 1024 * 1024; export class FileCopyAction extends Action { fileCache; constructor() { super(); this.fileCache = FileCache.getInstance(); } async execute(options) { const srcFile = options.srcFile; const srcFiles = options.srcFiles; const destDir = options.destDir; const useCache = options.useCache; const parallel = options.parallel; if ((!srcFile && !srcFiles) || !destDir) { throw new Error("Missing required options: srcFile/srcFiles or destDir."); } if (srcFiles && srcFiles.length > 0) { await this.copyMultipleFiles(srcFiles, destDir, { useCache, parallel, }); return; } if (srcFile) { await this.copySingleFile(srcFile, destDir, { useCache }); } } async copySingleFile(srcFile, destDir, options = {}) { if (options.useCache) { const hasChanged = await this.fileCache.hasFileChanged(srcFile); if (!hasChanged) { this.logDebug(`Skipping unchanged file: ${srcFile}`); return; } } this.logInfo(`Copying file from ${srcFile} to ${destDir}.`); try { await this.copyFileToDirectory(srcFile, destDir); if (options.useCache) { await this.fileCache.updateFileEntry(srcFile); } this.logInfo(`File copied successfully from ${srcFile} to ${destDir}.`); } catch (error) { this.logError(`Error copying file from ${srcFile} to ${destDir}: ${error}`); throw error; } } async copyMultipleFiles(srcFiles, destDir, options = {}) { const startTime = performance.now(); let filesToCopy = srcFiles; if (options.useCache) { filesToCopy = await this.fileCache.getChangedFiles(srcFiles); const skipped = srcFiles.length - filesToCopy.length; if (skipped > 0) { this.logInfo(`Skipping ${skipped} unchanged files.`); } } if (filesToCopy.length === 0) { this.logInfo("All files are up to date, nothing to copy."); return; } this.logInfo(`Copying ${filesToCopy.length} files to ${destDir}.`); try { if (options.parallel) { await this.copyFilesInParallel(filesToCopy, destDir, 10); } else { for (const file of filesToCopy) { await this.copyFileToDirectory(file, destDir); } } if (options.useCache) { await this.fileCache.updateFileEntries(filesToCopy); } const duration = performance.now() - startTime; this.logInfo(`Copied ${filesToCopy.length} files in ${duration.toFixed(2)}ms.`); } catch (error) { this.logError(`Error copying files: ${error}`); throw error; } } async copyFilesInParallel(srcFiles, destDir, maxConcurrent = 10) { const executing = new Set(); for (const srcFile of srcFiles) { const copyPromise = this.copyFileToDirectory(srcFile, destDir).finally(() => executing.delete(copyPromise)); executing.add(copyPromise); if (executing.size >= maxConcurrent) { await Promise.race(executing); } } await Promise.all(executing); } async copyFileToDirectory(srcFile, destDir) { try { await this.ensureDirectoryExists(destDir); const fileName = path.basename(srcFile); const destFilePath = path.join(destDir, fileName); const stat = await fs.promises.stat(srcFile); if (stat.size > STREAMING_THRESHOLD) { await this.streamCopyFile(srcFile, destFilePath); } else { await fs.promises.copyFile(srcFile, destFilePath); } } catch (error) { this.logError(`Error copying file: ${error}`); throw error; } } async streamCopyFile(srcFile, destFile) { const readStream = fs.createReadStream(srcFile); const writeStream = fs.createWriteStream(destFile); await pipeline(readStream, writeStream); } async ensureDirectoryExists(dirPath) { try { await fs.promises.mkdir(dirPath, { recursive: true }); } catch (error) { if (error instanceof Error) { const nodeError = error; if (nodeError.code !== "EEXIST") { throw nodeError; } } else { throw error; } } } describe() { return "Copies a file from a source location to a destination directory, ensuring directories exist and handling errors gracefully."; } } //# sourceMappingURL=FileCopyAction.js.map