@devlander/collect-exports-for-bundle
Version:
Generate comprehensive export files for TypeScript/JavaScript projects with Rollup compatibility
1,369 lines (1,331 loc) • 111 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var picocolors = require('picocolors');
var fs = require('fs');
var path = require('path');
var fs$1 = require('fs/promises');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var fs__namespace$1 = /*#__PURE__*/_interopNamespaceDefault(fs);
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs$1);
/**
* 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:
picocolors.green(message);
break;
case TColor.red:
picocolors.red(message);
break;
case TColor.blue:
picocolors.blue(message);
break;
case TColor.magenta:
picocolors.magenta(message);
break;
case TColor.yellow:
picocolors.yellow(message);
break;
case TColor.bgBlue:
picocolors.bgBlue(picocolors.white(picocolors.bold(message)));
break;
case TColor.bgGreen:
picocolors.bgGreen(picocolors.white(picocolors.bold(message)));
break;
case TColor.bgRed:
picocolors.bgRed(picocolors.white(picocolors.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(`${picocolors.bgBlack(picocolors.white(picocolors.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.existsSync(path) && fs.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.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.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') {
picocolors.red(`logExtensionFromExtensions: ${filePath} has no valid extension`);
} else if (parsedExtension && typeof parsedExtension.extension !== 'undefined') {
const {
extension
} = parsedExtension;
picocolors.bgBlack(`${picocolors.white(picocolors.bold(`@${message}` || '@'))}
${extensions.map(ext => ext === extension ? condition ? picocolors.green(ext) : picocolors.red(ext) : ext).join(', ')}
`);
} else {
picocolors.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 === '') {
picocolors.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.existsSync(filePath)) {
return '';
} else {
const result = fs.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.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.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.normalize(path.resolve(startPath));
logColoredMessage(`Starting to get absolute path from ${myPath}`, 'blue');
myPath = correctDuplicateDriveLetters(myPath);
logColoredMessage(`Corrected path to ${myPath}`, 'blue');
let pathsToTry = [myPath, path.normalize(path.resolve(startPath)), correctDuplicateDriveLetters(path.normalize(path.resolve(startPath))), `./${startPath}/`, `${startPath}/`];
if (config && config.excludedFolders) {
picocolors.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__namespace.lstat(pathToTry);
if (stat.isDirectory()) {
var _config$excludedFolde;
const directoryFiles = await fs__namespace.readdir(pathToTry);
const indexFiles = directoryFiles.filter(file => file.startsWith('index.') && config.allowedExtensions.includes(path.extname(file)));
// Determine whether to include index files
if (config.includeIndexes && indexFiles.length > 0 && pathToTry !== startPath) {
indexFiles.forEach(indexFile => {
validPaths.push(path.join(pathToTry, indexFile));
});
}
const relativePath = path.relative(startPath, pathToTry);
const isInExcludedFolder = (_config$excludedFolde = config.excludedFolders) === null || _config$excludedFolde === void 0 ? void 0 : _config$excludedFolde.some(excludedFolder => relativePath.includes(path.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);
}
picocolors.red(`Error accessing path: ${errorMessage}`);
return {
paths: []
};
}
};
const getCachedDirectory = async (filepath, config) => {
try {
const result = (await fs__namespace.lstat(filepath)).isDirectory();
if (config !== null && config !== void 0 && config.debug) ;
return result;
} catch (error) {
if (error instanceof Error) {
picocolors.bgRed(picocolors.white(picocolors.bold(error.message)) + ' in getCachedDirectory');
}
return false;
}
};
async function