UNPKG

kt-extendscript-builder

Version:

Vite based builder for transpile TypeScript to ExtendScript

144 lines (143 loc) 5.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Cleaner = void 0; const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const rimraf_1 = __importDefault(require("rimraf")); const util_1 = require("util"); const rimrafAsync = (0, util_1.promisify)(rimraf_1.default); /** * Class responsible for safely cleaning output directories */ class Cleaner { /** * Safely cleans the output directory * @param options Build options */ static async cleanDist(options) { if (!this.validateOptions(options)) { return; } if (!fs_1.default.existsSync(options.output)) { console.warn(`Output file ${options.output} does not exist. Cleaning will not be performed.`); return; } const stat = fs_1.default.lstatSync(options.output); const isFile = stat.isFile(); const isDirectory = stat.isDirectory(); const distPath = isFile ? path_1.default.dirname(options.output) : isDirectory ? path_1.default.resolve(options.output) : ''; if (!this.validateDistPath(distPath)) { return; } try { await this.performClean(distPath, options.output); } catch (error) { console.error(`Error cleaning ${distPath}:`, error); } } /** * Validates that the options are correct for cleaning * @param options Build options * @returns true if options are valid, false otherwise */ static validateOptions(options) { if (!options.output) { console.warn('No output file specified, cannot clean'); return false; } return true; } /** * Verifies that the output directory exists and is safe to clean * @param distPath Path to the output directory * @returns true if the directory is valid and safe, false otherwise */ static validateDistPath(distPath) { // Check if the directory exists if (!fs_1.default.existsSync(distPath)) { console.warn(`${distPath} does not exist. Skipping cleaning`); return false; } const absolutePath = path_1.default.resolve(distPath); const projectRoot = process.cwd(); // Check that the directory to clean is a subdirectory of the project // and not the project root directory itself if (!absolutePath.startsWith(projectRoot) || absolutePath === projectRoot) { console.error(`Security warning! ${absolutePath} will not be cleaned as it is not a safe project subdirectory`); return false; } // Verify it's not a protected directory const relativePath = path_1.default.relative(projectRoot, absolutePath); const pathParts = relativePath.split(path_1.default.sep); for (const part of pathParts) { // We check each segment of the path to verify it's not a protected folder // We exclude paths like "dist.test" that contain "test" but are valid for cleaning if (this.PROTECTED_FOLDERS.some((folder) => part === folder || // It's exactly a protected folder (part === 'test' && pathParts[0] !== 'dist.test') // It's 'test' but not inside 'dist.test' )) { console.error(`Security warning! ${absolutePath} will not be cleaned as it is a protected project directory`); return false; } } return true; } /** * Performs the cleaning of the directory or file * @param distPath Path to the output directory * @param outputPath Complete path to the output file */ static async performClean(distPath, outputPath) { const outputFilename = path_1.default.basename(outputPath); const outputFilePath = path_1.default.join(distPath, outputFilename); // If the specific file exists, delete only that file if (fs_1.default.existsSync(outputFilePath)) { await fs_1.default.promises.unlink(outputFilePath); console.log(`File cleaned: ${outputFilePath}`); return; } // If it's a directory, delete its contents but keep the directory const files = await fs_1.default.promises.readdir(distPath); if (files.length === 0) { console.log(`Directory ${distPath} is empty. Nothing to clean.`); return; } // Safe deletion file by file for (const file of files) { const filePath = path_1.default.join(distPath, file); await this.removeFileOrDirectory(filePath); } console.log(`Directory cleaned: ${distPath}`); } /** * Safely removes a file or directory * @param filePath Path to the file or directory to remove */ static async removeFileOrDirectory(filePath) { const stats = await fs_1.default.promises.lstat(filePath); if (stats.isDirectory()) { // Additional security check for subdirectories const dirName = path_1.default.basename(filePath); if (this.PROTECTED_FOLDERS.includes(dirName)) { console.warn(`Skipping protected folder: ${filePath}`); return; } await rimrafAsync(filePath); console.log(`Subdirectory removed: ${filePath}`); } else { await fs_1.default.promises.unlink(filePath); console.log(`File removed: ${filePath}`); } } } exports.Cleaner = Cleaner; // Critical folders that should not be deleted Cleaner.PROTECTED_FOLDERS = ['src', 'node_modules'];