UNPKG

@devlander/collect-exports-for-bundle

Version:

Generate comprehensive export files for TypeScript/JavaScript projects with Rollup compatibility

1,366 lines (1,332 loc) 109 kB
import { bgBlack, white, bold, bgRed, bgGreen, bgBlue, yellow, magenta, blue, red, green } from 'picocolors'; import * as fs$1 from 'fs'; import fs__default from 'fs'; import * as path from 'path'; import path__default from 'path'; import * as fs from 'fs/promises'; /** * Represents color options. * * @typedef TColor * @type {'green' | 'red' | 'blue' | 'yellow'} */ var TColor; (function (TColor) { TColor["green"] = "green"; TColor["red"] = "red"; TColor["blue"] = "blue"; TColor["yellow"] = "yellow"; TColor["bold"] = "bold"; TColor["magenta"] = "magenta"; TColor["bgWhite"] = "bgWhite"; TColor["bgRed"] = "bgRed"; TColor["bgGreen"] = "bgGreen"; TColor["bgBlack"] = "bgBlack"; TColor["bgBlue"] = "bgBlue"; })(TColor || (TColor = {})); /** * Logs a message with a specified color. * * @param color - The color to use for the message, either a predefined TColor or a TColorValue. * @param message - The message to log. */ const logWithColor = (color, message) => { switch (color) { case TColor.green: green(message); break; case TColor.red: red(message); break; case TColor.blue: blue(message); break; case TColor.magenta: magenta(message); break; case TColor.yellow: yellow(message); break; case TColor.bgBlue: bgBlue(white(bold(message))); break; case TColor.bgGreen: bgGreen(white(bold(message))); break; case TColor.bgRed: bgRed(white(bold(message))); break; default: console.log(message); // If no color is specified, just log the } }; /** * Logs a message with a specified color. * * @param message - The message to log. * @param color - The color value to use for the message. */ const logColoredMessage = (message, color) => { if (!color) color = TColor.bgBlue; logWithColor(color, message); }; /** * Logs a message based on a condition, using green for true and red for false. * * @param message - The message to log. * @param condition - The condition to determine the color of the message. */ const logMessageBasedOnCondition = (message, condition) => { const color = TColor.red; logWithColor(color, message); }; /** * Logs a failure message, typically used in catch blocks. * * @param functionName - The name of the function where the error occurred. * @param error - The error message or object to log. */ const logFailedMessage = (functionName, error) => { if (typeof error !== 'string') { error = error.toString(); } logColoredMessage(`Failed @: ${functionName}: ${error}`, TColor.red); }; /** * Logs a message about a function's execution with additional information about variables. * * @param functionName - The name of the function being logged. * @param message - The message to log. * @param variables - A key-value pair object containing variables to log with the message. */ const logMessageForFunction = (functionName, variables, message, color) => { if (!color) color = TColor.bgBlue; logColoredMessage(`${bgBlack(white(bold(` @: ${functionName} `)))} ${message ? `${message} -- ` : ''}${JSON.stringify(variables)}`, color); }; const regexDefinitions = { isCamelCase: /^[a-z][a-zA-Z0-9]*$/, containsUnderscore: /_/, containsDash: /-/, containsDuplicateDriveLetters: /([a-zA-Z]):.*\1:/i, isDashCase: /^[a-z0-9]+(-[a-z0-9]+)*$/, containsSpecialChar: /[^a-zA-Z0-9]+/, driveLetterPattern: /^([a-z]):/i, isSnakeCase: /^[a-z0-9]+(?:_[a-z0-9]+)*$/, isPascalCase: /^[A-Z][a-zA-Z0-9]*$/, isConstantCase: /^[A-Z]+(_[A-Z0-9]+)*$/, containsNonWordChar: /[^\w]/, defaultFunctionRegex: /export default function (\w+)\(\)/, matchesDefaultExport: /export default (\w+)/, matchesExportNamedAsDefault: /export \{ (\w+) as default \} from ['"](\.\/\w+)['"];?/, matchesNamedExport: /export\s+(const|let|var|type|enum|interface|class|function)\s+[a-zA-Z_$][0-9a-zA-Z_$]*|{\s*[a-zA-Z_$][0-9a-zA-Z_$]*\s*}/, matchesTypeExport: /export\s+type\s+([a-zA-Z_$][0-9a-zA-Z_$]*)(\s*=|\s+)/g, matchesInterfaceExport: /export\s+interface\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*{[\s\S]*?}/g, matchesFunctionExport: /export\s+function\s+([a-zA-Z_$][0-9a-zA-Z_$]*)/g, matchesConstExport: /export\s+const\s+([a-zA-Z_$][0-9a-zA-Z_$]*)(:\s*[^=]+)?\s*(=|\()/g, matchesLetExport: /export\s+let\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*=/g, matchesVarExport: /export\s+var\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*=/g, matchesEnumExport: /export\s+enum\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*{[\s\S]*?}/g, matchesClassExport: /export\s+class\s+([a-zA-Z_$][0-9a-zA-Z_$]*)/g }; // export interface ThemeBase { // colors: ColorsInterface; // fonts: FontsInterface; // darkThemeEnabled?: boolean; // padding?: PaddingOnThemeType; // elevation?: ElevationType; // deviceOnTheme: DeviceOnTheme; // } // Test for this // export interface TextInterfaceNative // extends // export type NativeTheme = GenericTheme<number>; // export type WebTheme = GenericTheme<string | number>; // this is what is inside of regexDefinitions // export const regexDefinitions = { // isCamelCase: /^[a-z][a-zA-Z0-9]*$/, // containsUnderscore: /_/, // containsDash: /-/, // containsDuplicateDriveLetters: /(^[a-zA-Z]:)(\\[a-zA-Z]:)/, // isDashCase: /^[a-z0-9]+(-[a-z0-9]+)*$/, // containsSpecialChar: /[^a-zA-Z0-9]/, // isSnakeCase: /^[a-z]+(_[a-z0-9]+)*$/, // isPascalCase: /^[A-Z][a-zA-Z0-9]*$/, // isConstantCase: /^[A-Z]+(_[A-Z0-9]+)*$/, // containsNonWordChar: /[^\w]/g, // matchesDefaultExport: /export default (\w+)/, // matchesNamedExport: // /export\s+(const|let|var|type|enum|interface|class|function)\s+[a-zA-Z_$][0-9a-zA-Z_$]*|{\s*[a-zA-Z_$][0-9a-zA-Z_$]*\s*}/, // matchesTypeExport: /export\s+type\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*=/g, // matchesInterfaceExport: // /export\s+interface\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*{/g, // matchesFunctionExport: /export\s+function\s+([a-zA-Z_$][0-9a-zA-Z_$]*)/g, // matchesConstExport: /export\s+const\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*=/g, // matchesLetExport: /export\s+let\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*=/g, // matchesVarExport: /export\s+var\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*=/g, // matchesEnumExport: /export\s+enum\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*{/g, // matchesClassExport: /export\s+class\s+([a-zA-Z_$][0-9a-zA-Z_$]*)/g // } function isCamelCase(str, debug) { if (!regexDefinitions.isCamelCase.test(str) || regexDefinitions.containsUnderscore.test(str) || regexDefinitions.isDashCase.test(str) || regexDefinitions.containsSpecialChar.test(str)) { return false; } else { return true; } } function toCamelCase(str) { // replace any special characters with a space if (isCamelCase(str)) { return str; // Return the string as-is if it's already in camelCase } if (regexDefinitions.containsSpecialChar.test(str)) { str = str.replace(regexDefinitions.containsSpecialChar, '_'); } if (regexDefinitions.containsDash.test(str)) { str = str.replace(regexDefinitions.containsDash, '_'); } str = str.trim(); if (!str) { return str; // Return empty string if input is empty } // Replace special characters with a space // Function to capitalize the first letter of a word const capitalize = word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); // Split the string by non-alphanumeric characters (like spaces, dashes, underscores) const parts = str.split(/[-_ ]+/); // Convert the first part to lowercase, capitalize the subsequent parts, and join them return parts.map((part, index) => index === 0 ? part.toLowerCase() : capitalize(part)).join(''); } /** * Simulate a custom progress bar. * @param {string} message - The message to display while progressing. * @param {number} total - The total number of steps. * @param {number} current - The current step. */ const simulateProgressBar = (message, total, current) => { const percentage = (current / total * 100).toFixed(2); const progressBar = `[${'='.repeat(current)}${' '.repeat(total - current)}] ${percentage}%`; logColoredMessage(`${message} ${progressBar}`, 'blue'); }; const createExtension = (mainWord, additionalWord, fileExt) => { // Helper function to check if the string has a leading dot and add one if not const ensureLeadingDot = str => str && !str.startsWith('.') ? `.${str}` : str; mainWord = ensureLeadingDot(mainWord); additionalWord = ensureLeadingDot(additionalWord); fileExt = ensureLeadingDot(fileExt); return `${mainWord}${additionalWord}${fileExt}`; }; const createExtensions = (word = '', wordList = [], fileExtensions = [], debug) => { try { if (!word || word === ' ') { word = ''; } else { word = word.trim(); } const dev = process.env.NODE_ENV === 'development'; if (debug || dev) { logColoredMessage(`Creating extensions for ${word}...`, 'yellow'); } if (!fileExtensions.length) { return []; } const combinedExtensions = new Set(); const totalSteps = wordList.length * fileExtensions.length + fileExtensions.length; let currentStep = 0; const updateProgress = () => { simulateProgressBar('Creating extensions progress', totalSteps, ++currentStep); }; // Begin processing fileExtensions.forEach(fileExtension => { combinedExtensions.add(createExtension(word, '', fileExtension)); if (debug || dev) { updateProgress(); // Update progress each time an extension is created } }); wordList.forEach(additionalWord => { fileExtensions.forEach(fileExtension => { const createdExtension = createExtension(word, additionalWord, fileExtension); combinedExtensions.add(createdExtension); if (debug || dev) { updateProgress(); // Update progress each time an extension is created } if (additionalWord) { const invertedExtension = createExtension(additionalWord, word, fileExtension); combinedExtensions.add(invertedExtension); if (debug || dev) { updateProgress(); // Update progress also for the inverted extension } } }); }); if (debug || dev) { logColoredMessage(`Finished creating extensions for ${word}: ${Array.from(combinedExtensions).join(', ')}`, 'green'); } return Array.from(combinedExtensions); } catch (error) { // Handle or log the error if (debug) { logFailedMessage('createExtensions', error); } return []; // Uncomment if you want to return a default value } }; const filterExtension = (extension, filterFromExtensionsOrWords) => { // Split the extension into parts based on '.' const extensionParts = extension.split('.').filter(Boolean); // Iterate through each filter for (const filter of filterFromExtensionsOrWords) { // Split the filter into parts const filterParts = filter.split('.').filter(Boolean); // If the filter is a single word (not an extension), check if it's included in any part of the extension if (filterParts.length === 1 && filter.indexOf('.') === -1) { if (extensionParts.includes(filter)) { return null; } } // Check if all parts of the filter are present in the extension in order else if (filterParts.length <= extensionParts.length) { const startIndex = extensionParts.length - filterParts.length; const matchingPart = extensionParts.slice(startIndex).join('.'); if ('.' + matchingPart === filter) { return null; } } } // If none of the above conditions are met, return the extension return extension; }; const getExtensions = (extensions, extensionsFilteredByWords = [], debug = false, forWhat = '') => { if (debug) { logColoredMessage(`Getting extensions for ${forWhat}`, 'yellow'); } const filteredExtensions = extensions.map(extension => filterExtension(extension, extensionsFilteredByWords)).filter(extension => extension !== null); if (debug) { if (filteredExtensions.length > 0) { logColoredMessage(`Filtered Extensions for ${forWhat}: ${filteredExtensions.join(', ')}`, 'blue'); } else { logFailedMessage(`No extensions passed the filter for ${forWhat}`, 'red'); } } return filteredExtensions; }; /** * Clean export generator that eliminates conflicts between different export modes * and provides comprehensive Rollup compatibility */ class ExportGenerator { constructor(config) { this.config = config; this.rollupConfig = this.initializeRollupConfig(); } /** * Initialize Rollup compatibility configuration with defaults */ initializeRollupConfig() { var _this$config$pathReso, _this$config$pathReso2, _this$config$pathReso3, _this$config$typescri, _this$config$typescri2, _this$config$typescri3, _this$config$typescri4, _this$config$validati, _this$config$validati2, _this$config$validati3, _this$config$validati4, _this$config$performa, _this$config$performa2, _this$config$performa3, _this$config$performa4; return { rollupCompatible: this.config.rollupCompatible || false, includeExtensions: this.config.includeExtensions || false, bundlerTarget: this.config.bundlerTarget || 'node', pathResolution: { mode: ((_this$config$pathReso = this.config.pathResolution) === null || _this$config$pathReso === void 0 ? void 0 : _this$config$pathReso.mode) || 'auto', extensions: ((_this$config$pathReso2 = this.config.pathResolution) === null || _this$config$pathReso2 === void 0 ? void 0 : _this$config$pathReso2.extensions) || ['.ts', '.tsx'], resolveStrategy: ((_this$config$pathReso3 = this.config.pathResolution) === null || _this$config$pathReso3 === void 0 ? void 0 : _this$config$pathReso3.resolveStrategy) || 'smart' }, typescript: { generateBarrelExports: ((_this$config$typescri = this.config.typescript) === null || _this$config$typescri === void 0 ? void 0 : _this$config$typescri.generateBarrelExports) || false, includeTypeOnlyExports: ((_this$config$typescri2 = this.config.typescript) === null || _this$config$typescri2 === void 0 ? void 0 : _this$config$typescri2.includeTypeOnlyExports) || true, preserveJSDocComments: ((_this$config$typescri3 = this.config.typescript) === null || _this$config$typescri3 === void 0 ? void 0 : _this$config$typescri3.preserveJSDocComments) || true, exportStrategy: ((_this$config$typescri4 = this.config.typescript) === null || _this$config$typescri4 === void 0 ? void 0 : _this$config$typescri4.exportStrategy) || 'comprehensive' }, validation: { validatePaths: ((_this$config$validati = this.config.validation) === null || _this$config$validati === void 0 ? void 0 : _this$config$validati.validatePaths) || false, checkCircularDependencies: ((_this$config$validati2 = this.config.validation) === null || _this$config$validati2 === void 0 ? void 0 : _this$config$validati2.checkCircularDependencies) || false, validateTypeScript: ((_this$config$validati3 = this.config.validation) === null || _this$config$validati3 === void 0 ? void 0 : _this$config$validati3.validateTypeScript) || false, failOnErrors: ((_this$config$validati4 = this.config.validation) === null || _this$config$validati4 === void 0 ? void 0 : _this$config$validati4.failOnErrors) || false }, performance: { enableCaching: ((_this$config$performa = this.config.performance) === null || _this$config$performa === void 0 ? void 0 : _this$config$performa.enableCaching) || false, cacheDirectory: ((_this$config$performa2 = this.config.performance) === null || _this$config$performa2 === void 0 ? void 0 : _this$config$performa2.cacheDirectory) || '.auto-export-cache', incrementalGeneration: ((_this$config$performa3 = this.config.performance) === null || _this$config$performa3 === void 0 ? void 0 : _this$config$performa3.incrementalGeneration) || false, parallelProcessing: ((_this$config$performa4 = this.config.performance) === null || _this$config$performa4 === void 0 ? void 0 : _this$config$performa4.parallelProcessing) || false } }; } /** * Main entry point - generates exports based on configuration */ generateExports(files) { if (this.config.debug) { logColoredMessage('Starting export generation...', 'blue'); logColoredMessage(`Export mode: ${this.config.exportMode}`, 'blue'); logColoredMessage(`Files: ${files.length}`, 'blue'); logColoredMessage(`Bundler target: ${this.rollupConfig.bundlerTarget}`, 'blue'); logColoredMessage(`Rollup compatible: ${this.rollupConfig.rollupCompatible}`, 'blue'); } // Parse files to understand their export structure this.parseFiles(files); // Choose the appropriate export strategy const strategy = this.selectExportStrategy(); // Generate exports using the selected strategy const output = strategy.generateExports(files, this.config); // Add header information output.header = this.generateHeader(); // Apply Rollup compatibility transformations if (this.rollupConfig.rollupCompatible) { output.exports = this.applyRollupCompatibility(output.exports); } // Validate exports if enabled if (this.rollupConfig.validation.validatePaths) { this.validateExportPaths(output.exports); } if (this.config.debug) { logColoredMessage(`Generated ${output.exports.length} exports`, 'green'); if (output.defaultExport) { logColoredMessage('Generated default export', 'green'); } if (output.bundleObject) { logColoredMessage('Generated bundle object', 'green'); } } return output; } /** * Apply Rollup compatibility transformations to export paths */ applyRollupCompatibility(exports) { return exports.map(exportStatement => { // Add file extensions for Rollup compatibility if (this.rollupConfig.includeExtensions) { return this.addFileExtensions(exportStatement); } return exportStatement; }); } /** * Add file extensions to export paths for Rollup compatibility */ addFileExtensions(exportStatement) { // Match export * from "path" patterns const exportPattern = /export \* from ['"]([^'"]+)['"];?/g; return exportStatement.replace(exportPattern, (match, path) => { // Add .ts extension if not already present if (!path.endsWith('.ts') && !path.endsWith('.tsx') && !path.endsWith('.js') && !path.endsWith('.jsx')) { return `export * from "${path}.ts";`; } return match; }); } /** * Validate export paths exist */ validateExportPaths(exports) { const fs = require('fs'); const path = require('path'); for (const exportStatement of exports) { const pathMatch = exportStatement.match(/from ['"]([^'"]+)['"]/); if (pathMatch) { const exportPath = pathMatch[1]; const fullPath = path.join(this.config.rootDir || 'src', exportPath); if (!fs.existsSync(fullPath)) { const error = `Export path not found: ${exportPath} (resolved to: ${fullPath})`; if (this.rollupConfig.validation.failOnErrors) { throw new Error(error); } else { logColoredMessage(error, 'red'); } } } } } /** * Parse files to understand their export structure */ parseFiles(files) { return files.map(file => { const name = this.extractFileName(file); const relativePath = this.makeRelativePath(file); return { path: file, name, relativePath, hasDefaultExport: false, hasNamedExports: true, exports: [] // Will be populated by actual parsing }; }); } /** * Select the appropriate export strategy based on configuration */ selectExportStrategy() { if (this.config.bundleAsObjectForDefaultExport) { return new BundleObjectStrategy(); } else if (this.config.primaryExportFile) { return new PrimaryFileStrategy(); } else { return new StandardStrategy(); } } /** * Generate header information for the export file */ generateHeader() { const header = []; if (this.config.title || this.config.description) { header.push('/**'); if (this.config.title) { header.push(` * ${this.config.title}`); } if (this.config.description) { header.push(` * ${this.config.description}`); } header.push(' * Generated automatically by @devlander/collect-exports-for-bundle'); header.push(' */'); header.push(''); } return header; } /** * Extract file name without extension */ extractFileName(filePath) { const fileName = filePath.split('/').pop() || filePath.split('\\').pop() || ''; return fileName.split('.')[0]; } /** * Make path relative to root directory */ makeRelativePath(filePath) { const rootDir = this.config.rootDir || 'src'; const relativePath = filePath.replace(rootDir, '.').replace(/\\/g, '/'); return relativePath.startsWith('./') ? relativePath : `./${relativePath}`; } } /** * Strategy 1: Bundle everything into a single object * This eliminates the conflict between primaryExportFile and bundleAsObjectForDefaultExport */ class BundleObjectStrategy { name = 'Bundle Object'; description = 'Bundles all exports into a single object'; generateExports(files, config) { const imports = []; const exports = []; const bundleExports = []; files.forEach(file => { const fileName = this.extractFileName(file); const relativePath = this.makeRelativePath(file, config.rootDir || 'src'); // Import each file's default export imports.push(`import { default as ${fileName} } from '${relativePath}';`); // Add to bundle exports bundleExports.push(` ${fileName},`); }); const bundleObject = `export default {\n${bundleExports.join('\n')}\n};`; return { imports, exports, bundleObject, header: [] }; } extractFileName(filePath) { const fileName = filePath.split('/').pop() || filePath.split('\\').pop() || ''; return fileName.split('.')[0]; } makeRelativePath(filePath, rootDir) { const relativePath = filePath.replace(rootDir, '.').replace(/\\/g, '/'); return relativePath.startsWith('./') ? relativePath : `./${relativePath}`; } } /** * Strategy 2: Primary file gets default export, others get named exports */ class PrimaryFileStrategy { name = 'Primary File'; description = 'Primary file gets default export, others get named exports'; generateExports(files, config) { const imports = []; const exports = []; let defaultExport; files.forEach(file => { const fileName = this.extractFileName(file); const relativePath = this.makeRelativePath(file, config.rootDir || 'src'); // Check if this is the primary export file const isPrimaryFile = config.primaryExportFile && typeof config.primaryExportFile === 'string' && file.includes(config.primaryExportFile); if (isPrimaryFile) { // Primary file gets default export defaultExport = `export { default } from '${relativePath}';`; // Also add named exports if mode supports it if (config.exportMode === 'both') { exports.push(`export * from '${relativePath}';`); } } else { // Other files get named exports if (config.exportMode === 'named' || config.exportMode === 'both') { exports.push(`export * from '${relativePath}';`); } // Add named default export if mode supports it if (config.exportMode === 'default' || config.exportMode === 'both') { exports.push(`export { default as ${fileName} } from '${relativePath}';`); } } }); return { imports, exports, defaultExport, header: [] }; } extractFileName(filePath) { const fileName = filePath.split('/').pop() || filePath.split('\\').pop() || ''; return fileName.split('.')[0]; } makeRelativePath(filePath, rootDir) { const relativePath = filePath.replace(rootDir, '.').replace(/\\/g, '/'); return relativePath.startsWith('./') ? relativePath : `./${relativePath}`; } } /** * Strategy 3: Standard export generation based on mode */ class StandardStrategy { name = 'Standard'; description = 'Standard export generation based on export mode'; generateExports(files, config) { const imports = []; const exports = []; files.forEach(file => { const fileName = this.extractFileName(file); const relativePath = this.makeRelativePath(file, config.rootDir || 'src'); // Generate exports based on mode if (config.exportMode === 'named' || config.exportMode === 'both') { exports.push(`export * from '${relativePath}';`); } if (config.exportMode === 'default' || config.exportMode === 'both') { exports.push(`export { default as ${fileName} } from '${relativePath}';`); } }); return { imports, exports, header: [] }; } extractFileName(filePath) { const fileName = filePath.split('/').pop() || filePath.split('\\').pop() || ''; return fileName.split('.')[0]; } makeRelativePath(filePath, rootDir) { const relativePath = filePath.replace(rootDir, '.').replace(/\\/g, '/'); return relativePath.startsWith('./') ? relativePath : `./${relativePath}`; } } /** * Convenience function to generate exports */ function generateExports(files, config) { const generator = new ExportGenerator(config); return generator.generateExports(files); } /** * Format export output into a string */ function formatExportOutput(output) { const lines = []; // Add header lines.push(...output.header); // Add imports if (output.imports.length > 0) { lines.push('// Import statements'); lines.push(...output.imports); lines.push(''); } // Add exports if (output.exports.length > 0) { lines.push('// Named exports'); lines.push(...output.exports); lines.push(''); } // Add default export if (output.defaultExport) { lines.push('// Default export'); lines.push(output.defaultExport); lines.push(''); } // Add bundle object if (output.bundleObject) { lines.push('// Bundle object'); lines.push(output.bundleObject); } return lines.join('\n'); } function isFilePath(path, debug) { // Adjusted regex to handle both absolute and relative paths const regex = /^(?:[a-zA-Z]:\\|\\\\[a-z0-9_.$●-]+\\[a-z0-9_.$●-]+|\/|\.\.\/|\.\/|[a-zA-Z0-9_-]+\/)(?:[^\\/:*?"<>|\r\n]+[\\/])*[^\\/:*?"<>|\r\n]*$/; const isPath = regex.test(path); if (!isPath) { return false; } else { return true; } } const getFileNameFromExtension = (fileNameWithExtension, removeComplexExtension) => { if (!fileNameWithExtension) return ''; const parts = fileNameWithExtension.split('.'); if (parts.length === 1) { // File name without extension return fileNameWithExtension; } { // Remove simple extension, keep complex part (e.g., 'myfile.native.ts' -> 'myfile.native') return parts.slice(0, -1).join('.'); } }; function getFilenameFromPath(path, removeComplexExtension, debug) { try { if (debug) ; // Check if the path is a directory if (fs__default.existsSync(path) && fs__default.lstatSync(path).isDirectory()) { if (debug) ; return undefined; } const parts = path.split(/[/\\]/); // Split the path by either forward or backward slashes let fileName = parts.pop(); // Extract the filename part if (!fileName) { return undefined; // Return undefined if fileName is empty or undefined } if (debug) ; fileName = getFileNameFromExtension(fileName, removeComplexExtension); if (debug) ; return fileName; } catch (err) { logFailedMessage(`getFilenameFromPath`, err); console.error(err); return undefined; } } const parseComplexExtensionFromFile = (target, options) => { let debug = false; let forceIsFilePath = false; if (options) { debug = options.debug ?? false; forceIsFilePath = options.forceIsFilePath ?? false; } let payload = { extension: undefined, fileName: undefined, folderName: undefined, baseFileName: undefined, originalValue: target, words: undefined }; const wordSet = new Set(); try { const handleParsing = fileName => { // Check if target is a file (contains a dot) or a directory payload.fileName = fileName; // Match everything from the first dot to the end of the string const match = fileName.match(/\..*$/); const extension = match && match[0] ? match[0] : ''; if (debug) { logColoredMessage(`Extracted Extension: ${extension}`, 'yellow'); } if (extension) { payload.extension = extension; } if (fileName.includes('.') || extension) { if (extension) { payload.baseFileName = fileName.replace(extension, ''); } else if (fileName.includes('.')) { payload.baseFileName = fileName.split('.').shift(); } } else { payload.baseFileName = fileName; } payload.words = Array.from(wordSet); return payload; }; // Split the target string by '/' and process each part const pathParts = target.split('/'); pathParts.forEach(part => { // Further split each part by '.' and process const splitParts = part.split('.').filter(Boolean); splitParts.forEach(word => wordSet.add(word)); }); // Assign words from wordSet to the payload // Check if target is a file (contains a dot) or a directory if (target.includes('.') && !forceIsFilePath) { const fileName = pathParts.pop(); // Get the last part after the last '/' if (fileName) { payload = handleParsing(fileName); } } else if (forceIsFilePath) { const fileName = target.includes('/') ? target.split('/').pop() : target; if (fileName) { payload = handleParsing(fileName); } } else { // Handle directory if (debug) { logColoredMessage(`Path is a directory: ${target}`, 'yellow'); } payload.folderName = pathParts.pop(); } // Assign words from wordSet to the payload payload.words = Array.from(wordSet); } catch (error) { if (debug) { logFailedMessage(`Error while processing path: ${target}`, error); } return payload; } return payload; }; function parseComplexExtensionFromPath(filePath, debug) { let payload = { extension: undefined, fileName: undefined, folderName: undefined, originalValue: filePath, baseFileName: undefined, words: undefined }; try { if (isFilePath(filePath) === false) { throw new Error('Provided path is not a file path.'); } // Check if path exists before trying to get stats if (!fs__default.existsSync(filePath)) { // Path doesn't exist, but we can still parse the filename from the path const fileName = filePath.split('/').pop(); // Get the last part after the last '/' if (fileName) { payload = parseComplexExtensionFromFile(fileName, { debug, forceIsFilePath: true }); } return payload; } const stats = fs__default.lstatSync(filePath); if (!stats.isDirectory()) { const fileName = filePath.split('/').pop(); // Get the last part after the last '/' if (!fileName) { throw new Error('No file name found in the provided file path.'); } else { payload = parseComplexExtensionFromFile(fileName, { debug, forceIsFilePath: true }); } } else { if (debug) ; payload.folderName = filePath.split('/').pop(); } } catch (error) { return payload; } return payload; } const logExtensionFromExtensions = (filePath, extensions, condition, message) => { if (!message) { message = 'Existing extensions:'; } if (filePath === '') { return; } if (!isFilePath(filePath)) { logFailedMessage('logExtensionFromExtensions', `${filePath} is not a valid file path`); } const parsedExtension = parseComplexExtensionFromPath(filePath); if (!parsedExtension || typeof parsedExtension.extension === 'undefined') { red(`logExtensionFromExtensions: ${filePath} has no valid extension`); } else if (parsedExtension && typeof parsedExtension.extension !== 'undefined') { const { extension } = parsedExtension; bgBlack(`${white(bold(`@${message}` || '@'))} ${extensions.map(ext => ext === extension ? condition ? green(ext) : red(ext) : ext).join(', ')} `); } else { red(`logExtensionFromExtensions: ${filePath} has no valid extension`); } }; const hasFileLogger = (fileName, ext, color) => { logColoredMessage(`${color === 'red' ? 'Excluding' : 'Including'} file: ${fileName} with fileExtension: ${ext}`, color); }; function isValidExtension(filePath, allowedExtensions = ['.ts', '.tsx', '.types.ts'], debug) { if (!filePath || !isFilePath(filePath) || filePath === '') { red(`isValidExtension: ${filePath} is not a valid file path`); logExtensionFromExtensions(filePath ? filePath : 'No file path provided', allowedExtensions, false, 'Allowed extensions:'); return false; } const parsedExtension = parseComplexExtensionFromPath(filePath); if (!parsedExtension.fileName || !parsedExtension.extension) { logExtensionFromExtensions(filePath, allowedExtensions, false, 'Allowed extensions:'); return false; } const { fileName, extension } = parsedExtension; const filePathHasCorrectExtension = filePath.includes(extension) && allowedExtensions.includes(extension); if (debug) { logMessageForFunction('isValidExtension', { filePath, allowedExtensions, fileName, extension, filePathHasCorrectExtension }); } const isValid = filePathHasCorrectExtension; if (isValid && debug) { hasFileLogger(filePath, extension, 'green'); } else if (!isValid && debug) { hasFileLogger(filePath, extension, 'red'); } // Logging the extensions regardless of the validity of the filePath logExtensionFromExtensions(filePath, allowedExtensions, isValid, 'Allowed extensions:'); return isValid; } function notValidExtension(filePath, ignoredExtensions, debug) { let isIgnoredExtension = false; let extension = undefined; let fileName = undefined; if (isFilePath(filePath)) { const parsedExtension = parseComplexExtensionFromPath(filePath); if (typeof parsedExtension !== 'undefined') { if (typeof parsedExtension.extension !== 'undefined') { extension = parsedExtension.extension; } if (typeof parsedExtension.fileName !== 'undefined') { fileName = parsedExtension.fileName; } if (typeof extension !== 'undefined' && fileName !== 'undefined') { isIgnoredExtension = ignoredExtensions.includes(extension); if (debug) { const logColor = isIgnoredExtension ? 'red' : 'green'; hasFileLogger(filePath, extension, logColor); } } } } if (debug) { logMessageForFunction('notValidExtension', { filePath, ignoredExtensions, isIgnoredExtension, additionalInfo: 'Some additional debug info if needed' }); logExtensionFromExtensions(filePath, ignoredExtensions, isIgnoredExtension, 'Ignored extensions:'); } return isIgnoredExtension; } function fileHasValidExtension(filePath, config) { var _config$excludeSpecif, _config$specificFiles; if (filePath && isFilePath(filePath) === false || filePath === '') { return false; } // Early return for non-string file names const nameOfFile = getFilenameFromPath(filePath); if (typeof nameOfFile !== 'string') { return false; } // Debugging utility function const debugLog = (message, variables) => { if (config.debug) { logMessageForFunction('fileHasValidExtension', { variables }, message, 'yellow'); } }; // Check if the file is explicitly excluded if ((_config$excludeSpecif = config.excludeSpecificFiles) !== null && _config$excludeSpecif !== void 0 && _config$excludeSpecif.includes(nameOfFile)) { debugLog('Excluded file', { nameOfFile }); return false; } // Check if the file is explicitly included if ((_config$specificFiles = config.specificFiles) !== null && _config$specificFiles !== void 0 && _config$specificFiles.includes(nameOfFile)) { debugLog(`Returning true`, { nameOfFile }); return true; } // Check for invalid extension if (notValidExtension(filePath, config.ignoredExtensions || [], config.debug)) { debugLog(`File with invalid extension`, { nameOfFile }); return false; } // Check for valid extension if (isValidExtension(filePath, config.allowedExtensions || [], config.debug)) { debugLog(`File with valid extension`, { nameOfFile }); return true; } // If none of the above conditions are met, the file is not valid return false; } const getFileContent = filePath => { try { if (!isFilePath(filePath)) { logMessageBasedOnCondition(`getFileContent: ${filePath} is not a valid file path`, false); } if (!fs__default.existsSync(filePath)) { return ''; } else { const result = fs__default.readFileSync(filePath, 'utf8'); if (result) { return result; } else { return ''; } } } catch (error) { return ''; } }; const defaultDeclarationKeywords$1 = ['function', 'const', 'let', 'var', 'enum', 'class']; const getExportedFunctionNamesByFileContent = (fileContent, declarationsToExport, debug) => { if (!declarationsToExport || declarationsToExport && declarationsToExport.length === 0) declarationsToExport = defaultDeclarationKeywords$1; const patterns = { function: regexDefinitions.matchesFunctionExport, const: regexDefinitions.matchesConstExport, let: regexDefinitions.matchesLetExport, var: regexDefinitions.matchesVarExport, enum: regexDefinitions.matchesEnumExport, class: regexDefinitions.matchesClassExport }; const functionNames = []; let match; for (const declaration of declarationsToExport) { const pattern = patterns[declaration]; if (!pattern) continue; while ((match = pattern.exec(fileContent)) !== null) { functionNames.push(match[1]); } } if (!functionNames.length) { if (debug) { logColoredMessage(`No named exports found in ${fileContent}`, 'blue'); } return []; } if (functionNames !== null) { return functionNames; } else { return []; } }; const defaultDeclarationKeywords = ['type', 'interface']; const getExportedTypeDeclarationsByFileContent = (fileContent, declarationsToExport, debug) => { if (!declarationsToExport || declarationsToExport && declarationsToExport.length === 0) { declarationsToExport = defaultDeclarationKeywords; } const patterns = { type: regexDefinitions.matchesTypeExport, interface: regexDefinitions.matchesInterfaceExport }; const typeNames = []; let match; for (const declaration of declarationsToExport) { const pattern = patterns[declaration]; if (!pattern) continue; while ((match = pattern.exec(fileContent)) !== null) { typeNames.push(match[1]); } } if (!typeNames.length && debug) { logColoredMessage(`No exported type declarations found in ${fileContent}`, 'yellow'); return []; } if (typeNames !== null) { return typeNames; } else { return []; } }; function testForPatterns(fileContent, patterns) { let foundInPattern = false; patterns.forEach(pattern => { if (pattern.test(fileContent)) { foundInPattern = true; } }); return foundInPattern; } function hasDefaultExport(fileContent, debug) { const hasExport = testForPatterns(fileContent, [regexDefinitions.matchesDefaultExport, regexDefinitions.matchesExportNamedAsDefault]); if (debug) { logMessageForFunction('hasDefaultExport', { hasExport, fileContent }); } return hasExport; } function hasNamedExports(fileContent, debug) { const hasExport = testForPatterns(fileContent, [regexDefinitions.matchesNamedExport]); if (debug) { logMessageForFunction('hasNamedExports', { fileContent, hasExport }); } return hasExport; } const hasNoExports = (fileContent, debug) => { const exportFunctionNames = getExportedFunctionNamesByFileContent(fileContent, [], debug); const exportTypeNames = getExportedTypeDeclarationsByFileContent(fileContent, [], debug); const hasNamed = hasNamedExports(fileContent, debug); const hasDefault = hasDefaultExport(fileContent, debug); if (debug) { logMessageForFunction('hasNoExports', { hasNamed, hasDefault, fileContent }); } return !hasNamed && !hasDefault && !exportFunctionNames.length && !exportTypeNames.length; }; async function getPathsWithExports(paths, config) { try { const directoriesChecked = []; const distinctPaths = [...new Set(paths)]; // Remove duplicates using Set const filteredPaths = distinctPaths.filter(path => { const hasValidExtension = fileHasValidExtension(path, config); if (config.debug) { logMessageForFunction('getPathsWithExports', { path, hasValidExtension: hasValidExtension }, 'yellow'); } if (hasValidExtension) { // check to see if the file has either a default export or named exports // if it doesn't, it's not a valid file const fileOutput = getFileContent(path).toString(); if (config.debug) { logMessageForFunction('getPathsWithExports', { fileOutput }, 'this is file since file has valid extension', 'yellow'); } const noExports = hasNoExports(fileOutput, config.debug); if (config.debug) { logMessageForFunction('getPathsWithExports', { noExports }, 'this is noExports', 'yellow'); } if (!noExports) { // get the directory from the path const directory = path.split('/').slice(0, -1).join('/'); // check to see if the directory has already been checked directoriesChecked.push(directory); return path; } } }); logMessageForFunction('getPathsWithExports', { distinctPaths, filteredPaths, directoriesChecked }, 'yellow'); return filteredPaths; } catch (err) { logFailedMessage('collectPathsFromDirectories', err); return []; } } const removeFoldersFromPaths = (paths, foldersToRemove) => { return paths.filter(p => { const pathParts = p.split(path__default.sep); // Check if the path includes any of the foldersToRemove const containsFolderToRemove = pathParts.some(part => foldersToRemove.includes(part)); if (containsFolderToRemove) { try { // Check if it's a real path and a folder const stats = fs__default.statSync(p); return !stats.isDirectory(); } catch (error) { // Error handling, e.g., path does not exist or no permission console.error(`Error accessing path "${p}": ${error}`); return false; } } return true; }); }; var ResultItemType; (function (ResultItemType) { ResultItemType["IncludedFolder"] = "includedFolders"; ResultItemType["IncludedFile"] = "includedFiles"; ResultItemType["ExcludedFolder"] = "excludedFolders"; ResultItemType["ExcludedFile"] = "excludedFiles"; ResultItemType["IncludedExport"] = "includedExports"; ResultItemType["ExcludedExport"] = "excludedExports"; })(ResultItemType || (ResultItemType = {})); const pushToResults = (results, itemType, result) => { const tempResults = { ...results }; // Clone to avoid direct mutation // Function to check if an item with the same name or path already exists const findExistingItem = items => { return items.find(item => item.nameOrPath === result.nameOrPath); }; let existingItem; const resultCategory = tempResults[itemType]; switch (itemType) { case ResultItemType.IncludedFolder: case ResultItemType.IncludedFile: case ResultItemType.ExcludedFolder: case ResultItemType.ExcludedFile: case ResultItemType.IncludedExport: case ResultItemType.ExcludedExport: existingItem = findExistingItem(resultCategory); if (existingItem) { existingItem.reason.push(result.reason[0]); } else { resultCategory.push(result); } break; } return tempResults; }; function correctDuplicateDriveLetters(path) { const isTestEnvironment = process.env.NODE_ENV === 'test'; if (!isTestEnvironment) { return path; } // Regular expression to match duplicated drive letters const duplicatedDriveLetterPattern = /^([a-z]:\\)([a-z]:\\)/i; if (duplicatedDriveLetterPattern.test(path)) { // Replace the duplicated drive letter with a single instance const correctedPath = path.replace(duplicatedDriveLetterPattern, '$1'); if (isTestEnvironment) { logColoredMessage(`Corrected path: ${correctedPath}`, 'green'); } return correctedPath; } return path; // Return the original path if no duplicates are found } const getAbsolutePath = async (startPath, config) => { try { let myPath = path__default.normalize(path__default.resolve(startPath)); logColoredMessage(`Starting to get absolute path from ${myPath}`, 'blue'); myPath = correctDuplicateDriveLetters(myPath); logColoredMessage(`Corrected path to ${myPath}`, 'blue'); let pathsToTry = [myPath, path__default.normalize(path__default.resolve(startPath)), correctDuplicateDriveLetters(path__default.normalize(path__default.resolve(startPath))), `./${startPath}/`, `${startPath}/`]; if (config && config.excludedFolders) { red(`${config.excludedFolders} is excluded`); pathsToTry = removeFoldersFromPaths(pathsToTry, config.excludedFolders); } logMessageForFunction('getAbsolutePath', { pathsToTry, config }); const validPaths = []; let absolutePath; for (const pathToTry of pathsToTry) { if (isFilePath(pathToTry)) { if (config.debug) { logColoredMessage(`Checking path: ${pathToTry}`, 'magenta'); } try { const stat = await fs.lstat(pathToTry); if (stat.isDirectory()) { var _config$excludedFolde; const directoryFiles = await fs.readdir(pathToTry); const indexFiles = directoryFiles.filter(file => file.startsWith('index.') && config.allowedExtensions.includes(path__default.extname(file))); // Determine whether to include index files if (config.includeIndexes && indexFiles.length > 0 && pathToTry !== startPath) { indexFiles.forEach(indexFile => { validPaths.push(path__default.join(pathToTry, indexFile)); }); } const relativePath = path__default.relative(startPath, pathToTry); const isInExcludedFolder = (_config$excludedFolde = config.excludedFolders) === null || _config$excludedFolde === void 0 ? void 0 : _config$excludedFolde.some(excludedFolder => relativePath.includes(path__default.normalize(excludedFolder))); if (!isInExcludedFolder) { if (config.debug) { logMessageForFunction('getAbsolutePath', { isInExcludedFolder, relativePath, pathToTry }, 'Adding to validPaths'); } validPaths.push(pathToTry); if (!absolutePath) { absolutePath = pathToTry; } } else { if (config.debug) { logMessageForFunction('getAbsolutePath', { isInExcludedFolder, relativePath, pathToTry }, 'Not adding to validPaths'); } } } } catch (error) { if (config.debug) { logColoredMessage(`Error accessing path: ${pathToTry}`, 'red'); } } } } return { paths: validPaths, absolutePath }; } catch (error) { let errorMessage = 'An error occurred'; if (error instanceof Error) { errorMessage = error.message; } if (config.debug) { logFailedMessage('getAbsolutePath', error); } red(`Error accessing path: ${errorMessage}`); return { paths: [] }; } }; const getCachedDirectory = async (filepath, config) => { try { const result = (await fs.lstat(filepath)).isDirectory(); if (config !== null && config !== void 0 && config.debug) ; return result; } catch (error) { if (error instanceof Error) { bgRed(white(bold(error.message)) + ' in getCachedDirectory'); } return false; } }; async function collectPaths(startPath, config) { if (isFilePath(startPath) === false || startPath === '') { return []; } let paths = []; let absolutePath = undefined; const resultFromGetAbsolutePath = await getAbsolutePath(startPath, { debug: config.debug, results: config.results, includeIndexes: config.includeIndexes ? config.includeIndexes : false, excludedFolders: config.excludedFolders ? config.excludedFolders : [], allowedExtensions: config && config.allowedExtensions ? config.allowedExtensions : [] }); paths = resultFromGetAbsolutePath.paths; absolutePath = resultFromGetAbsolutePath.absolutePath ?? undefined; if (paths && config.excludedFolders) { paths = removeFoldersFromPaths(paths, config.excludedFolders); } if (!absolutePath) { if (!paths.length) { consol